-- | -- Module : Algebra.Closure.Set.BreadthFirst -- Copyright : (c) Joseph Abrahamson 2013 -- License : MIT -- -- Maintainer : me@jspha.com -- Stability : experimental -- Portability : non-portable -- -- Depth-first closed sets. For a particular endomorphism @(p :: a -> -- a)@ a 'Closed' set is a set where if some element @x@ is in the set -- then so is @p x@. Unlike "Algebra.Closure.Set.DepthFirst", this -- algorithm computes the closure in a depth-first manner and thus can -- be useful for computing infinite closures. -- -- It's reasonable to think of a breadth-first 'Closed' set as the -- process of generating a depth-first -- 'Algebra.Closure.Set.DepthFirst.Closed' set frozen in time. This -- retains information about the number of iterations required for -- stability and allows us to return answers that depend only upon -- partial information even if the closure itself is unbounded. module Algebra.Closure.Set.BreadthFirst ( -- * Closed sets Closed, seenBy, seen, -- ** Operations memberWithin', memberWithin, member', member, -- ** Creation close, ) where import Prelude hiding (foldr) import Data.HashSet (HashSet) import Data.Hashable import Data.Foldable (Foldable, foldr, toList) import qualified Data.HashSet as Set -- | A closed set @Closed a@, given an endomorphism @(p :: a -> a)@, -- is a set where if some element @x@ is in the set then so is @p x@. data Closed a = Unchanging | Closed Int (a -> a) (HashSet a) (Closed a) -- | @seenBy n@ converts a 'Closed' set into its underlying set, -- approximated by @n@ iterations. seenBy :: Int -> Closed a -> HashSet a seenBy _ Unchanging = Set.empty seenBy 0 (Closed _ _ set _) = set seenBy n (Closed _ _ set Unchanging) = set seenBy n (Closed _ _ set next) = seenBy (pred n) next -- | Converts a 'Closed' set into its underlying set. If the 'Closed' -- set is unbounded then this operation is undefined (see -- 'seenBy'). It's reasonable to think of this operation as -- -- @ -- let omega = succ omega in seenBy omega -- @ seen :: Closed a -> HashSet a seen Unchanging = Set.empty seen (Closed _ _ set Unchanging) = set seen (Closed _ _ set next) = seen next -- | @memberWithin' n a@ checks to see whether an element is within a -- 'Closed' set after @n@ improvements. The 'Closed' set returned is a -- compressed, memoized 'Closed' set which may be faster to query. memberWithin' :: (Hashable a, Eq a) => Int -> a -> Closed a -> (Bool, Closed a) memberWithin' n _ Unchanging = (False, Unchanging) memberWithin' 0 _ set = (False, set) memberWithin' n a c@(Closed _ _ set next) | Set.member a set = (True, c) | otherwise = memberWithin' (pred n) a next -- | @memberWithin' n a@ checks to see whether an element is within a -- 'Closed' set after @n@ improvements. memberWithin :: (Hashable a, Eq a) => Int -> a -> Closed a -> Bool memberWithin n a = fst . memberWithin' n a -- | Determines whether a particular element is in the 'Closed' -- set. If the element is in the set, this operation is always -- defined. If it is not and the set is unbounded, this operation is -- undefined (see 'memberWithin'). It's reasonable to think of this -- operation as -- -- @ -- let omega = succ omega in memberWithin omega -- @ -- The 'Closed' set returned is a compressed, memoized 'Closed' set -- which may be faster to query. member' :: (Hashable a, Eq a) => a -> Closed a -> (Bool, Closed a) member' _ Unchanging = (False, Unchanging) member' a c@(Closed _ _ set next) | Set.member a set = (True, c) | otherwise = member' a next -- | Determines whether a particular element is in the 'Closed' -- set. If the element is in the set, this operation is always -- defined. If it is not and the set is unbounded, this operation is -- undefined (see 'memberWithin'). It's reasonable to think of this -- operation as -- -- @ -- let omega = succ omega in memberWithin omega -- @ member :: (Hashable a, Eq a) => a -> Closed a -> Bool member a = fst . member' a -- | Converts any 'Foldable' container into the 'Closed' set of its -- contents. close :: (Hashable a, Eq a, Foldable t) => (a -> a) -> t a -> Closed a close iter = build 0 Set.empty . toList where inserter :: (Hashable a, Eq a) => a -> (HashSet a, [a]) -> (HashSet a, [a]) inserter a (set, fresh) | Set.member a set = (set, fresh) | otherwise = (Set.insert a set, a:fresh) build n curr [] = Unchanging build n curr as = Closed n iter curr $ step n (foldr inserter (curr, []) as) step n (set, added) = build (succ n) set (map iter added)