【问题标题】:O(1) circular buffer in haskell?Haskell中的O(1)循环缓冲区?
【发布时间】:2010-02-08 16:12:26
【问题描述】:

我正在使用 Haskell 开发一个需要循环缓冲区的小型概念项目。我已经设法使用具有 O(1) 旋转的数组创建了一个缓冲区,但当然需要 O(N) 进行插入/删除。我找到了一个使用列表的实现,它似乎需要 O(1) 来进行插入和删除,但由于它维护一个左右列表,因此在旋转时越过某个边界需要 O(N) 时间。在命令式语言中,我可以实现一个具有 O(1) 插入、删除和旋转的双向链接循环缓冲区。我认为这在像 Haskell 这样的纯函数式语言中是不可能的,但我很想知道我是否错了。

【问题讨论】:

  • 如果旋转时“越界”需要O(N)时间,不越界的代价是多少?如果是 O(1) 并且你只有 1/N 的概率越过边界,那么旋转平均需要 O(1) 时间。
  • 是的,但是执行顺序操作可以保证您会在某个时刻越过它,并且时间复杂度对于每次旋转都很重要,因为这最终可能会成为一个软实时应用程序。
  • 我从未使用过循环缓冲区;简单描述一下你的缓冲区在做什么很容易吗?在你的应用程序中它应该“覆盖”元素吗?
  • 忘了补充:您是否考虑过是否可以利用惰性来使循环缓冲区结构变得不必要?
  • 具体来说,我需要能够传递一个焦点在某个样本上的缓冲区,改变该样本,在 O(1) 中向左旋转焦点,并在 O(1) 中向右旋转焦点),全部作为单独的操作。

标签: haskell functional-programming circular-buffer


【解决方案1】:

如果您可以处理 amortized O(1) 操作,您可以使用容器包中的 Data.Sequence 或 dequeue 包中的 Data.Dequeue。前者使用finger trees,而后者使用Okasaki的Purely Functional Data Structures的“Banker's Dequeue”(之前的在线版本here)。

【讨论】:

    【解决方案2】:

    ST monad 允许在 Haskell 中描述和执行命令式算法。您可以使用STRefs 作为双向链表的可变指针。

    使用ST 描述的自包含算法使用runST 执行。不同的runST 执行可能不共享ST 数据结构(STRefSTArray、..)。

    如果算法不是“自包含”的,并且数据结构需要在使用之间执行 IO 操作来维护,您可以使用stToIOIO monad 中访问它。

    关于这是否纯粹是功能性的 - 我想不是吗?

    【讨论】:

      【解决方案3】:

      听起来您可能需要比这更复杂的东西(因为您提到了双向链表),但这可能会有所帮助。此函数的作用类似于 map 在可变循环列表上:

      mapOnCycling f = concat . tail . iterate (map f)
      

      像这样使用:

      *Main> (+1) `mapOnCycling` [3,2,1]
      
      [4,3,2,5,4,3,6,5,4,7,6,5,8,7,6,9,8,7,10,9...]
      

      这是一个类似于 mapAccumL 的:

      mapAccumLOnCycling f acc xs = 
          let (acc', xs') =  mapAccumL f acc xs
           in xs' ++ mapAccumLOnCycling f acc' xs'
      

      无论如何,如果您想更详细地说明您的数据结构究竟需要能够“做什么”,我会非常有兴趣了解它。

      编辑:正如 camccann 所提到的,您可以为此使用Data.Sequence,根据文档,这应该会给您 O1 时间复杂度(是否存在 O1 摊销时间之类的东西?)以供查看或在序列的左侧和右侧添加元素,以及沿途修改末端。这是否会有你需要的性能,我不确定。

      您可以将“当前位置”视为序列的左端。在这里,我们沿着一个序列来回穿梭,产生一个无限的值列表。抱歉,如果它没有编译,我目前没有 GHC:

      shuttle (viewl-> a <: as) = a : shuttle $ rotate (a+1 <| as)
          where rotate | even a    = rotateForward
                       | otherwise = rotateBack
                rotateBack (viewr-> as' :> a')    = a' <| as'
                rotateForward (viewl-> a' <: as') = as' |> a'
      

      【讨论】:

      • 查看对原始问题的评论以了解更具体的功能。
      • 更新了我的答案,我认为它可以为您提供您正在寻找的纯功能解决方案
      • 顺便说一句,摊销 O(1) 是完全合理的——它只是意味着可能会发生昂贵的操作,但频率与其成本成反比。例如,一个操作可能大部分时间是 O(1),偶尔是 O(N),但只要后者不比每 N 个操作中的一次更常见,摊销复杂度仍然是 O(1)。这对于大多数目的来说都很好,但对于这里的问题的 cmets 来说,对于软实时来说并不是那么好......
      猜你喜欢
      • 2021-08-11
      • 1970-01-01
      • 2016-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多