这是带有标记节点和边的循环有向图的类型。
import qualified Data.Map as M
import Data.Foldable
import Data.Monoid
data Node n e = N n [(e, Node n e)] -- the node's label and its list of neighbors
newtype Graph n e = G (M.Map n (Node n e))
为了处理图有循环的情况,可以“打结”并在有限空间中创建无限递归图。
type GraphInput n e = M.Map n [(e, n)]
mkGraph :: Ord n => GraphInput n e -> Graph n e
mkGraph spec = G $ nodeMap
where nodeMap = M.mapWithKey mkNode (makeConsistent spec)
-- mkNode :: n -> [(e, n)] -> Node n e
mkNode lbl edges = N lbl $ map getEdge edges
-- We know that (!) can't fail because we ensured that
-- all edges have a key in the map (see makeConsistent)
getEdge (e, lbl) = (e, nodeMap ! lbl)
makeConsistent :: Ord n => GraphInput n e -> GraphInput n e
makeConsistent m = foldr addMissing m nodesLinkedTo
where addMissing el m = M.insertWith (\_ old -> old) el [] m
nodesLinkedTo = map snd $ join $ M.elems m
通过将图形视为节点的集合,我们可以编写一个Foldable
执行深度优先遍历的实例。*
newtype NodeGraph e n = NG {getNodeGraph :: Graph n e}
instance Foldable (NodeGraph e) where
foldMap f (NG (G m)) = foldMap mapNode (M.elems m)
where mapNode (N n es) = f n `mappend` foldMap mapEdge es
mapEdge (e, n) = mapNode n
然而,即使对于简单的树形图,这也会产生重复的元素:
-- A
-- / \ X
-- B C
-- |
-- D
ghci> let ng = NG $ mkGraph [('A', [(1, 'B'), (1, 'C')]), ('C', [(1, 'D')]), ('X', [])]
ghci> let toList = Data.Foldable.foldr (:) []
ghci> toList ng
"ABCDBCDDX"
当图形有循环时,效果更加显着——foldMap
永远递归!循环中的项目是重复的,有些元素永远不会返回!
这个可以吗?一个实例可以Foldable
多次返回它的一些元素,还是我违反了类的合同?实例可以在结构的一部分上无限循环吗?我一直在寻找关于这个问题的指导——我希望有一套“可折叠的法律”来解决这个问题——但我无法在网上找到任何关于这个问题的讨论。
摆脱这种情况的一种方法是“记住”在我遍历图表时已经访问过的元素。但是,这会给 的签名添加一个Eq
orOrd
约束foldMap
,这会阻止我的类型成为 的成员Foldable
。
* 顺便说一下,我们不能为 编写一个Functor
实例NodeGraph
,因为它会破坏图中节点被唯一标记的不变性。(fmap (const "foo")
例如,将每个节点重新标记为“foo”,尽管它们都有不同的边集!)我们可以(使用适当的newtype
)编写一个Functor
映射所有边标签的映射。