【问题标题】:How do inits and tails work in Data.Sequence?初始化和尾部如何在 Data.Sequence 中工作?
【发布时间】:2015-03-06 20:05:24
【问题描述】:

Louis Wasserman 在Data.Sequence 中编写了initstails 的当前实现。他表示它们非常高效,而且确实只要查看代码,我就可以看到无论它们在做什么,它们都是以干净、自上而下的方式进行的,这种方式往往会为惰性树带来良好的性能。不幸的是,我实际上无法对他们正在做的事情做出正面或反面。谁能帮我一把?代码有点长,可以在Hackage上找到。

【问题讨论】:

标签: haskell data-structures sequence


【解决方案1】:

是我!

我认为最好的方法可能是通过一个例子来工作。让我们走吧……

Deep (Two 1 2)                                                    (Two 7 8))
                (Deep (One (Node2 3 4))        (One (Node2 5 6))
                                         Empty

这是一个序列,有些简化(例如,省略了 Elem 包装器)。

让我们对此进行初始化;尾巴本质上是对称的。我们的递归算法将省略空的 init,只包含非空的东西。


前缀

因此,init 的前缀数字本质上将使用fmap digitToTree (initsDigit (Two 1 2)) 生成。

initsDigit (Two 1 2) = Two (One 1) (Two 1 2)
fmap digitToTree (Two (One 1) (Two 1 2)) = 
    Two (Single 1) (Deep (One 1) Empty (One 2))

所以这是整个事情的前两个初始值,这个数字将是inits 结果的前缀数字。 (除了我们要在完成后添加空序列,但我们现在先忽略它。)


内层树

现在让我们看一下内部树的初始化,将其视为FingerTree (Node a)——我们还不打算拆开节点,它只是一个包含两个节点的两个元素FingerTree。这个细节我就不说了,只是通过相同的算法递归,我只是要神奇的得到结果

Deep 
    (One (Single (Node2 3 4))) 
    Empty 
    (One (Deep (One (Node2 3 4)) Empty (One (Node2 5 6))))
  :: FingerTree (FingerTree (Node a))

所以这些是内部树的初始值。这些如何对应于外部树的初始值?内层树的每个init对应一棵包含

的树
  • 原始树的前缀数字,Two 1 2
  • 除了内部树的 init 的最后一个 Node 之外的所有内容
  • 内部树的 init 的最后一个 Node 的一些前缀

因此,通过获取内部树的 init 获得的每个 FingerTree (Node a) 将映射到 Node (FingerTree a),其中包含 FingerTree 用于 FingerTree (Node a) 中最后一个节点的每个 init。

因此例如Single (Node2 3 4),在其最后一个节点被提取后,将被分解为EmptyNode2 3 4,得到的Node (FingerTree a)

Node2 
   (Deep (Two 1 2 {- prefix of original tree -}) 
         Empty 
         (One 3 {- first prefix of Node2 3 4 -}))
   (Deep (Two 1 2) 
         Empty 
         (Two 3 4 {- second prefix of Node2 3 4 -}))

对于内部树的另一个前缀 Deep (One (Node2 3 4)) Empty (One (Node2 5 6)),提取最后一个 Node 得到余数 Single (Node2 3 4) 和提取的节点 Node2 5 6,因此得到 Node (FingerTree a)

Node2
   (Deep (Two 1 2 {- prefix of original tree -})
         (Single (Node2 3 4) {- init of the inner tree minus the last Node -})
         (One 5 {- first prefix of Node2 5 6 -})
   (Deep (Two 1 2 {- prefix of original tree -})
         (Single (Node2 3 4) {- init of the inner tree minus the last Node -})
         (Two 5 6 {- second prefix of Node2 5 6 -}))

所以这是一个将FingerTree (Node a)(内部树的单个初始化)带到Node (FingerTree a) 的操作。因此,通过递归获取内部树的初始值作为FingerTree (FingerTree (Node a)),我们将这个函数映射到它们上以获得FingerTree (Node (FingerTree a)),这正是我们想要的;它是整个事物的 inits 的内部树。


后缀

最后,有原树的inits组成

  • 原始前缀
  • 原来的内部树
  • 原树后缀的每个init

这些成为init树的后缀数字。 initsDigit (Two 7 8) 返回Two (One 7) (Two 7 8),我们基本上只是将\sf -> Deep pr m sf 映射到它上面,得到

Two 
   (Deep (Two 1 2 {- original -})
         (Deep (One (Node2 3 4)) Empty (One (Node2 5 6)) {- original -})
         (One 7 {- first init of original suffix digit -}))
   (Deep (Two 1 2 {- original -})
         (Deep (One (Node2 3 4)) Empty (One (Node2 5 6)) {- original -})
         (Two 7 8 {- second init of original suffix digit -})) 

所以,这并不是代码的组织方式。我们已经描述了从FingerTree aFingerTree (FingerTree a) 的函数,但实际的实现基本上是加上fmap,因为我们最终总是需要以某种方式映射元素——即使它只是包装新类型。但这基本上就是我们正在做的事情。

【讨论】:

  • 我必须承认这仍然让我有点头晕,但我会继续努力克服它。感谢您花时间解释。
  • 我想我现在明白了它的大致要点。它似乎只是勉强工作,几乎是偶然的。手指树很奇怪。你有没有机会看到我对<*>*>zipWithData.Sequence 的重新实现?我终于将<*>*> 变成了不错的形状today,尽管早期版本变成了containers-0.5.6.3
  • 我已经好几年没关注这个了,不过我明天可以看看。
猜你喜欢
  • 1970-01-01
  • 2018-12-07
  • 1970-01-01
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-25
相关资源
最近更新 更多