【问题标题】:How can I ensure amortized O(n) concatenation from Data.Vector?如何确保来自 Data.Vector 的摊销 O(n) 连接?
【发布时间】:2011-12-16 08:51:11
【问题描述】:

我有一个应用程序,在其中一部分代码中使用向量非常有效。但是,在计算过程中,我需要跟踪一些元素。我听说您可以从 Data.Vectors 获得 O(n) 摊销连接(通过通常的数组增长技巧),但我认为我做得不对。所以假设我们有以下设置:

import Data.Vector((++),Vector)
import Prelude hiding ((++))
import Control.Monad.State.Strict

data App = S (Vector Int)

add :: Vector Int -> State App ()
add v1 = modify (\S v2 -> S (v2 ++ v1))

在这种情况下,add 是否在摊销 O(n) 时间内运行?如果没有,我怎样才能让add 做到这一点(我需要在状态中存储(forall s. MVector s Int) 吗?)。有没有更有效的方法来实现add

【问题讨论】:

  • @hvr:不,这不是我想要的。我很确定我在我的问题中没有正确地做到这一点。但是,我不太了解 Data.Vector 库,虽然文档 (++) 只声明它是 O(m+n) 最坏的情况,但它可能仍然有 O (n) 摊销运行时间。
  • 所以我讨厌 StackOverflow 上的人们用问题回答问题,但我即将这样做:P - 你为什么认为你做错了?我假设您出于某种与此问题无关的原因使用State,所以如果您将State 相关的东西去掉,很明显您没有做任何疯狂的事情:

标签: haskell vector amortized-analysis


【解决方案1】:

我也不是很了解矢量库,所以我主要还是要坚持一般原则。对其进行基准测试,运行一系列与您在应用程序中所期望的类似的添加,并查看您获得的性能。如果它“足够好”,那就太好了,坚持使用简单的代码。如果没有,在状态中存储(forall s. MVector s Int) 之前(你不能直接,元组不能保存所有类型,所以你需要包装它),尝试通过转换为可变向量来改进添加行为并在冻结之前在那里执行连接以再次大致获得一个不可变的向量

add v1 = do
    S v2 <- get
    let v3 = runST $ do
                m1 <- unsafeThaw v2
                m2 <- unsafeGrow m1 (length v1)
                -- copy contents of v1 behind contents of v2
                unsafeFreeze m2
    put (S v3)

您可能需要在此处插入一些严格性。但是,如果 unsafeGrow 需要复制,则不能保证摊销 O(n) 行为。

您可以通过

获得摊销的 O(n) 行为
  1. 也在状态中存储已使用的插槽数
  2. 如果新向量最后适合空闲空间,则解冻、复制、冻结而不增长
  3. 如果它不适合可用空间,则至少增长 2 倍,这样可以保证每个元素平均最多被复制两次

【讨论】:

  • 添加的顺序在问题中是错误的,所以我改变了它。第一种情况你的问题是我追求的是什么......
  • 已更新以匹配修订后的问题。
  • 我可能是错的,但我认为要摊销 O(n),你不需要增长因子 2。我认为它必须大于 1(但你需要检查您是否至少增长了 1 个带有小列表的元素)。
  • 您当然是对的,任何因子 q > 1 都可以,副本数的界限为 q/(q-1)。如果内存是一个问题,较小的因素可能更可取,我选择了 2,因为它很容易并且可以保持较低的副本数量。
猜你喜欢
  • 2018-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多