[英]Haskell Length function implementation
我正在學習 Haskell 編程,並試圖了解列表的工作原理,因此我嘗試編寫兩個可能的length
函數:
myLength :: [a] -> Integer
myLength = foldr (\x -> (+) 1) 0
myLength1 :: [a] -> Integer
myLength1 [] = 0
myLength1 (x:xs) = (+1) (myLength1 xs)
哪一個更好?
從我的角度來看, myLength1
更容易理解,並且在列表上操作看起來很自然。
另一方面, myLength
更短,並且不使用遞歸; 這是否意味着myLength
比myLength1
運行得更快?
請記住foldr
這種“偽實現”:
foldr :: function -> initializer -> [a] -> b
foldr _ i [] = i
foldr f i (x:xs) = x `f` (foldr f i xs)
現在我們有了你的代碼
myLength :: [a] -> Integer
myLength = foldr (\x -> (+) 1) 0
myLength1 :: [a] -> Integer
myLength1 [] = 0
myLength1 (x:xs) = (+1) (myLength1 xs)
由於foldr
本身也是遞歸的,因此 myLength1 和 myLength 幾乎相同,但在第一種情況下,遞歸調用由 foldr 完成,而不是由您自己明確完成。 他們應該在同一時間跑。
兩個函數都做同樣的事情:foldr 使用遞歸,最終會以類似於直接遞歸函數的方式執行。 可能有人會說 foldr 版本更簡潔(一旦你習慣了它們,高階函數通常比直接遞歸更具可讀性)。
但是這兩個函數非常糟糕:它們最終都會構建一個很大的 thunk(一個未評估的值) 1 + (1 + (1 + ... + 0)..))
這將占用大量內存( O (n) space ) 並且會減慢評估速度。 為避免這種情況,您應該從列表的開頭開始添加 1,如下所示:
betterLength xs = go 0 xs
where
go n [] = n
go n (_:xs) = n `seq` go (n+1) xs
seq
確保在遞歸調用 go 函數之前評估 n ,因此沒有+1
累積。 使用BangPatterns
擴展,你可以這樣寫:
betterLength xs = go 0 xs
where
go n [] = n
go !n (_:xs) = go (n+1) xs
也可以使用 fold 執行此版本:
betterLength = foldl' (\n _ -> n + 1) 0
其中foldl'
從L EFT就是嚴格(”)。
使用foldr
,它可以實現為:
length' xs = foldr (\_ n -> 1 + n) 0 xs
解釋:
lambda 函數(\\_ x -> n + 1)
每次有一個元素時都會將累加器加一。 例如:
lenght' [1..4]
將應用為: 1 + ( 1 + ( 1 + ( 1 + 0)))
回想一下foldr
是這樣定義的:
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.