无限深度和无限宽度玫瑰树的延迟折叠到其边缘路径
Posted
技术标签:
【中文标题】无限深度和无限宽度玫瑰树的延迟折叠到其边缘路径【英文标题】:Lazy Folding of Infinite Depth & Infinite Breadth Rose Tree to its Edge Paths 【发布时间】:2016-01-12 17:49:21 【问题描述】:这个问题haskell fold rose tree paths 深入研究了将玫瑰树折叠到其路径的代码。我正在试验无限玫瑰树,我发现提供的解决方案不够懒惰,无法在深度和广度都无限的无限玫瑰树上工作。
考虑一棵玫瑰树,例如:
data Rose a = Rose a [Rose a] deriving (Show, Functor)
这是一棵有限的玫瑰树:
finiteTree = Rose "root" [
Rose "a" [
Rose "d" [],
Rose "e" []
],
Rose "b" [
Rose "f" []
],
Rose "c" []
]
边缘路径列表的输出应该是:
[["root","a","d"],["root","a","e"],["root","b","f"],["root","c"]]
这是一棵二维的无限玫瑰树:
infiniteRoseTree :: [[a]] -> Rose a
infiniteRoseTree ((root:_):breadthGens) = Rose root (infiniteRoseForest breadthGens)
infiniteRoseForest :: [[a]] -> [Rose a]
infiniteRoseForest (breadthGen:breadthGens) = [ Rose x (infiniteRoseForest breadthGens) | x <- breadthGen ]
infiniteTree = infiniteRoseTree depthIndexedBreadths where
depthIndexedBreadths = iterate (map (+1)) [0..]
这棵树是这样的(只是摘录,有无限的深度和无限的广度):
0
|
|
[1,2..]
/ \
/ \
/ \
[2,3..] [2,3..]
路径看起来像:
[[0,1,2..]..[0,2,2..]..]
这是我最近的尝试(在 GHCi 上执行此操作会导致无限循环,没有流输出):
rosePathsLazy (Rose x []) = [[x]]
rosePathsLazy (Rose x children) =
concat [ map (x:) (rosePathsLazy child) | child <- children ]
rosePathsLazy infiniteTree
另一个答案中提供的解决方案也没有产生任何输出:
foldRose f z (Rose x []) = [f x z]
foldRose f z (Rose x ns) = [f x y | n <- ns, y <- foldRose f z n]
foldRose (:) [] infiniteTree
上述两种方法都适用于有限玫瑰树。
我尝试了许多变体,但我无法弄清楚如何让无限二维玫瑰树的边缘折叠操作变得惰性。我觉得这与无限量的concat
有关。
因为输出是一个二维列表。我可以运行二维take
并同时使用深度限制或广度限制进行投影!
感谢任何帮助!
在查看了这里的答案并进行了更多思考之后。我意识到这是可展开的,因为结果列表是无数无限的。这是因为无限深度和广度玫瑰树不是二维数据结构,而是无限维数据结构。每个深度级别都赋予一个额外的维度。换句话说,它在某种程度上相当于一个无限维矩阵,想象一个矩阵,其中每个字段都是另一个矩阵.. ad-infinitum。无限矩阵的基数是infinity ^ infinity
,它已被证明(我认为)是不可数无限的。这意味着任何无限维数据结构在有用的意义上都不是真正可计算的。
要将这个应用到玫瑰树,如果我们有无限的深度,那么路径永远不会枚举到玫瑰树的最左边。那就是这棵树:
0
|
|
[1,2..]
/ \
/ \
/ \
[2,3..] [2,3..]
会产生类似[[0,1,2..], [0,1,2..], [0,1,2..]..]
的路径,而我们永远无法通过[0,1,2..]
。
或者以另一种方式,如果我们有一个包含无限列表的列表。我们也永远无法计算(枚举)它,因为代码会跳转到无数个维度。
这也与不可数无限的实数有关。在无限实数的惰性列表中,只会无限产生0.000..
,并且永远不会枚举过去。
我不确定如何将上述解释形式化,但这是我的直觉。 (供参考:https://en.wikipedia.org/wiki/Uncountable_set)看到有人扩展将https://en.wikipedia.org/wiki/Cantor's_diagonal_argument 应用于此问题会很酷。
这本书似乎对它进行了扩展:https://books.google.com.au/books?id=OPFoJZeI8MEC&pg=PA140&lpg=PA140&dq=haskell+uncountably+infinite&source=bl&ots=Z5hM-mFT6A&sig=ovzWV3AEO16M4scVPCDD-gyFgII&hl=en&sa=X&redir_esc=y#v=onepage&q=haskell%20uncountably%20infinite&f=false
【问题讨论】:
【参考方案1】:出于某种原因,dfeuer 删除了他的答案,其中包括一个非常好的见解,并且只有一个小问题,很容易解决。下面我讨论他的好见解,并解决容易解决的问题。
他的见解是原始代码挂起的原因是因为concat
并不明显其参数列表的任何元素都是非空的。既然我们可以证明这一点(在 Haskell 之外,用纸和铅笔),我们可以稍微作弊来让编译器相信它确实如此。
不幸的是,concat
不够好:如果你给concat
一个类似[[1..], foo]
的列表,它永远不会从foo
中提取元素。 universe
包集合可以通过它的 diagonal
函数提供帮助,它确实从所有子列表中绘制元素。
这两个见解共同导致以下代码:
import Data.Tree
import Data.Universe.Helpers
paths (Node x []) = [[x]]
paths (Node x children) = map (x:) (p:ps) where
p:ps = diagonal (map paths children)
如果我们定义一个特定的无限树:
infTree x = Node x [infTree (x+i) | i <- [1..]]
我们可以看看它在 ghci 中的表现:
> let v = paths (infTree 0)
> take 5 (head v)
[0,1,2,3,4]
> take 5 (map head v)
[0,0,0,0,0]
看起来不错!当然,正如 ErikR 所观察到的,我们不能在这里拥有所有路径。但是,给定通过t
的无限路径的任何有限前缀p
,在paths t
中存在一个有限索引,其元素以前缀p
开头。
【讨论】:
我删除了我的帖子,因为 user3237465 指出它也被卡住了。我不太明白为什么,也想不出一个快速的解决办法。 我最喜欢的帖子部分是无限树构造。你可能会考虑偷它。 @dfeuer,我不是第一个:有人说带有concatMap
的版本会循环。问题在于concatMap
:map paths
导致[(x1 : th1), (x2 : th2),...]
、concat
然后只产生第一个x1
。如果我们用shift xs = map head xs ++ concat (map tail xs)
替换concat
,那么paths
确实会产生一些无意义的输入。我猜diagonal
之所以有效,是因为路径被强制得更深:例如如果Rose
的深度大于2
,那么我们知道所有路径都至少有2
元素。
@user3237465,我不明白的部分是为什么th1 = _|_
。
*“无意义的输出”而不是“无意义的输入”。 @dfeuer,looks 喜欢,因为在 a:as
as
永远不会被强迫。但是我不明白diagonal
有什么不同,也不想手动去追踪。【参考方案2】:
不是一个完整的答案,但您可能对这个有关 Haskell 的 permutations
函数如何编写以使其适用于无限列表的详细答案感兴趣:
What does this list permutations implementation in Haskell exactly do?
更新
这是创建无限玫瑰树的更简单方法:
iRose x = Rose x [ iRose (x+i) | i <- [1..] ]
rindex (Rose a rs) [] = a
rindex (Rose _ rs) (x:xs) = rindex (rs !! x) xs
例子:
rindex (iRose 0) [0,1,2,3,4,5,6] -- returns: 26
rindex infiniteTree [0,1,2,3,4,5,6] -- returns: 13
无限深度
如果玫瑰树具有无限深度和非平凡宽度 (> 1),则无法仅使用计数参数列出所有路径的算法 - 总路径的数量不可计数。
有限的深度和无限的宽度
如果玫瑰树的深度是有限的,那么即使树的宽度是无限的,路径的数量也是可数的,并且有一种算法可以产生所有可能的路径。关注此空间以获取更新。
【讨论】:
我注意到你可以折叠一个无限深度和无限宽度的树,如果你让结果是一个类似于rindex
的函数,除了无限索引列表。
你如何定义深度?所有路径的长度的上限?如果所有路径都是有限的(但可能具有无限长度),那么路径的数量是可数的(只要每个节点的宽度是可数的)。
您无法列出所有路径。但是您可以拥有一个具有显着属性的路径列表:给定路径索引的任何有限前缀,在包含具有相同前缀的路径的路径列表中存在一个有限索引。这几乎可以肯定对于计算来说已经足够好了,因为无论如何,在有限空间中几乎没有无限路径可以表示。请参阅@dfeuer 的答案以了解如何操作。
@DanielWagner:您可以列出所有路径,只要每条路径的长度有限,但您必须更改它们的顺序。
我添加了我的直觉,说明为什么这棵树是可展开的。【参考方案3】:
ErikR 解释了为什么您不能生成一个必然包含所有路径的列表,但是可以从左侧懒惰地列出路径。最简单的技巧,尽管很脏,是认识到结果永远不会是空的,然后把这个事实强加给 Haskell。
paths (Rose x []) = [[x]]
paths (Rose x children) = map (x :) (a : as)
where
a : as = concatMap paths children
-- Note that we know here that children is non-empty, and therefore
-- the result will not be empty.
要制作非常无限的玫瑰树,请考虑
infTree labels = Rose labels (infForest labels)
infForest labels = [Rose labels' (infForest labels')
| labels' <- map (: labels) [0..]]
作为 chi points out,虽然 paths
的这个定义是有效的,但在某些情况下它会永远重复最左边的路径,并且永远不会再到达。哎呀!因此,有必要进行一些公平或对角遍历的尝试,以提供有趣/有用的结果。
【讨论】:
很好的答案!尽管您必须将concat
替换为 diagonal
或类似名称。否则,由于第一个孩子将产生无限多的路径,concatMap paths children
将不会在任何有限索引处包含来自第二个孩子的任何路径。所以递归调用应该是diagonal (map paths children)
。
你能根据Rose
构造函数写infTree
吗?这将使测试更容易。我正在等待:head ((paths (iRose 0)) !! 2)
。我的代码:lpaste.net/143025
我对此感到困惑,尤其是当你说“从左边”时。当每个节点恰好有两个按该顺序标记为True,False
的子节点时,考虑一棵二叉树。您生成的第一个路径是repeat True
。好的。然而,second 路径是什么?我担心它又是repeat True
。更糟糕的是,您将永远无法到达某些不同的路径(其中包含一些 False
)。我担心整个结果是repeat (repeat True)
。
@chi,有趣的一点!一些“公平”似乎对于产生任何有用的结果是必要的。
@dfeuer,除了第一个路径之外,您的版本是否产生任何内容?我不能让它与问题中的infiniteTree
一起使用:take 2 $ map (take 1) $ paths infiniteTree
循环。以上是关于无限深度和无限宽度玫瑰树的延迟折叠到其边缘路径的主要内容,如果未能解决你的问题,请参考以下文章