简体   繁体   English

haskell function 的时间复杂度

[英]Time complexity for haskell function

is there a simple way to control time complexity of functions in haskell?有没有一种简单的方法来控制 haskell 中函数的时间复杂度?

I have constructed a function that reverses a list of any type, the goal is to have it in linear time complexity which i have attempted to solve like this:我已经构建了一个 function 反转任何类型的列表,目标是使其具有线性时间复杂度,我试图这样解决:

rev :: [a] -> [a]
rev(x:[]) = [x]
rev(x:xs) = (rev xs) ++ [x]

my intetion is to have it linear, O(n), but i am unsure if it actually is and would therefore like use some type of tool that might analyze the code/function to show that it is infact linear.我的意图是让它成为线性的,O(n),但我不确定它是否真的是这样,因此我想使用某种类型的工具来分析代码/函数以表明它实际上是线性的。

i would thus like to know:因此,我想知道:

Is this function linear?这是 function 线性的吗? can i show it with any analyzing tools?我可以用任何分析工具展示它吗?

(…) have it linear, O(n), but i am unsure if it actually is and would therefore like use some type of tool that might analyze the code/function to show that it is infact linear. (...)它是线性的,O(n),但我不确定它是否真的是,因此我想使用某种类型的工具来分析代码/函数以表明它实际上是线性的。

The function is not linear. function不是线性的。 The append function (++):: [a] -> [a] -> [a] is linear. append function (++):: [a] -> [a] -> [a]是线性的。 Since we do this for each element in the list, we end up with O(n 2 ) time complexity.由于我们对列表中的每个元素都执行此操作,因此最终的时间复杂度为O(n 2 )

What we can do to make this a linear function is use an accumulator : an extra parameter that we use that each time will be passed in an update form:我们可以做些什么来使它成为线性 function 是使用累加器:我们每次使用的额外参数将以更新形式传递:

myrev :: [a] -> [a]
myrev = (`go` [])
  where go (x:xs) ys = go xs (x:ys)
        go [] ys = ys

We thus start the accumulator with an empty list.因此,我们从一个空列表开始累加器。 For each element in the original list x we pop it from the first list, and push it to the second list.对于原始列表x中的每个元素,我们将其从第一个列表中弹出,并将其推送到第二个列表。 When the first list is exhausted, then ys contains the list in reverse.当第一个列表用完时, ys包含反向列表。

Using the ghci interpreter:使用 ghci 解释器:

As a rough first cut, you can use the ghci interpreter.作为粗略的第一步,您可以使用ghci解释器。 For simplicity let's enter your definition of rev interactively rather than from a source file.为简单起见,让我们以交互方式而不是从源文件中输入您对rev的定义。 Then switch statistics mode on:然后打开统计模式

$ ghci
 GHCi, version 8.8.4: https://www.haskell.org/ghc/  :? for help

 λ> 
 λ> let { rev :: [a] -> [a] ; rev [] = [] ; rev (x:xs) = (rev xs) ++ [x] }
 λ> 
 λ> :set +s
 λ> 

Then we just benchmark your rev function on 10,000 elements then 20,000 elements.然后,我们只需在 10,000 个元素和 20,000 个元素上对您的rev function 进行基准测试。 The point of calling length on the result is to force a full evaluation:对结果调用length的目的是强制进行全面评估:

 λ> 
 λ> length $ rev [1..10000]
10000
(1.27 secs, 4,294,492,504 bytes)
 λ> 
 λ> length $ rev [1..20000]
20000
(5.69 secs, 17,511,565,464 bytes)
 λ> 

So it appears that doubling the workload makes the cost about 4 times larger.所以看起来工作量加倍会使成本增加大约 4 倍。 The rev function has quadratic cost. rev function 具有二次成本。

Why so?为什么这样? well, as everything else in Haskell, the leftmost argument of operator (++) is immutable.好吧,就像 Haskell 中的其他所有内容一样,运算符 (++) 的最左侧参数是不可变的。 Hence, in order to incorporate its leftmost argument into its output, the function has to duplicate it.因此,为了将其最左边的参数合并到其 output 中,function 必须复制它。

One can try the same benchmark using the code provided in Willem's answer.可以使用 Willem 的答案中提供的代码尝试相同的基准测试。 Much better... In order to get significant numbers, it is better to try it with 1,000,000 rather than just 10,000 elements.好多了...为了获得重要的数字,最好用 1,000,000 个而不是 10,000 个元素进行尝试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM