[英]Haskell - List comprehension with infinite lists
這是一段代碼
primepowers n = foldr merge [] [ map (^i) primes | i <- [1..n] ] -- (1)
merge::(Ord t) =>[t]->[t]->[t]
merge x [] = x
merge [] y = y
merge (x:xs) (y:ys)
| x < y = x:merge xs (y:ys)
| otherwise = y:merge (x:xs) ys
等於數學表達式{p^i | p is prime, 1 <= i <= n}
{p^i | p is prime, 1 <= i <= n}
。
prime
返回無限數量的素數列表。 我感興趣的是對(1)
的評估。 這些是我的想法:
如果我們首先看看[ map (^i) primes | i <- [1..3] ]
[ map (^i) primes | i <- [1..3] ]
將返回[[2,3,5,7,9,...],...]
的無限列表。 但是我們知道p^1
(p是質數)永遠不會結束,Haskell永遠不會評估[p^2]
和[p^3]
。 僅僅是因為它是一個無限的列表還是由於懶惰的評估?
讓我們繼續合並:合並將返回[2,3,5,7,9,11,...]
因為我們仍然有一個無限列表,或者由於其他原因?
現在到foldr
: foldr
從后面開始評估。 在這里,特別要求最右邊的元素,它是一個無限列表[p^3]
。 所以評估會像這樣
merge (merge (merge [] [p^3]) [p^2]) [p^1]
但是我們不應該忘記這些列表是無限的,那么Haskell如何處理這個事實呢?
誰能解釋一下上述功能的評估過程?
訣竅是將其定義為
primepowers n = foldr (\(x:xs) r-> x:merge xs r)
[] [ map (^i) primes | i <- [1..n] ]
(如Richard Ord的代碼在文章“ O'Neill,Melissa E.,“真正的Eratosthenes篩子”中所見))。
當前列表右邊的列表均以較大的數字開頭,合並列表產生的值不可能小於或等於當前列表的開頭,因此可以無條件生成。
這樣,它也將僅探索所需的內部流:
GHCi> let pps_list = [ map (^i) primes | i <- [1..42] ]
GHCi> :sprint pps_list
pps_list = _
GHCi> take 20 $ foldr (\(x:xs) r-> x:merge xs r) [] pps_list
[2,3,4,5,7,8,9,11,13,16,17,19,23,25,27,29,31,32,37,41]
GHCi> :sprint pps_list
pps_list = (2 : 3 : 5 : 7 : 11 : 13 : 17 : 19 : 23 : 29 : 31 : 37 :
41 : _) :
(4 : 9 : 25 : 49 : _) : (8 : 27 : 125 : _) : (16 : 81 : _) :
(32 : 243 : _) : (64 : _) : _
就您自己的問題而言,文件foldr fz [a,b,c,...,n] = fa (fb (fc (... (fnz)...)))
所以(為map (^n) primes
寫ps_n
map (^n) primes
),您的表達式等於
merge ps (merge ps_2 (merge ps_3 (... (merge ps_n [])...)))
= merge ps r
where r = merge ps_2 (merge ps_3 (... (merge ps_n [])...))
因為您將merge
用作合並功能。 請注意,最左邊的merge
首先開始起作用,而r
的表達式甚至還沒有建立(因為還不需要其值-Haskell的評估是需要的 。)
現在,此merge
需要它的第一個和第二個參數的頭值(如所寫,它實際上首先檢查第二個參數是否為[]
)。
第一個參數不是問題,但第二個參數是折疊其余所有列表的結果( foldr
的combining函數中的“ r”代表“遞歸結果”)。 因此,將訪問列表中的每個元素,並強制其head元素-所有這一切僅是通過最左邊的merge
調用生成一個第一個值,即結果列表的head ...
在我的代碼中,合並功能首先不要求其第二個參數列表的開頭。 這就是限制其瀏覽整個列表的原因,使其在需求上更加經濟,從而提高了生產率 (如果您完全省略n
它甚至可以工作)。
您的示例Haskell表達式[ map (^i) primes | i <- [1..3] ]
[ map (^i) primes | i <- [1..3] ]
返回長度為3的 有限列表,每個元素為無限列表: [[2,3,5,7,11,...],[4,9,25,...],[8,27,125,...]]
所以foldr
沒有問題翻譯成merge [2,3,5,7,11,...] (merge [4,9,25,...] (merge [8,27,125,..] []))
:
folder merge [] [map(^ i)素數| 我<-[1..3]]
=合並[2,3,5,7,11,...]
) =合並[2,3,5,7,11,...](合並[4,9,25,...] 夾 )
)) =合並[2,3,5,7,11,...](合並[4,9,25,...](合並[8,27,125,..] 夾 )))
=合並[2,3,5,7,11,...](合並[4,9,25,...](合並[8,27,125,..] [] )))
=合並[2,3,5,7,11,...](合並[4,9,25,...] [8,27125,..])
=合並[2,3,5,7,11,...](4:合並[9,25,...] [8,27,125,..])
= 2:合並[3,5,7,11,...](4:合並[9,25,...] [8,27,125,..])
= 2:3:合並[5,7,11,...](4:合並[9,25,...] [8,27,125,..])
= 2:3:4:合並[5,7,11,...](合並[9,25,...] [8,27125,..])
= 2:3:4:合並[5,7,11,...](8:合並[9,25,...] [27125,..])
= 2:3:4:5:合並[7,11,...](8:合並[9,25,...] [27125,..])
.....
如您所見,首先檢查最右邊的內部列表,因為merge
在兩個參數上都是嚴格的(即要求知道),如上所述。 對於[ map (^i) primes | i <- [1..42] ]
[ map (^i) primes | i <- [1..42] ]
會展開所有42個元素,並檢查所有元素的頭部,然后再生成結果的head元素。
通過調整函數mg (x:xs) r = x:merge xs r
,求值過程如下
folder mg [] [map(^ i)primes | 我<-[1..3]]
=毫克[2,3,5,7,11,...](foldr相似毫克[] [圖(^ I)引發(prime)| I < - [2..3]])
= 2:合並[3,5,7,11,...] 夾
= 2:合並[3,5,7,11,...](毫克[4,9,25,...](foldr相似毫克[] [圖(^ I)引發(prime)| I < - [3 .. 3]]))
= 2:合並[3,5,7,11,...](4:合並[9,25,...](foldr相似毫克[] [圖(^ I)引發(prime)| I < - [3 .. 3]]))
= 2:3:合並[5,7,11,...](4:合並[9,25,...](foldr相似毫克[] [圖(^ I)引發(prime)| I < - [3 .. 3]]))
) = 2:3:4:merge [5,7,11,...](合並[9,25,...] 夾 )
= 2:3:4:合並[5,7,11,...](合並[9,25,...](毫克[8,27125,..](foldr相似毫克[] [])))
= 2:3:4:合並[5,7,11,...](合並[9,25,...](8:合並[27125,..](foldr相似毫克[] [])))
= 2:3:4:合並[5,7,11,...](8:合並[9,25,...](合並[27125,..](foldr相似毫克[] [])))
= 2:3:4:5:合並[7,11,...](8:合並[9,25,...](合並[27125,..](foldr相似毫克[] [])))
.....
因此它可以更快地開始產生結果,而無需擴展許多內部列表。 這只是遵循foldr
的定義,
foldr f z (x:xs) = f x (foldr f z xs)
在這里,由於懶惰, 如果 f
不要求它(或它的一部分,如它的頭部 )不要求它的值,則不會立即評估(foldr fz xs)
)。
合並的列表是無限的,但這並不重要。
重要的是,您僅要合並有限數量的列表,因此要計算合並的下一個元素,您只需要執行有限數量的比較即可。
要計算merge xs ys
的頭,您只需要計算xs
的頭和ys
的頭。 因此,通過歸納,如果您具有有限的merge
操作樹,則可以在有限的時間內計算整體合並的頭部。
確實, merge
需要完全掃描其整個輸入列表以產生其整個輸出。 但是,關鍵是輸出中的每個元素僅取決於輸入列表的有限前綴 。
例如,考慮take 10 (map (*2) [1..])
。 要計算前10個元素,您無需檢查整體[1..]
。 實際上, map
不會掃描整個無限列表,並且“之后”將開始返回輸出:如果它的行為如此,它將僅掛在無限列表上。 map
這種“流式”屬性由懶惰和map
定義給出
map f [] = []
map f (x:xs) = x : map f xs
最后一行顯示“ yield x, 然后進行其余操作”,因此調用者可以在map
產生其全部輸出之前檢查x
。 通過對比
map f xs = go xs []
where go [] acc = acc
go (x:xs) acc = go xs (acc ++ [f x])
將是map
另一種定義,它僅在消耗了輸入后才開始生成其輸出。 它在有限列表上等效(不考慮性能),但在無限列表上等效(掛在無限列表上)。
如果您想憑經驗測試merge
確實很懶惰,請嘗試以下操作:
take 10 $ merge (10:20:30:error "end of 1") (5:15:25:35:error "end of 2")
隨時更改常量即可播放。 您將看到在屏幕上打印出一個異常,但是只有在merge
已經產生了一些列表元素之后。
[map (^i) primes | i <- [1..3]]
[map (^i) primes | i <- [1..3]]
僅返回thunk
。 目前尚無任何評估。 您可以嘗試以下方法:
xs = [x | x <- [1..], error ""]
main = print $ const 0 xs
該程序輸出0
,因此這里未評估error ""
。
您可以考慮像這樣定義文件foldr
:
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f xs)
然后
primepowers n = foldr merge [] [map (^i) primes | i <- [1..3]]
評估結果如下(強制執行后):
merge thunk1 (merge thunk2 (merge thunk3 []))
thunkn
是第n次冪的素數的懸浮計算。 現在,第一個merge
強制評估thunk1
和merge thunk2 (merge thunk3 [])
,它們的評估結果為弱頭法線形式(whnf)。 強制merge thunk2 (merge thunk3 [])
會導致強制thunk2
和merge thunk3 []
。 merge thunk3 []
為thunk3
,然后強制thunk3
。 所以表達式變成
merge (2 : thunk1') (merge (4 : thunk2') (8 : thunk3'))
由於合並的定義,其減少為
merge (2 : thunk1') (4 : merge thunk2' (8 : thunk3')
然后再次:
2 : merge thunk1' (4 : merge thunk2' (8 : thunk3')
現在merge
力thunk1'
,但不merge
表達式的其余部分,因為它已經在whnf中
2 : merge (3 : thunk1'') (4 : merge thunk2' (8 : thunk3)
2 : 3 : merge thunk1'' (4 : merge thunk2' (8 : thunk3')
2 : 3 : merge (5 : thunk1''') (4 : merge thunk2' (8 : thunk3')
2 : 3 : 4 : merge (5 : thunk1''') (merge thunk2' (8 : thunk3')
2 : 3 : 4 : merge (5 : thunk1''') (merge (9 : thunk2'') (8 : thunk3')
2 : 3 : 4 : merge (5 : thunk1''') (8 : merge (9 : thunk2'') thunk3')
2 : 3 : 4 : 5 : merge thunk1''' (8 : merge (9 : thunk2'') thunk3')
...
直觀地,只有那些需要的值才被評估。 閱讀本文以獲得更好的解釋。
您還可以合並無限列表的無限列表。 最簡單的方法是:
interleave (x:xs) ys = x : interleave ys xs
primepowers = foldr1 interleave [map (^i) primes | i <- [1..]]
interleave
功能交錯兩個無限列表,例如, interleave [1,3..] [2,4..]
等於[1..]
。 因此, take 20 primepowers
給你[2,4,3,8,5,9,7,16,11,25,13,27,17,49,19,32,23,121,29,125]
。 但是此列表是無序的,我們可以做得更好。
[map (^i) primes | i <- [1..]]
[map (^i) primes | i <- [1..]]
減小為
[[2,3,5...]
,[4,9,25...]
,[8,27,125...]
...
]
我們具有先決條件,即在每個第n個列表中都有比第(n + 1)個列表的頭要小的元素。 我們可以從第一個列表中提取此類元素( 2
和3
小於4
),現在我們有了:
[[5,7,11...]
,[4,9,25...]
,[8,27,125...]
...
]
前提條件不成立,因此我們必須解決此問題並交換第一個列表和第二個列表:
[[4,9,25...]
,[5,7,11...]
,[8,27,125...]
...
]
現在我們提取4
並交換第一個列表和第二個列表:
[[5,7,11...]
,[9,25,49...]
,[8,27,125...]
...
]
但是前提條件不成立,因為第二個列表( 9
)中有不少於第三個列表( 8
)頭的元素。 因此,我們再次執行相同的技巧:
[[5,7,11...]
,[8,27,125...]
,[9,25,49...]
...
]
現在我們可以再次提取元素。 無限地重復該過程,可以得到有序的主要力量列表。 這是代碼:
swap xs@(x:_) xss = xss1 ++ xs : xss2 where
(xss1, xss2) = span ((< x) . head) xss
mergeAll (xs:xss@((x:_):_)) = xs1 ++ mergeAll (swap xs2 xss) where
(xs1, xs2) = span (< x) xs
primepowers = mergeAll [map (^i) primes | i <- [1..]]
例如, take 20 primepowers
數等於[2,3,4,5,7,8,9,11,13,16,17,19,23,25,27,29,31,32,37,41]
。
這可能不是獲得有序力量的最佳列表的最佳方法,但這是相當容易的一種。
編輯
查看Will Ness的答案以獲得更好的解決方案,該解決方案既容易又好。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.