我正在尝试在Scala中实现功能广度优先搜索,以计算给定节点与未加权图中所有其他节点之间的距离。我使用了State Monad,签名为: -
case class State[S,A](run:S => (A,S))
其他功能,如map,flatMap,sequence,modify等,与你在标准State Monad中找到的相似。
这是代码: -
case class Node(label: Int)
case class BfsState(q: Queue[Node], nodesList: List[Node], discovered: Set[Node], distanceFromSrc: Map[Node, Int]) {
val isTerminated = q.isEmpty
}
case class Graph(adjList: Map[Node, List[Node]]) {
def bfs(src: Node): (List[Node], Map[Node, Int]) = {
val initialBfsState = BfsState(Queue(src), List(src), Set(src), Map(src -> 0))
val output = bfsComp(initialBfsState)
(output.nodesList,output.distanceFromSrc)
}
@tailrec
private def bfsComp(currState:BfsState): BfsState = {
if (currState.isTerminated) currState
else bfsComp(searchNode.run(currState)._2)
}
private def searchNode: State[BfsState, Unit] = for {
node <- State[BfsState, Node](s => {
val (n, newQ) = s.q.dequeue
(n, s.copy(q = newQ))
})
s <- get
_ <- sequence(adjList(node).filter(!s.discovered(_)).map(n => {
modify[BfsState](s => {
s.copy(s.q.enqueue(n), n :: s.nodesList, s.discovered + n, s.distanceFromSrc + (n -> (s.distanceFromSrc(node) + 1)))
})
}))
} yield ()
}
请告诉我们: -
searchNode
函数中出现的状态转换是否应该是BfsState
本身的成员?答案 0 :(得分:1)
首先,我建议将与private def
相关的所有bfs
移动到bfs
本身。这是仅用于实现另一个方法的方法的惯例。
其次,我建议不要在此问题上使用State
。 State
(和大多数monad一样)是关于构图的。当你有很多东西需要访问同一个全局状态时,它很有用。在这种情况下,BfsState
专门用于bfs
,可能永远不会在其他地方使用(将类移到bfs
也可能是个好主意),{{1 }}本身总是State
,所以外部世界永远不会看到它。 (在很多情况下,这很好,但是这里的范围太小,run
无用。)将State
的逻辑拉入searchNode
会更清晰本身。
第三,我不明白为什么你需要bfsComp
和nodesList
,当你可以在discovered
上致电_.toList
完成你的计算。我已经在重新实现中留下了它,但是,如果此代码中还有更多内容尚未显示。
discovered
如果你认为你做有充分的理由在这里使用def bfsComp(old: BfsState): BfsState = {
if(old.q.isEmpty) old // You don't need isTerminated, I think
else {
val (currNode, newQ) = old.q.dequeue
val newState = old.copy(q = newQ)
adjList(curNode)
.filterNot(s.discovered) // Set[T] <: T => Boolean and filterNot means you don't need to write !s.discovered(_)
.foldLeft(newState) { case (BfsState(q, nodes, discovered, distance), adjNode) =>
BfsState(
q.enqueue(adjNode),
adjNode :: nodes,
discovered + adjNode,
distance + (adjNode -> (distance(currNode) + 1)
)
}
}
}
def bfs(src: Node): (List[Node], Map[Node, Int]) = {
// I suggest moving BfsState and bfsComp into this method
val output = bfsComp(BfsState(Queue(src), List(src), Set(src), Map(src -> 0)))
(output.nodesList, output.distanceFromSrc)
// Could get rid of nodesList and say output.discovered.toList
}
,这是我的想法。
您使用State
。 def searchNode
的要点是它是纯粹且不可变的,因此它应该是State
,否则每次使用时都会重建相同的val
。
你写道:
State
首先,Scala的语法是为了让您不需要围绕匿名函数node <- State[BfsState, Node](s => {
val (n, newQ) = s.q.dequeue
(n, s.copy(q = newQ))
})
和()
而设计的:
{}
其次,这对我来说并非相当。使用for-syntax的一个好处是匿名函数对你来说是隐藏的,并且有最小的缩进。我只是写出来
node <- State[BfsState, Node] { s =>
// ...
}
脚注:让oldState <- get
(node, newQ) = oldState.q.dequeue
newState = oldState.copy(q = newQ)
成为Node
的内部类是否有意义?只是一个建议。