{-# LANGUAGE FlexibleContexts, FlexibleInstances, ScopedTypeVariables, UndecidableInstances #-} -- | Functions for iterating trees. -- A 'List' whose underlying monad is also a 'List' is a tree. -- -- It's nodes are accessible, in contrast to the list monad, -- which can also be seen as a tree, except only its leafs -- are accessible and only in "dfs order". -- -- > import Control.Monad.DList (toListT) -- > import Control.Monad.Generator -- > import Control.Monad.Trans -- > import Data.List.Class (genericTake, takeWhile, toList, lastL) -- > -- > bits = toListT (t "") -- > t prev = -- > generate $ do -- > yield prev -- > x <- lift "01" -- > yields $ t (prev ++ [x]) -- > -- > > take 3 (bfsLayers bits) -- > [[""],["0","1"],["00","01","10","11"]] -- > -- > > take 10 (bfs bits) -- > ["","0","1","00","01","10","11","000","001","010"] -- > -- > > dfs (genericTake 4 bits) -- > ["","0","00","000","001","01","010","011","1","10","100","101","11","110","111"] -- > -- > > toList $ genericTake 3 bits -- > [["","0","00"],["","0","01"],["","1","10"],["","1","11"]] -- -- Examples of pruning with 'prune' and 'takeWhile': -- -- > > dfs . takeWhile (not . isSuffixOf "11") $ genericTake 4 bits -- > ["","0","00","000","001","01","010","1","10","100","101"] -- > -- > > lastL . takeWhile (not . isSuffixOf "11") $ genericTake 4 bits -- > ["000","001","010","01","100","101","1"] -- > -- > > lastL . prune (not . isSuffixOf "11") $ genericTake 4 bits -- > ["000","001","010","100","101"] -- module Data.List.Tree ( Tree, -- | Search algorithms dfs, bfs, bfsLayers, bestFirstSearchOn, bestFirstSearchSortedChildrenOn, -- | Pruning methods prune, pruneM, branchAndBound ) where import Control.Monad ( MonadPlus(..), guard, join, liftM, liftM2, when) import Control.Monad.ListT (ListT) import Control.Monad.State (StateT, MonadState(..)) import Control.Monad.Trans (lift) import Data.List.Class ( List(..), ListItem(..), cons, foldrL, joinM, mergeOn, transpose, transformListMonad) -- | A 'type-class synonym' for Trees. class (List t, List (ItemM t)) => Tree t instance (List t, List (ItemM t)) => Tree t search :: (List l, MonadPlus (ItemM l)) => (ItemM l (ItemM l a) -> ItemM l a) -> l a -> ItemM l a search merge = merge . foldrL step mzero where step a = return . cons a . merge -- | Iterate a tree in DFS pre-order. (Depth First Search) dfs :: (List l, MonadPlus (ItemM l)) => l a -> ItemM l a dfs = search join -- | Transform a tree into lists of the items in its different layers bfsLayers :: Tree t => t a -> ItemM t (ItemM t a) bfsLayers = search (liftM join . transpose) . liftM return -- | Iterate a tree in BFS order. (Breadth First Search) bfs :: Tree t => t a -> ItemM t a bfs = join . bfsLayers -- | Best First Search given a scoring function. bestFirstSearchOn :: (Ord b, Tree t) => (a -> b) -> t a -> ItemM t a bestFirstSearchOn = search . mergeOn mergeOnSortedHeads :: (Ord b, List l) => (a -> b) -> l (l a) -> l a mergeOnSortedHeads f ll = joinL $ do lli <- runList ll case lli of Nil -> return mzero Cons l ls -> do li <- runList l return $ case li of Nil -> mergeOnSortedHeads f ls Cons x xs -> cons x . mergeOnSortedHeads f $ bury xs ls where bury xx yyy = joinL $ do xi <- runList xx case xi of Nil -> return yyy Cons x xs -> do let rxx = cons x xs yyi <- runList yyy case yyi of Nil -> return (cons rxx mzero) Cons yy yys -> do yi <- runList yy return $ case yi of Nil -> bury xx yys Cons y ys -> let ryy = cons y ys in if f x <= f y then cons rxx . cons ryy $ yys else cons ryy . bury rxx $ yys -- | Prune a tree or list given a predicate. -- Unlike 'takeWhile' which stops a branch where the condition doesn't hold, -- prune "cuts" the whole branch (the underlying MonadPlus's mzero). prune :: (List l, MonadPlus (ItemM l)) => (a -> Bool) -> l a -> l a prune = pruneM . fmap return pruneM :: (List l, MonadPlus (ItemM l)) => (a -> ItemM l Bool) -> l a -> l a pruneM cond = joinM . liftM r where r x = do cond x >>= guard return x -- | Best-First-Search given that a node's children are in sorted order (best first) and given a scoring function. -- Especially useful for trees where nodes have an infinite amount of children, where 'bestFirstSearchOn' will get stuck. -- -- Example: Find smallest Pythagorian Triplets -- -- > import Control.Monad -- > import Control.Monad.DList (toListT) -- > import Control.Monad.Generator -- > import Control.Monad.Trans -- > import Data.List.Tree -- > import Data.Maybe -- > -- > pythagorianTriplets = -- > catMaybes . -- > fmap fst . -- > bestFirstSearchSortedChildrenOn snd . -- > toListT . generate $ do -- > x <- lift [1..] -- > yield (Nothing, x) -- > y <- lift [1..] -- > yield (Nothing, x + y) -- > z <- lift [1..] -- > yield (Nothing, x + y + z) -- > lift . guard $ x^2 + y^2 == z^2 -- > yield (Just (x, y, z), 0) -- > -- > > print $ take 10 pythagorianTriplets -- > [(3,4,5),(4,3,5),(6,8,10),(8,6,10),(5,12,13),(12,5,13),(9,12,15),(12,9,15),(15,8,17),(8,15,17)] bestFirstSearchSortedChildrenOn :: (Ord b, Tree t) => (a -> b) -> t a -> ItemM t a bestFirstSearchSortedChildrenOn = search . mergeOnSortedHeads -- | Generalized "Branch and Bound". A method for pruning. -- -- The result of this function -- would usually be given to another search algorithm, -- such as `dfs`, in order to find the node with lowest value. -- -- This augments the regular search by pruning the tree. -- Given a function to calculate a lower and upper bound for a subtree, -- we keep the lowest upper bound (hence the State monad) encountered so far, -- and we prune any subtree whose lower bound is over the known upper bound. branchAndBound :: (Ord b, Tree t) => (a -> (Maybe b, Maybe b)) -> t a -> ListT (ListT (StateT (Maybe b) (ItemM (ItemM t)))) a branchAndBound boundFunc = pruneM cond . transformListMonad (transformListMonad lift) where cond x = do upperSoFar <- get if Just True == liftM2 (>=) lower upperSoFar then return False else do -- this "when" clause isn't before the if, -- so upper bound won't be calculated if not needed. -- this optiminzation is based on (upper >= lower) when ( Nothing == upperSoFar || Just True == liftM2 (<) upper upperSoFar ) (put upper) return True where (lower, upper) = boundFunc x