我想出了一个与 Gabriel 非常相似的解决方案,但我的数据表示使用 aMap
以便我可以将大部分工作加载到Data.Map.unionWith
.
import Data.Map (Map, empty, singleton, unionWith, assocs)
import Data.Monoid
type Path a = [a]
data Tree a = Tree {leaf :: Bool, childs :: Map a (Tree a)} deriving Show
树中的布尔标志标记该节点是否可以是路径的末端。这些a
值隐藏在childs
地图中。为了热身,让我们定义如何将单个路径转换为树。
root :: Tree a
root = Tree True empty
cons :: a -> Tree a -> Tree a
cons node tree = Tree False (singleton node tree)
follow :: Path a -> Tree a
follow = foldr cons root
该follow
函数fromList
在 Gabriel 的代码中被调用。我们还可以枚举树中包含的所有路径。
paths :: Tree a -> [Path a]
paths (Tree leaf childs) =
(if leaf then [[]] else []) ++
[ node : path | (node, tree) <- assocs childs, path <- paths tree ]
这些问题本质上是要求这个paths
函数的倒数。使用unionWith
,我们可以很容易地定义树的幺半群结构。
instance Ord a => Monoid (Tree a) where
mempty = Tree False empty
mappend (Tree leaf1 childs1) (Tree leaf2 childs2) = Tree leaf childs where
leaf = leaf1 || leaf2
childs = unionWith mappend childs1 childs2
现在要将路径列表转换为树,我们只需使用mconcat
and follow
。
unpaths :: Ord a => [Path a] -> Tree a
unpaths = mconcat . map follow
这是一个使用问题路径的测试用例。
a, b, c, d :: Path String
a = ["foo", "bar", "qux"]
b = ["foo", "bar", "baz"]
c = ["qux", "bar", "qux"]
d = ["foo", "bar", "baz", "quux"]
-- test is True
test = (paths . unpaths) [a, b, c, d] == [b, d, a, c]
我们得到与存储在树中相同的路径,但作为有序列表。