level-monad-0.4.1: Control/Monad/Levels.hs
-- |
-- Module : Control.Monad.Levels
-- Copyright : Sebastian Fischer
-- License : PublicDomain
--
-- Maintainer : Sebastian Fischer (sebf@informatik.uni-kiel.de)
-- Stability : experimental
-- Portability : portable
--
-- This library provides an implementation of the MonadPlus type class
-- that enumerates the levels of the search space and allows to
-- implement breadth-first search and iterative deepening depth-first
-- search.
--
-- The implementation is inspired by Mike Spivey and Silvija Seres:
-- see Chapter 9 of the book 'The Fun of Programming' and the paper
-- 'Algebras for Combinatorial Search'.
--
-- The implementation of breadth-first search is similar to the
-- inspiring implementation but uses lists with constant-time
-- concatenation to represent levels. The implementation of iterative
-- deepening depth-first is simpler than the inspiring implementation
-- thanks to the use of a continuation monad.
module Control.Monad.Levels ( bfs, idfs, idfsBy ) where
import Data.Monoid
import Data.FMList
-- | The function @bfs@ enumerates the results of a non-deterministic
-- computation using breadth-first search. The implementation does not
-- guarantee that results are returned in any specific order but it
-- does guarantee that every result is eventually enumerated. Due to
-- the large memory requirements of breadth-first search you should
-- use @idfs@ for expensive search.
bfs :: FMList a -> [a]
bfs a = runLevels (unFM a yield)
where yield x = Levels [singleton x]
-- Non-Deterministic computations of type @Levels a@ can be searched
-- level-wise.
newtype Levels a = Levels { levels :: [FMList a] }
-- Concatenates levels amd yields result as list.
runLevels :: Levels a -> [a]
runLevels = toList . foldr append empty . levels
instance Monoid (Levels a) where
mempty = Levels []
a `mappend` b = Levels (empty : merge (levels a) (levels b))
-- like 'zipWith append' without cutting the longer list
merge :: [FMList a] -> [FMList a] -> [FMList a]
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys) = append x y : merge xs ys
-- | The function @idfs@ computes the levels of a depth bound
-- computation using iterative deepening depth-first search. Unlike
-- breadth-first search it runs in constant space but usually takes a
-- bit longer, depending on how the depth limit is increased. Use
-- @idfsBy@ to control this. Don't use this algorithm if you know that
-- there is only a finite number of results because it will continue
-- trying larger depth limits without recognizing that there are no
-- more solutions. It can, however, produce results lazily: calling
-- @take n . idfs@ terminates if the number of results is larger than
-- @n@.
idfs :: FMList a -> [a]
idfs = idfsBy 100
-- | The function @idfsBy@ computes the levels of a depth bound
-- computation using iterative deepening depth-first search
-- incrementing the depth limit between searches using the given
-- number of steps.
idfsBy :: Int -> FMList a -> [a]
idfsBy n a = toList $ foldr append empty [ unFM a yield ! d | d <- [0,n..] ]
where yield x = DepthBound (\d -> if d<n then singleton x else empty)
-- The type @DepthBound@ represents computations with a bounded depth
-- to iterative deepening search.
newtype DepthBound a = DepthBound { (!) :: Int -> FMList a }
instance Monoid (DepthBound a) where
mempty = DepthBound (const empty)
a `mappend` b = DepthBound (\d -> if d==0 then empty
else append (a!(d-1)) (b!(d-1)))