簡體   English   中英

Haskell-具有無限列表的列表理解

[英]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,...]因為我們仍然有一個無限列表,或者由於其他原因?

現在到foldrfoldr從后面開始評估。 在這里,特別要求最右邊的元素,它是一個無限列表[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) primesps_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強制評估thunk1merge thunk2 (merge thunk3 []) ,它們的評估結果為弱頭法線形式(whnf)。 強制merge thunk2 (merge thunk3 [])會導致強制thunk2merge 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')

現在mergethunk1' ,但不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)個列表的頭要小的元素。 我們可以從第一個列表中提取此類元素( 23小於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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM