在Haskell中使用State monad的广度优先搜索 [英] Breadth-First Search using State monad in Haskell

查看:242
本文介绍了在Haskell中使用State monad的广度优先搜索的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近,我问了一个关于从Stackoverflow中的Graph构建DFS树的问题,并且已经知道可以通过使用State Monad来简单地实现。



DFS in haskell

尽管DFS仅需要跟踪访问节点,因此我们可以使用'Set'或'List'或某种线性数据结构来跟踪访问节点,BFS需要'访问节点'和'队列'数据结构。



我的BFS伪码是

  Q =空队列
T =空树
将除了u之外的所有节点标记为未访问
而Q不为空do
u = deq( Q)
为每个顶点v∈Ad(u)
如果v没有被访问
然后将边(u,v)添加到T
将v标记为visited并且enq(v )

从伪科学可以推断出来e,我们只需要每次迭代3次。



  1. 从队列中取出队列

  2. 将该点的所有未访问邻居添加到当前树的子级,队列和已访问列表中
  3. 为队列中的下一个重复此操作


因为我们不使用递归遍历来进行BFS搜索,所以我们需要一些其他的遍历方法,比如while循环。我已经在hackage中查找了loop-while包,但似乎有些不赞同。



我假设我需要某种类似的代码:

  {-...-} 
... = evalState(bfs)((SetSingleton start),[start ])
where
neighbors x = Map.findWithDefault [] x adj
bfs = do(vis,x:queue)< -get
map(\\\
eighbor - > ;
if(Set.member neighbor vis)
then put(vis,queue)
else put((Set.insert neighbor vis),queue ++ [neighbor])>>(addToTree邻居)
)邻居x
(vis,队列)< -get
while(length queue> 0)

我明白这个实现是非常错误的,但是这应该给我如何实现BFS的最简单的看法。另外,我真的不知道如何避免使用while循环来实现块(即,我应该使用递归算法来克服它,还是应该考虑完全不同的策略)

<考虑到我在前面的问题中找到的答案之一,看起来像这样的答案:

  newtype图a =图(Map.Map a [a])派生(Ord,Eq,Show)
数据树a =树a [Tree a]派生(Ord,Eq,Show)

bfs ::(Ord a)=>图表a - > a - >树a
bfs(图形形式)start = evalState(bfs')((SetSingleton start),[start])
其中
bfs'= { - 其中我没有知道 -

最后,如果由于某种原因使用状态monad的BFS的这种实现是不可能的我认为这不是),请纠正我的错误假设。



我已经在Haskell中看到了一些BFS示例,但没有使用状态monad,但我想了解更多信息关于如何处理state monad,并且找不到使用state monad实现的任何BFS示例。



预先感谢。






编辑:
我想出了一些使用状态monad的算法,但我陷入了无限循环。

  bfs ::(Ord a)=>图表a  - > a  - >树a 
