我正在寻找一个用于保存n - 1
个问题的部分结果的容器,以便计算n
个问题。这意味着最后容器的大小始终为n
。
容器的每个元素i
取决于至少2个,最多4个以前的结果。
容器必须提供:
或者(给定O(n)
初始化):
std::vector
以及它为何相关对于那些不了解C ++的人来说,std::vector
是一个动态大小的数组。它非常适合这个问题,因为它能够:
因此,在{+ 1}}复杂性,C ++中,这个问题是可以解决的。
O(n)
不是Data.Vector
std::vector
与Data.Vector
一起提供与Data.Array
类似的功能,但不完全相同。当然,两者都在中间提供恒定的时间索引,但它们既不提供恒定的时间修改(std::vector
,例如至少为(//)
),也不提供在任何一个开头的常量时间插入。
什么容器真的模仿Haskell中的O(n)
?或者,我最好的镜头是什么?
答案 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。但它有哪些特质?
<|
和|>
的前/后插入。fromlist
列表中创建i
的元素建立索引。此外,这个结构支持许多已经从类似列表结构中获得的已知和方便的函数:replicate
,zip
,null
,scan
s ,sort
,take
,drop
,splitAt
等等。由于这些相似之处,您必须进行限定导入或隐藏Prelude
中具有相同名称的函数。
Data.Map
Maps
是实现“事物”之间对应关系的标准主力,你可以称之为Hashmap或其他编程语言中的关联数组在Haskell中被称为Maps;除了说Python Maps
是纯粹的 - 因此更新会为您提供一个新的Map,而不会修改原始实例。
从文档中引用
此模块的API在键和值中都是严格的。
此模块的API在键中是严格的,但在值中是懒惰的。
因此,您需要选择最适合您应用的选项。您可以使用criterion
尝试同时使用这两个版本和基准。
而不是列出我要传递给{/ 1>的Data.Map
的功能
Data.IntMap.Strict
这可以利用密钥是整数的事实来挤出更好的性能 引用我们首先注意到的文档:
许多操作都具有O(min(n,W))的最坏情况复杂度。这意味着操作可以在元素数量上变为线性,最大值为W - Int(32或64)中的位数。
那么IntMaps
(!)
,如果密钥/索引不存在,您将收到错误,这是不安全的。这与Data.Sequence
的行为相同。size
lookup
,如果找不到密钥,则返回Nothing
,否则返回Just a
。 insert
,delete
,adjust
和update
所以你看到这个结构的效率低于Sequences
,但是如果你实际上不需要所有的条目,比如稀疏图的表示,那么节点就会提供更多的安全性和一个很大的好处是整数。
为了完整性,我想提一个名为persistent-vector
的包,它实现了clojure风格的向量,但似乎被放弃了,因为最后一次上传来自(2012)。
因此,对于您的用例,我强烈推荐Data.Sequence
或Data.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,它使用数组的其他元素来计算它的值。通过这种方式,我们可以构建一个单独的数组,从不必执行连接,并随意从数组中调出值,只需支付到那时的计算。
这种方法的美妙之处在于你不必只是在你身后,你也可以在你面前看。