在minimum = head . sort
中,sort
不会完全完成,因为它不会预先完成。sort
只会根据head
.
在例如合并排序中,首先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
:
*Main> map (fst . head . mgsort . g) [10, 20, 40, 80, 160, 1600]
[9,19,39,79,159,1599]
要查看排序代码本身是否为~ 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 。