我一直在研究非临时性惰性评估,这使我想到了KeeganMcAllister的演讲:为什么要学习Haskell。在幻灯片8中,他显示了最小功能,定义为:
minimum = head . sort
并指出其复杂度为O(n)。我不明白,如果按替换排序为O(nlogn),为什么说复杂度是线性的。帖子中提到的排序不能是线性的,因为它不假设任何有关数据的信息,这是线性排序方法(例如计数排序)所要求的。
懒惰的评估在这里扮演着神秘的角色吗?如果是这样,其背后的解释是什么?
在minimum = head . sort中,sort将不能完全做到,因为它不会做 前期 。将sort根据需要生产出的第一个元素,通过要求才会被尽可能多的完成head。
sort
head
在例如mergesort中,首先n将成对比较列表中的数字,然后将获胜者进行配对和比较(n/2数字),然后将新的获胜者(n/4)等。总而言之,O(n)进行比较以产生最小的元素。
n
n/2
n/4
O(n)
mergesortBy less [] = [] mergesortBy less xs = head $ until (null.tail) pairs [[x] | x <- xs] where pairs (x:y:t) = merge x y : pairs t pairs xs = xs merge (x:xs) (y:ys) | less y x = y : merge (x:xs) ys | otherwise = x : merge xs (y:ys) merge xs [] = xs merge [] ys = ys
可以扩展上面的代码,以使用产生的大量比较标记它产生的每个数字:
mgsort xs = go $ map ((,) 0) xs where go [] = [] go xs = head $ until (null.tail) pairs [[x] | x <- xs] where .... merge ((a,b):xs) ((c,d):ys) | (d < b) = (a+c+1,d) : merge ((a+1,b):xs) ys -- cumulative | otherwise = (a+c+1,b) : merge xs ((c+1,d):ys) -- cost .... g n = concat [[a,b] | (a,b) <- zip [1,3..n] [n,n-2..1]] -- a little scrambler
运行它几个列表长度,我们看到 它确实是~ n:
~ n
*Main> map (fst . head . mgsort . g) [10, 20, 40, 80, 160, 1600] [9,19,39,79,159,1599]
要查看排序代码本身是否为~ n log n,我们对其进行更改,以使每个产生的数字都带有其自身的成本,然后通过对整个排序列表求和得出总成本:
~ n log n
merge ((a,b):xs) ((c,d):ys) | (d < b) = (c+1,d) : merge ((a+1,b):xs) ys -- individual | otherwise = (a+1,b) : merge xs ((c+1,d):ys) -- cost
这是各种长度列表的结果,
*Main> let xs = map (sum . map fst . mgsort . g) [20, 40, 80, 160, 320, 640] [138,342,810,1866,4218,9402] *Main> map (logBase 2) $ zipWith (/) (tail xs) xs [1.309328,1.2439256,1.2039552,1.1766101,1.1564085]
上面显示了随着列表长度的增加,经验的增长顺序,n正如 ~ n log n 计算通常显示的那样,这些顺序正在迅速减少。另请参阅此博客文章。快速相关检查:
*Main> let xs = [n*log n | n<- [20, 40, 80, 160, 320, 640]] in map (logBase 2) $ zipWith (/) (tail xs) xs [1.3002739,1.2484156,1.211859,1.1846942,1.1637106]
编辑: 懒惰的评估可以比喻为生产者/消费者习惯用语1,以独立的备注存储为中介。我们编写的任何生产性定义都定义了一个生产者,该生产者将根据其消费者的需求(但不尽早)一点一点地产生其输出。所产生的任何内容都会被记录下来,这样,如果另一个消费者以不同的速度消费相同的输出,它将访问先前填充的相同存储。
当没有更多的消费者引用存储空间时,就会收集垃圾。有时,通过优化,编译器可以完全消除中间存储,从而淘汰中间人。
1另请参见:Oleg Kiselyov,Simon Peyton-Jones和Amr Sabry撰写的Simple Generators v。Lazy Evaluation。