[英]How to return the first n-1 elements of a list of length n in Haskell?
[英]Haskell - Sum up the first n elements of a list
我是 Haskell 的新手。
假設我想用自己生成的函數總結列表的前n 個元素。 我不知道如何用 Haskell 做到這一點。 我只知道如何總結整個給定的列表,例如
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs
為了總結一個列表的前n 個元素,例如
從
[1..10]
取出前5 個數字,即1+2+3+4+5 = 15
我以為我可以做這樣的事情:
sumList :: Int -> [Int] -> Int
sumList take [] = 0
sumList take (x:xs) = x + take $ sumList xs
但它不起作用......怎么了?
所以你知道如何總結列表中的數字,
sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs
如果該列表中的元素不超過 5 個,並且您確實打算對參數列表中的元素求和不超過 5 個,則該函數甚至會返回正確的結果。 讓我們通過重命名這個函數來明確我們的期望,
sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements [] = 0
sumUpToFiveElements (x:xs) = x + sumUpToFiveElements xs
對於長度超過五個的列表,它不會返回正確的結果,但至少名稱是正確的。
我們能解決這個問題嗎? 我們可以數到 5 嗎? 我們可以在沿着輸入列表前進的同時數到 5 嗎?
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
這當然還是不對的。 我們現在計數,但出於某種原因我們忽略了計數器。 如果我們想要不超過 5 個元素,什么時候對計數器做出反應? 讓我們試試counter == 5
:
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5 [] = 0
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
但是為什么我們要求列表在達到 5 時也為空? 我們不要那樣做:
sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5 _ = 0 -- the wildcard `_` matches *anything*
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs
成功! 我們現在在達到 5 時停止計數! 更多,我們也停止求和!!
等等,但是counter
的初始值是多少? 我們沒有指定它,所以我們函數的用戶(也就是我們自己)很容易出錯並使用不正確的初始值。 順便說一句,什么是正確的初始值?
好的,讓我們這樣做:
sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements xs = go 1 xs -- is 1 the correct value here?
where
go counter _ | counter == 5 = 0
go counter [] = 0
go counter (x:xs) = x + go (counter + 1) xs
現在我們沒有那個使我們的定義變得如此脆弱、如此容易出現用戶錯誤的無關論點。
現在是重點:
概括! (通過用符號值替換示例值;將5
更改為n
)。
sumUpToNElements :: Int -> [Int] -> Int
sumUpToNElements n xs = .......
........
完畢。
還有一句忠告:不要在剛開始學習 Haskell 時使用$
。 使用顯式括號。
sumList take (x:xs) = x + take $ sumList xs
被解析為
sumList take (x:xs) = (x + take) (sumList xs)
這將兩個不相關的數字相加,然后將結果用作要調用的函數,以(sumList xs)
作為參數(換句話說,這是一個錯誤)。
如果您使用顯式括號,您可能不會那樣寫。
好吧,您應該使用參數限制值的數量(最好不要take
,因為這是Prelude
一個函數),從而限制數量。
您的代碼中的這種限制顯然是take $ sumList xs
,這很奇怪:在您的函數中take
是一個Int
,而$
基本上會將您的語句寫入(x + take) (sumList xs)
。 因此,您顯然希望使用(x + take)
(一個Int
)作為函數,並將sumList xs
作為參數來執行函數應用程序。 但是Int
不是函數,因此它不進行類型檢查,也不包含任何限制數字的邏輯。
所以基本上我們應該考慮三種情況:
0
的空列表;0
; 和0
,在這種情況下,我們將頭部添加到從尾部少取一個元素的總和中。所以一個簡單的映射是:
sumTakeList :: (Integral i, Num n) => i -> [n] -> n
sumTakeList _ [] = 0
sumTakeList t (x:xs) | t <= 0 = 0
| otherwise = x + sumTakeList (t-1) xs
但是你不需要自己寫這樣的邏輯,你可以結合take :: Int -> [a] -> [a]
內置函數sum :: Num a => [a] -> a
函數:
sumTakeList :: Num n => Int -> [n] -> n
sumTakeList t = sum . take t
現在,如果您需要對前五個元素求和,我們可以將其設為特例:
subList5 :: Num n => [n] -> n
sumList5 = sumTakeList 5
Hoogle 是查看哪些功能可用以及它們如何工作的絕佳資源。 這里是它的網頁上take
和文檔你想要的功能。
如您所見,名稱take
已被采用,但您可以使用它來實現此功能。
請注意,您的sumList
需要另一個參數,即要求和的元素數。 你想要的語法是這樣的:
sumList :: Int -> [Int] -> Int
sumList n xs = _ $ take n xs
_
是空白的地方,您可以自己填寫。 它是 Prelude 中的一個函數,但類型簽名有點太復雜,現在無法進入。
或者您可以遞歸地編寫它,使用兩個基本情況和第三個累加參數(通過輔助函數):
sumList :: Int -> [Int] -> Int
sumList n xs = sumList' n xs 0 where
sumList' :: Int -> [Int] -> Int -> Int
sumList' 0 _ a = _ -- A base case.
sumList' _ [] a = _ -- The other base case.
sumList' m (y:ys) a = sumList' _ _ _ -- The recursive case.
在這里,等號左邊的_
符號應該保留在那里,這意味着模式保護忽略了該參數,但右邊的_
符號是空白,供您自己填寫。 同樣,GHC 會告訴您填充孔所需的類型。
這種尾遞歸函數是 Haskell 中非常常見的模式; 您想確保每次遞歸調用都使您更接近基本情況。 通常,這意味着從計數參數中減去 1 來調用自身,或者將列表參數的尾部作為新的列表參數來調用自身。 在這里,您想同時進行。 當您遞歸調用函數本身時,不要忘記更新您的運行總和a
。
這是一個簡短但甜蜜的答案。 你真的很親近。 考慮以下:
take
參數告訴您需要總結多少個元素,因此如果您執行sumList 0 anything
您應該總是得到0
因為您沒有使用任何元素。 如果您想要前n
元素,請將第一個元素添加到總數中並計算接下來的n-1
元素的總和。
sumList 0 anything = 0 sumList n [] = 0 sumList n (e:es) = e + sumList (n-1) e
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.