mirror of
https://github.com/GrammaticalFramework/gf-core.git
synced 2026-06-15 04:50:20 -06:00
More efficient implementation of topological sort.
Profiling the compilation of the OALD lexicon showed that 90-95% of the time was spent in topoSort. The old implementation was quadratic. Replaced this with O(E + V) implementation, in GF.Data.Relation. This gave a 10x speed-up (~ 25 sec instead of ~270 sec) for compiling ParseEng and OaldEng.
This commit is contained in:
@@ -22,12 +22,16 @@ module GF.Data.Relation (Rel, mkRel, mkRel'
|
||||
, equivalenceClasses
|
||||
, isTransitive, isReflexive, isSymmetric
|
||||
, isEquivalence
|
||||
, isSubRelationOf) where
|
||||
, isSubRelationOf
|
||||
, topologicalSort) where
|
||||
|
||||
import Data.Foldable (toList)
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
import Data.Sequence (Seq)
|
||||
import qualified Data.Sequence as Seq
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as Set
|
||||
|
||||
@@ -44,7 +48,7 @@ mkRel ps = relates ps Map.empty
|
||||
mkRel' :: Ord a => [(a,[a])] -> Rel a
|
||||
mkRel' xs = Map.fromListWith Set.union [(x,Set.fromList ys) | (x,ys) <- xs]
|
||||
|
||||
relToList :: Rel a -> [(a,a)]
|
||||
relToList :: Ord a => Rel a -> [(a,a)]
|
||||
relToList r = [ (x,y) | (x,ys) <- Map.toList r, y <- Set.toList ys ]
|
||||
|
||||
-- | Add a pair to the relation.
|
||||
@@ -67,6 +71,9 @@ allRelated r x = fromMaybe Set.empty (Map.lookup x r)
|
||||
domain :: Ord a => Rel a -> Set a
|
||||
domain r = foldl Set.union (Map.keysSet r) (Map.elems r)
|
||||
|
||||
reverseRel :: Ord a => Rel a -> Rel a
|
||||
reverseRel r = mkRel [(y,x) | (x,y) <- relToList r]
|
||||
|
||||
-- | Keep only pairs for which both elements are in the given set.
|
||||
intersectSetRel :: Ord a => Set a -> Rel a -> Rel a
|
||||
intersectSetRel s = filterRel (\x y -> x `Set.member` s && y `Set.member` s)
|
||||
@@ -98,12 +105,12 @@ reflexiveElements r = Set.fromList [ x | (x,ys) <- Map.toList r, x `Set.member`
|
||||
|
||||
-- | Keep the related pairs for which the predicate is true.
|
||||
filterRel :: Ord a => (a -> a -> Bool) -> Rel a -> Rel a
|
||||
filterRel p = purgeEmpty . Map.mapWithKey (Set.filter . p)
|
||||
filterRel p = fst . purgeEmpty . Map.mapWithKey (Set.filter . p)
|
||||
|
||||
-- | Remove keys that map to no elements.
|
||||
purgeEmpty :: Ord a => Rel a -> Rel a
|
||||
purgeEmpty r = Map.filter (not . Set.null) r
|
||||
|
||||
purgeEmpty :: Ord a => Rel a -> (Rel a, Set a)
|
||||
purgeEmpty r = let (r',r'') = Map.partition (not . Set.null) r
|
||||
in (r', Map.keysSet r'')
|
||||
|
||||
-- | Get the equivalence classes from an equivalence relation.
|
||||
equivalenceClasses :: Ord a => Rel a -> [Set a]
|
||||
@@ -128,3 +135,59 @@ isEquivalence r = isReflexive r && isSymmetric r && isTransitive r
|
||||
|
||||
isSubRelationOf :: Ord a => Rel a -> Rel a -> Bool
|
||||
isSubRelationOf r1 r2 = all (uncurry (isRelatedTo r2)) (relToList r1)
|
||||
|
||||
-- | Returns 'Left' if there are cycles, and 'Right' if there are cycles.
|
||||
topologicalSort :: Ord a => Rel a -> Either [a] [[a]]
|
||||
topologicalSort r = tsort r' noIncoming Seq.empty
|
||||
where r' = relToRel' r
|
||||
noIncoming = Seq.fromList [x | (x,(is,_)) <- Map.toList r', Set.null is]
|
||||
|
||||
tsort :: Ord a => Rel' a -> Seq a -> Seq a -> Either [a] [[a]]
|
||||
tsort r xs l = case Seq.viewl xs of
|
||||
Seq.EmptyL | isEmpty' r -> Left (toList l)
|
||||
| otherwise -> Right (findCycles (rel'ToRel r))
|
||||
x Seq.:< xs -> tsort r' (xs Seq.>< Seq.fromList new) (l Seq.|> x)
|
||||
where (r',_,os) = remove x r
|
||||
new = [o | o <- Set.toList os, Set.null (incoming o r')]
|
||||
|
||||
findCycles :: Ord a => Rel a -> [[a]]
|
||||
findCycles = map Set.toList . equivalenceClasses . reflexiveSubrelation . symmetricSubrelation . transitiveClosure
|
||||
|
||||
--
|
||||
-- * Alternative representation that keeps both incoming and outgoing edges
|
||||
--
|
||||
|
||||
-- | Keeps both incoming and outgoing edges.
|
||||
type Rel' a = Map a (Set a, Set a)
|
||||
|
||||
isEmpty' :: Ord a => Rel' a -> Bool
|
||||
isEmpty' = Map.null
|
||||
|
||||
relToRel' :: Ord a => Rel a -> Rel' a
|
||||
relToRel' r = Map.unionWith (\ (i,_) (_,o) -> (i,o)) ir or
|
||||
where ir = Map.map (\s -> (s,Set.empty)) $ reverseRel r
|
||||
or = Map.map (\s -> (Set.empty,s)) $ r
|
||||
|
||||
rel'ToRel :: Ord a => Rel' a -> Rel a
|
||||
rel'ToRel = Map.map snd
|
||||
|
||||
-- | Removes an element from a relation.
|
||||
-- Returns the new relation, and the set of incoming and outgoing edges
|
||||
-- of the removed element.
|
||||
remove :: Ord a => a -> Rel' a -> (Rel' a, Set a, Set a)
|
||||
remove x r = let (mss,r') = Map.updateLookupWithKey (\_ _ -> Nothing) x r
|
||||
in case mss of
|
||||
-- element was not in the relation
|
||||
Nothing -> (r', Set.empty, Set.empty)
|
||||
-- remove element from all incoming and outgoing sets
|
||||
-- of other elements
|
||||
Just (is,os) ->
|
||||
let r'' = foldr (\i -> Map.adjust (\ (is',os') -> (is', Set.delete x os')) i) r' $ Set.toList is
|
||||
r''' = foldr (\o -> Map.adjust (\ (is',os') -> (Set.delete x is', os')) o) r'' $ Set.toList os
|
||||
in (r''', is, os)
|
||||
|
||||
incoming :: Ord a => a -> Rel' a -> Set a
|
||||
incoming x r = maybe Set.empty fst $ Map.lookup x r
|
||||
|
||||
outgoing :: Ord a => a -> Rel' a -> Set a
|
||||
outgoing x r = maybe Set.empty snd $ Map.lookup x r
|
||||
Reference in New Issue
Block a user