什么容器真的模仿Haskell中的std :: vector?

时间:2014-06-09 15:52:52

标签: c++ haskell

问题

我正在寻找一个用于保存n - 1个问题的部分结果的容器,以便计算n个问题。这意味着最后容器的大小始终为n

容器的每个元素i取决于至少2个,最多4个以前的结果。

容器必须提供:

  • 在开头或结尾插入常量时间(两者之一,不一定都是)
  • 中间的恒定时间索引

或者(给定O(n)初始化):

  • 恒定时间单个元素编辑
  • 中间的恒定时间索引

什么是std::vector以及它为何相关

对于那些不了解C ++的人来说,std::vector是一个动态大小的数组。它非常适合这个问题,因为它能够:

  • 建造时预留空间
  • 在中间提供恒定时间索引
  • 在末尾提供恒定时间插入(带有保留空间)

因此,在{+ 1}}复杂性,C ++中,这个问题是可以解决的。

为什么O(n)不是Data.Vector

std::vectorData.Vector一起提供与Data.Array类似的功能,但不完全相同。当然,两者都在中间提供恒定的时间索引,但它们既不提供恒定的时间修改(std::vector,例如至少为(//)),也不提供在任何一个开头的常量时间插入。

结论

什么容器真的模仿Haskell中的O(n)?或者,我最好的镜头是什么?

4 个答案:

答案 0 :(得分:12)

来自reddit的建议是使用Data.Vector.constructN

  

O(n)通过将生成器函数重复应用于向量的已构造部分,构造具有n个元素的向量。

 constructN 3 f = let a = f <> ; b = f <a> ; c = f <a,b> in f <a,b,c>

例如:

λ import qualified Data.Vector as V
λ V.constructN 10 V.length
fromList [0,1,2,3,4,5,6,7,8,9]
λ V.constructN 10 $ (1+) . V.sum
fromList [1,2,4,8,16,32,64,128,256,512]
λ V.constructN 10 $ \v -> let n = V.length v in if n <= 1 then 1 else (v V.! (n - 1)) + (v V.! (n - 2))
fromList [1,1,2,3,5,8,13,21,34,55]

如上所述,这似乎有资格解决问题。

答案 1 :(得分:9)

我想到的第一个数据结构是来自Data.Map的地图或来自Data.Sequence的序列。

更新

Data.Sequence

序列是持久性数据结构,允许大多数操作有效,同时仅允许有限序列。如果您感兴趣,它们的实现基于finger-trees。但它有哪些特质?

  • O(1)计算长度
  • O(1)分别在运营商<||>的前/后插入。
  • fromlist 列表中创建
  • O(n)
  • O(log(min(n1,n2)))连接长度为n1和n2的序列。
  • O(log(min(i,n-i)))为长度为n的序列中位置i的元素建立索引。

此外,这个结构支持许多已经从类似列表结构中获得的已知和方便的函数:replicatezipnullscan s ,sorttakedropsplitAt等等。由于这些相似之处,您必须进行限定导入或隐藏Prelude中具有相同名称的函数。

Data.Map

Maps是实现“事物”之间对应关系的标准主力,你可以称之为Hashmap或其他编程语言中的关联数组在Haskell中被称为Maps;除了说Python Maps是纯粹的 - 因此更新会为您提供一个新的Map,而不会修改原始实例。

地图有两种版本 - strictlazy

从文档中引用

严格

  

此模块的API在键和值中都是严格的。

懒惰

  

此模块的API在键中是严格的,但在值中是懒惰的。

因此,您需要选择最适合您应用的选项。您可以使用criterion尝试同时使用这两个版本和基准。

而不是列出我要传递给{/ 1>的Data.Map的功能

Data.IntMap.Strict

这可以利用密钥是整数的事实来挤出更好的性能 引用我们首先注意到的文档:

  

许多操作都具有O(min(n,W))的最坏情况复杂度。这意味着操作可以在元素数量上变为线性,最大值为W - Int(32或64)中的位数。

那么IntMaps

的特征是什么?
  • O(min(n,W))(不安全)索引(!),如果密钥/索引不存在,您将收到错误,这是不安全的。这与Data.Sequence的行为相同。
  • O(n)计算size
  • O(min(n,W))用于安全索引lookup,如果找不到密钥,则返回Nothing,否则返回Just a
  • O {min(n,W))适用于insertdeleteadjustupdate

所以你看到这个结构的效率低于Sequences,但是如果你实际上不需要所有的条目,比如稀疏图的表示,那么节点就会提供更多的安全性和一个很大的好处是整数。

为了完整性,我想提一个名为persistent-vector的包,它实现了clojure风格的向量,但似乎被放弃了,因为最后一次上传来自(2012)。

结论

因此,对于您的用例,我强烈推荐Data.SequenceData.Vector,遗憾的是我对后者没有任何经验,因此您需要自己尝试一下。从我所知道的东西,它提供了一个称为流融合的强大功能,它优化了在一个紧密的“循环”中执行多个函数,而不是为每个函数运行循环。可以找到Vector的教程here

答案 2 :(得分:3)

在寻找具有特定渐近运行时间的函数容器时,我总是拉出Edison

请注意,结果是在具有不可变数据结构的严格语言中,在它们之上实现可变数据结构总是存在对数减速。隐藏在懒惰背后的有限突变是否可以避免这种放缓,这是一个悬而未决的问题。还存在持久性与短暂性的问题......

Okasaki仍然是一个很好的阅读背景,但是手指树或像RRB树这样更复杂的东西应该是“现成的”并解决你的问题。

答案 3 :(得分:2)

  

我正在寻找一个容器,用于保存n - 1个问题的部分结果,以便计算第n个问题。

     

容器的每个元素i取决于至少2个,最多4个先前的结果。

让我们考虑一个非常小的计划。计算斐波纳契数。

fib 1 = 1
fib 2 = 1 
fib n = fib (n-1) + fib (n-2)

对于小N来说这很好,但是对于n&gt;来说很可怕10.此时,你偶然发现了这个宝石:

fib n = fibs !! n where fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

你可能会惊讶地发现这是黑魔法(无限的,自我参照列表构建和压缩?wth!)但它确实是打结的一个很好的例子,并使用懒惰来确保将值计算为 - 需要的。

同样,我们也可以使用数组来打结。

import Data.Array

fib n = arr ! 10
  where arr :: Arr Int Int
        arr = listArray (1,n) (map fib' [1..n])
        fib' 1 = 1
        fib' 2 = 1
        fib' n = arr!(n-1) + arr!(n-2)

数组的每个元素都是一个thunk,它使用数组的其他元素来计算它的值。通过这种方式,我们可以构建一个单独的数组,从不必执行连接,并随意从数组中调出值,只需支付到那时的计算。

这种方法的美妙之处在于你不必只是在你身后,你也可以在你面前看。