Haskell:List v.Array,性能差异

时间:2014-10-03 18:26:41

标签: arrays performance list haskell

来自Haskell n00b的另一个问题。

我在Project Euler网站上比较用于解决问题#14的各种方法的效率。特别是,我希望能够更好地理解推动解决问题的四种(略微)不同方法的评估时间差异的因素。

(问题#14的描述和各种方法如下。)

首先,快速概述问题#14。它与" Collat​​z数字" (即与我上一篇探讨Haskell不同方面的文章相同的编程练习)。给定整数的Collat​​z数等于该整数的Collat​​z序列的长度。整数的Collat​​z序列计算如下:序列中的第一个数字(" n0")是整数本身;如果n0是偶数,则序列中的下一个数字(" n1")等于n / 2;如果n0是奇数,那么n1等于3 * n0 + 1.我们继续递归地扩展序列,直到我们到达1,此时序列结束。例如,5的折叠序列是:{5,16,8,4,2,1}(因为16 = 3 * 5 + 1,8 = 16 / 2,4 = 8/2,......)。

问题14要求我们找到具有最大Collat​​z数的1,000,000以下的整数。为此,我们可以考虑一个功能" collat​​z"当传递一个整数" n"作为参数,返回具有最大Collat​​z数的n以下的整数。换句话说,p 1000000为我们提供了问题#14的答案。

出于本练习的目的(即,了解评估时间的差异),我们可以考虑Haskell版本的' collat​​z'它在两个方面有所不同:

(1)实现:我们是否将Collat​​z数据的数据集(将为所有整数1..n生成)作为列表或数组存储?我称之为"实施"维度,即函数的实现是" list"或"数组"。

(2)算法:我们是否通过扩展Collat​​z序列直到它完成(即直到我们达到1)来计算任何给定整数n的Collat​​z数?或者我们只延伸序列,直到我们达到小于n的数字k(此时我们可以简单地使用我们已经计算过的k的Collat​​z数)?我称之为"算法"维度,即函数的算法是"完成" (计算每个整数的Collat​​z数)或" partial"。后者显然需要更少的操作。

以下是" collat​​z"的四个可能版本。 function:array / partial,list / partial,array / complete和list / complete:

import Data.Array ( (!) , listArray , assocs )
import Data.Ord ( comparing )
import Data.List ( maximumBy )

--array implementation; partial algorithm (FEWEST OPERATIONS)
collatzAP x = maximumBy (comparing snd) $ assocs a where
    a = listArray (0,x) (0:1:[c n n | n <- [2..x]])
    c n i = let z = if even i then div i 2 else 3*i+1
      in if i < n then a ! i else 1 + c n z

--list implementation; partial algorithm
collatzLP x = maximum a where   
    a = zip (0:1:[c n n | n <- [2..x]]) [0..x]
    c n i = let z = if even i then div i 2 else 3*i+1
      in if i < n then fst (a!!i) else 1 + c n z

--array implementation, complete algorithm
collatzAC x = maximumBy (comparing snd) $ assocs a where
    a = listArray (0,x) (0:1:[c n n | n <- [2..x]])
    c n i = let z = if even i then div i 2 else 3*i+1
     in if i == 1 then 1 else 1 + c n z     

--list implementation, complete algorithm (MOST OPERATIONS)
collatzLC x = maximum a where   
    a = zip (0:1:[c n n | n <- [2..x]]) [0..x]
    c n i = let z = if even i then div i 2 else 3*i+1
      in if i == 1 then 1 else 1 + c n z

关于评估速度:我知道数组的访问速度远远快于列表(即,给定索引n的O(1)与O(n)访问时间)所以我期望&#39;数组&# 39;实施&#34; collat​​z&#34;要快于&#39;列表&#39;实施,其他条件不变。另外,我预计会有部分&#39;算法要比“完成”算法快。算法(ceteris paribus),因为它需要执行更少的操作才能构建Collat​​z数字的数据集。

在不同大小的输入上测试我们的四个函数,我们观察以下评估时间(下面的评论):

enter image description here

&#39;阵列/部分&#39;确实如此。版本是&#34; collat​​z&#34;的最快版本。 (差距很大)。但是,我觉得有点违反直觉,列出/完成&#39;不是最慢的版本。这个荣誉归于&#39; list / partial&#39;,这比'list / complete&#39;慢20倍!

我的问题:&lt; list / partial&#39;之间的评估时间是否有差异?和&#39;列出/完成&#39; (与&#39; array / partial&#39; array&complete&#39;之间的完全相比)完全是由于Haskell中列表和数组之间访问效率的差异?或者我没有进行对照实验&#34; (即,还有其他因素在起作用吗?)

1 个答案:

答案 0 :(得分:4)

我不明白两个与列表一起使用的算法的相对性能问题如何与数组完全相关......但这是我的看法:

如果性能有任何问题,请尽量避免索引列表,特别是长列表。索引实际上是一种遍历(如您所知)。 &#34;列表/部分&#34;索引/遍历很多。列表/完成不是。因此,Array / complete和List / complete之间的差异可以忽略不计,并且&#34; list / partial&#34;之间的差异可以忽略不计。其余的都是巨大的。