bfs(Graph adj)start = evalState(bfs'(Graph adj)start)(Set.singleton start)

bfs'::(Ord a)=>图表a - > a - > State(Set.Set a)(Tree a)
bfs'(Graph adj)point = do
vis< - get
let neighbors x = Map.findWithDefault [] x adj
let addableNeighbors(x:xs)= if set.member x vis
then addableNeighbors(xs)
else x:addableNeighbors(xs)
let addVisited(vis)(ns)= Set .union(vis)$ Set.fromList ns
let newVisited = addVisited vis $ addableNeighbors $ neighbors point
put newVisited
return(Tree point $ map(flip evalState newVisited)(map(bfs' (Graph adj))$ addableNeighbors $ neighbors point))

编辑2:我已经提出了一个解决方案,使用图返回并排队处理BFS图。尽管它不是生成BFS树/图的最佳解决方案,但它仍然可行。

  bfs ::(Ord a)= >图表a  - > a  - >图a 
bfs(图形形式)start = evalState(bfs'(Graph adj)(Graph(Map.empty))[start])(Set.singleton start)


bfs'::(Ord a)=>图表a - >图表a - > [a] - > State(Set.Set a)(Graph a)
bfs'_(Graph ret)[] = return(Graph ret)
bfs'(Graph adj)(Graph ret)(p:points)= do
vis< - get
let neighbors x = Map.findWithDefault [] x adj
let addableNeighbors ns
| null ns = []
|否则=如果Set.member(head ns)vis
then addableNeighbors(tail ns)
else(head ns):addableNeighbors(tail ns)
let addVisited(v)(ns)= Set .union(v)$ Set.fromList ns
let unVisited = addableNeighbors $ neighbors p
let newVisited = addVisited vis unVisited
let unionGraph(Graph g1)(Graph g2)= Graph(Map。 (图形调整)(图形(Map.singleton p unVisited)))(points ++ unVisited)
$' c $ c>

编辑3:我已经将图形的转换函数添加到树中。 EDIT2和EDIT3中的运行功能将产生BFS树。它不是计算时间最好的算法,但我相信它对于像我这样的新手来说是直观和容易理解的:)

pre $ graphToTree ::(Ord a)=>图表a - > a - > Tree a
graphToTree(Graph adj)point =树点$ map(graphToTree(Graph adj))$ neighbors point
where neighbors x = Map.findWithDefault [] x adj


解决方案

将图转换为宽度优先比简单地搜索图形宽度要困难得多 - 第一。如果你正在搜索图表,你只需要从一个分支返回。将图转换为树时,结果需要包含来自多个分支的结果。



我们可以使用比更通用的类型a图a 为我们可以搜索或转换为树木。我们可以使用函数 a - >搜索或转换树。 [α] 。对于 Graph ,我们使用函数(Map。!)m ,其中 m Map 。使用换位表进行搜索时会有一个签名,如

  breadthFirstSearchUnseen :: Ord r => (a→r)→> - 如何比较`a`s 
(a - > Bool) - > - 在哪里停止
(a - > [a]) - > - 你可以从`a`
[a] - > - 从哪里开始
可能[a]

将函数转换为包含每个在最早深度的可到达节点都有一个签名,如

  shortestPathTree :: Ord r => (a→r)→> - 如何比较`a`s 
(a - > l) - 在树中放置什么标签
(a - > [a]) - > - 你可以从`a`
a - > - 从哪里开始
树l

我们可以稍微更一般地从任何数量的节点并建立一个 Forest ,它包含每个可到达节点的最早深度。

  shortestPathTrees :: Ord r => (a→r)→> - 如何比较`a`s 
(a - > l) - 在树中放置什么标签
(a - > [a]) - > - 你可以从`a`
[a] - > - 从哪里开始
[Tree l]



搜索



执行转换为树并不能真正帮助我们搜索,我们可以在原始图上执行广度优先搜索。

  import Data.Sequence(viewl,ViewL(..),(><))
将限定的Data.Sequence导入为Seq
将限定的Data.Set导入为Set

breadthFirstSearchUnseen :: Ord r => (a→r)→> (a - > Bool) - > (a - > [a]) - > [a] - >可能[a]
breadthFirstSearchUnseen repr p expand = combine Set.empty Seq.empty []
其中
合并看到排队的祖先unseen =
go
(看到`Set ((,)祖先)$ unseen))
去看队列= $ b(Set.fromList。map repr $ unseen))
(queued>< $ b案例
的viewl队列EmptyL - >没有
(祖先,a):<排队 - >
如果p a
则Just。反向$ ancestors'
else结合看到排队的祖先'看不见的
其中
ancestors'= a:ancestors
unseen =过滤器(翻转Set.notMember看到。repr)。展开$ a

在上述搜索算法中维护的状态是 Seq 接下来要访问的节点的队列和 设置 节点已被 。如果我们继续跟踪已经访问过的节点,那么如果我们在相同深度找到到节点的多条路径,则可以多次访问同一个节点。在我写这个广度优先搜索的答案中有一个更完整的解释



我们可以轻松编写搜索 Graph s用于我们的常规搜索。

 导入限定的Data.Map为Map 
$ b newtype图a =图(Map.Map a [a])派生(Ord,Eq,Show)

bfsGraph ::(Ord a)=>图表a - > (a - > Bool) - > [a] - >也许[a]
bfsGraph(Graph adj)test = breadthFirstSearchUnseen id test((Map。!)adj)

我们还可以编写如何搜索 Tree s本身。

  import Data.Tree 

bfsTrees ::(Ord a)=> (a - > Bool) - > [树a] - >也许[a]
bfsTrees test = fmap(map rootLabel)。 breadthFirstSearchUnseen rootLabel(test.urlLabel)subForest



构建树



构建宽度优先的树是很多的很多更难。幸运的是 Data.Tree 已经提供了从一元展开的宽度第一次构建 Tree s的方法。广度优先顺序将负责排队,我们只需要跟踪我们已经看到的节点的状态。



unfoldTreeM_BF 的类型为 Monad m => (b→m(a,[b]))→> b - > m(树a) m Monad 我们的计算将会在 b 是我们将基于的构建树的数据类型, a 是树标签的类型。为了使用它来构建树,我们需要创建一个函数 b - > m(a,[b])。我们将标签重命名为 a l b a ,这就是我们一直在使用的节点。我们需要制作一个 a - > m(l,[a])。对于 m ,我们将使用 State monad from 变压器来跟踪某些状态;该状态将是> Set 节点,其表示 r 我们已经见过;我们将使用 State(Set.Set r) monad。总的来说,我们需要提供一个函数 a - > State(Set.Set r)(l,[a])

  expandUnseen :: Ord r => (a→r)→> (a→1)→> (a  - > [a]) - > a  - > State(Set.Set r)(l,[a])
expandUnseen repr label expand a = do
seen< - get
let unseen = filter(翻转Set.notMember看过。 )。 uniqueBy repr。展开$ a
放。 Set.union见过。 Set.fromList。 map repr $ unseen
return(label a,unseen)

为了构建树,我们运行由 unfoldForestM_BF

  shortestPathTrees :: Ord r => (a→r)→> (a→1)→> (a  - > [a]) - > [a]  - > [Tree l] 
shortestPathTrees repr label expand = run。 unfoldForestM_BF k。 uniqueBy repr
其中
run = flip evalState Set.empty
k = expandUnseen repr label expand

uniqueBy 是一个利用 Ord 的 nubBy code>实例而不是 Eq

  uniqueBy :: Ord r => (a→r)→> [a]  - > [a] 
uniqueBy repr = go Set.empty
其中
可见[] = []
如果Set.member出现(x:xs)=
(repr x)看到
然后看到xs
else x:go(看到Set.insert(repr x))xs

我们可以从 Graph s编写构建最短路径树,就我们的一般最短路径树构建而言, b

  shortestPathsGraph :: Ord a =>图表a  - > [a]  - > [Tree a] 
shortestPathsGraph(Graph adj)= shortestPathTrees id id((Map。!)adj)

我们可以通过将 Forest 过滤为仅通过 Forest 的最短路径来执行相同操作。

  shortestPathsTree :: Ord a => [树a]  - > [Tree a] 
shortestPathsTree = shortestPathTrees rootLabel rootLabel subForest


Recently, I've asked a question for building DFS tree from Graph in Stackoverflow and had learned that it can be simply implemented by using State Monad.

DFS in haskell

While DFS requires to track only visited nodes, so that we can use 'Set' or 'List' or some sort of linear data structure to track visited nodes, BFS requires 'visited node' and 'queue' data structure to be accomplished.

My pseudocode for BFS is

Q = empty queue
T = empty Tree
mark all nodes except u as unvisited
while Q is nonempty do
u = deq(Q)
    for each vertex v ∈ Adj(u)
        if v is not visited 
        then add edge (u,v) to T
             Mark v as visited and enq(v)

As can be inferred from pseudocode, we only have to do 3 processes per iteration.

  1. dequeue point from queue
  2. add all unvisited neighbors of the point to current tree's child, queue and 'visited' list
  3. repeat this for next in queue

Since we are not using recursive traversal for BFS search, we need some other traversal method such as while loop. I've looked up loop-while package in hackage, but it seems somewhat deprecated.

What I assume is that I require some sort of code like this :

{-...-}
... =   evalState (bfs) ((Set.singleton start),[start])
where 
    neighbors x = Map.findWithDefault [] x adj 
    bfs =do (vis,x:queue)<-get
             map (\neighbor ->
                  if (Set.member neighbor vis)
                  then put(vis,queue) 
                  else put ((Set.insert neighbor vis), queue++[neighbor]) >> (addToTree neighbor)
                 )  neighbors x
            (vis,queue)<-get
         while (length queue > 0)

I understand that this implementation is very erroneous but this should give minimalistic view for how I think BFS should be implemented. Also, I really don't know how to circumvent using while loop for do blocks.(i.e should I use recursive algorithm to overcome it or should I think of completely different strategy)

Considering one of the answer I've found in previous question linked above, it seems like the answer should look like this :

newtype Graph a = Graph (Map.Map a [a]) deriving (Ord, Eq, Show)
data Tree a = Tree a [Tree a] deriving (Ord, Eq, Show)

bfs :: (Ord a) => Graph a -> a -> Tree a
bfs (Graph adj) start = evalState (bfs') ((Set.singleton start),[start])
    where
        bfs' = {-part where I don't know-}

Finally, if such implementation for BFS using state monad is impossible due to some reason, (which I believe not to be) please correct my false assumption.

I've seen some of the examples for BFS in Haskell without using state monad but I want to learn more about how state monad can be processed and couldn't have found any of examples of BFS implemented using state monad.

Thanks in advance.


EDIT: I came up with some sort of algorithm using state monad but I fall in infinite loop.

bfs :: (Ord a) => Graph a -> a -> Tree a
bfs (Graph adj) start = evalState (bfs' (Graph adj) start) (Set.singleton start)

bfs' :: (Ord a) => Graph a -> a -> State (Set.Set a) (Tree a)
bfs' (Graph adj) point= do
                        vis <- get
                        let neighbors x = Map.findWithDefault [] x adj
                        let addableNeighbors (x:xs) =   if Set.member x vis
                                                        then addableNeighbors(xs)
                                                        else x:addableNeighbors(xs)
                        let addVisited (vis) (ns) = Set.union (vis) $ Set.fromList ns
                        let newVisited = addVisited vis $ addableNeighbors $ neighbors point
                        put newVisited
                        return (Tree point $ map (flip evalState newVisited) (map (bfs' (Graph adj)) $ addableNeighbors $ neighbors point))

EDIT2: With some expense of space complexity, I've came out with a solution to get BFS graph using graph to return and queue to process. Despite it is not the optimal solution for generating BFS tree/graph, it will work.

bfs :: (Ord a) => Graph a -> a -> Graph a
bfs (Graph adj) start = evalState (bfs' (Graph adj) (Graph(Map.empty))  [start]) (Set.singleton start)


bfs':: (Ord a) => Graph a -> Graph a -> [a] -> State (Set.Set a) (Graph a)
bfs' _ (Graph ret) [] = return (Graph ret)
bfs' (Graph adj) (Graph ret) (p:points)= do
                                        vis <- get
                                        let neighbors x = Map.findWithDefault [] x adj
                                        let addableNeighbors ns
                                                | null ns = []
                                                | otherwise =   if Set.member (head ns) vis
                                                                then addableNeighbors(tail ns)
                                                                else (head ns):addableNeighbors(tail ns)
                                        let addVisited (v) (ns) = Set.union (v) $ Set.fromList ns
                                        let unVisited = addableNeighbors $ neighbors p
                                        let newVisited = addVisited vis unVisited
                                        let unionGraph (Graph g1) (Graph g2) = Graph (Map.union g1 g2)
                                        put newVisited
                                        bfs' (Graph adj) (unionGraph (Graph ret) (Graph (Map.singleton p unVisited))) (points ++ unVisited)

EDIT3: I've added convert function for graph to tree. Running function in EDIT2, and EDIT3 will yield BFS Tree. It is not the best algorithm for computation time wise, but I believe it is intuitive and easy to understand for newbies like me :)

graphToTree :: (Ord a) => Graph a -> a -> Tree a
graphToTree (Graph adj) point  = Tree point $ map (graphToTree (Graph adj)) $ neighbors point
    where neighbors x = Map.findWithDefault [] x adj

解决方案

Converting a graph into a Tree breadth-first is a bit more difficult than simply searching the graph breadth-first. If you are searching the graph, you only ever need to return from a single branch. When converting the graph into a tree, the result needs to include results from multiple branches.

We can use a more general type than Graph a for what we can search or convert to trees. We can search or convert to trees anything with a function a -> [a]. For a Graph we'd use the function (Map.!) m, where m is the Map. Searching with a transposition table has a signature like

breadthFirstSearchUnseen:: Ord r => (a -> r) -> -- how to compare `a`s 
                           (a -> Bool) ->       -- where to stop
                           (a -> [a]) ->        -- where you can go from an `a`
                           [a] ->               -- where to start
                           Maybe [a]

Converting the function to a tree that contains each reachable node at the earliest depth has a signature like

shortestPathTree :: Ord r => (a -> r) -> -- how to compare `a`s
                    (a -> l)             -- what label to put in the tree
                    (a -> [a]) ->        -- where you can go from an `a`
                    a ->                 -- where to start
                    Tree l

We can slightly more generally start at any number of nodes and build a Forest that contains each reachable node at the earliest depth.

shortestPathTrees :: Ord r => (a -> r) -> -- how to compare `a`s
                     (a -> l)             -- what label to put in the tree
                     (a -> [a]) ->        -- where you can go from an `a`
                     [a] ->               -- where to start
                     [Tree l]

Searching

Performing the conversion to a tree doesn't really help us search, we can perform breadth first searches on the original graph.

import Data.Sequence (viewl, ViewL (..), (><))
import qualified Data.Sequence as Seq
import qualified Data.Set as Set

breadthFirstSearchUnseen:: Ord r => (a -> r) -> (a -> Bool) -> (a -> [a]) -> [a] -> Maybe [a]
breadthFirstSearchUnseen repr p expand = combine Set.empty Seq.empty []
    where
        combine seen queued ancestors unseen =
            go
                (seen  `Set.union` (Set.fromList . map repr            $ unseen))
                (queued ><         (Seq.fromList . map ((,) ancestors) $ unseen))
        go seen queue =
            case viewl queue of
                EmptyL -> Nothing
                (ancestors, a) :< queued ->
                    if p a
                    then Just . reverse $ ancestors'
                    else combine seen queued ancestors' unseen
                    where
                        ancestors' = a:ancestors
                        unseen = filter (flip Set.notMember seen . repr) . expand $ a

The state maintained in the above search algorithm is a Seq queue of what nodes to visit next and a Set of nodes that have already been seen. If we instead kept track of nodes that have already been visited, then we could visit the same node multiple times if we find multiple paths to the node at the same depth. There's a more complete explanation in the answer I wrote this breadth first search for.

We can easily write searching Graphs in terms of our general search.

import qualified Data.Map as Map

newtype Graph a = Graph (Map.Map a [a]) deriving (Ord, Eq, Show)

bfsGraph :: (Ord a) => Graph a -> (a -> Bool) -> [a] -> Maybe [a]
bfsGraph (Graph adj) test = breadthFirstSearchUnseen id test ((Map.!) adj)

We can also write how to search Trees themselves.

import Data.Tree

bfsTrees :: (Ord a) => (a -> Bool) -> [Tree a] -> Maybe [a]
bfsTrees test = fmap (map rootLabel) . breadthFirstSearchUnseen rootLabel (test . rootLabel) subForest

Building trees

Building trees breadth-first is a lot more difficult. Fortunately Data.Tree already provides ways to build Trees in breadth first order from a monadic unfold. The breadth first order will take care of the queuing, we will only need to keep track of the state for the nodes we've already seen.

unfoldTreeM_BF has the type Monad m => (b -> m (a, [b])) -> b -> m (Tree a). m is the Monad our computations will be in, b is the type of data we are going to build the tree based on, and a is the type for the labels of the tree. In order to use it to build a tree we need to make a function b -> m (a, [b]). We're going to rename a to l for label, and b to a, which is what we've been using for our nodes. We need to make an a -> m (l, [a]). For m, we'll use the State monad from transformers to keep track of some state; the state will be the Set of nodes whose representation r we've already seen; we'll be using the State (Set.Set r) monad. Overall, we need to provide a function a -> State (Set.Set r) (l, [a]).

expandUnseen :: Ord r => (a -> r) -> (a -> l) -> (a -> [a]) -> a -> State (Set.Set r) (l, [a])
expandUnseen repr label expand a = do
    seen <- get
    let unseen = filter (flip Set.notMember seen . repr) . uniqueBy repr . expand $ a
    put . Set.union seen . Set.fromList . map repr $ unseen
    return (label a, unseen)

To build the trees, we run the state computation built by unfoldForestM_BF

shortestPathTrees :: Ord r => (a -> r) -> (a -> l) -> (a -> [a]) -> [a] -> [Tree l]
shortestPathTrees repr label expand = run . unfoldForestM_BF k . uniqueBy repr
    where
        run = flip evalState Set.empty
        k = expandUnseen repr label expand

uniqueBy is a nubBy that takes advantage of an Ord instance instead of Eq.

uniqueBy :: Ord r => (a -> r) -> [a] -> [a]
uniqueBy repr = go Set.empty
    where
        go seen []     = []
        go seen (x:xs) =
            if Set.member (repr x) seen
            then go seen xs
            else x:go (Set.insert (repr x) seen) xs

We can write building shortest path trees from Graphs in terms of our general shortest path tree building

shortestPathsGraph :: Ord a => Graph a -> [a] -> [Tree a]
shortestPathsGraph (Graph adj) = shortestPathTrees id id ((Map.!) adj)

We can do the same for filtering a Forest to only the shortest paths through the Forest.

shortestPathsTree :: Ord a => [Tree a] -> [Tree a]
shortestPathsTree = shortestPathTrees rootLabel rootLabel subForest

这篇关于在Haskell中使用State monad的广度优先搜索的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