跳轉到內容

Haskell/Solutions/列表 III

來自華夏公益教科書,開放的書籍,開放的世界

← 返回列表 III

練習
  1. 以遞迴方式定義以下函式(類似於上面sumproductconcat的定義),然後將它們轉換為摺疊
    • and :: [Bool] -> Bool,如果布林值的列表全部為 True,則返回 True,否則返回 False。
    • or :: [Bool] -> Bool,如果布林值的列表中任何一個為 True,則返回 True,否則返回 False。
  2. 使用foldl1foldr1定義以下函式
    • maximum :: Ord a => [a] -> a,返回列表中的最大元素(提示:max :: Ord a => a -> a -> a返回兩個值的較大者)。
    • minimum :: Ord a => [a] -> a,返回列表中的最小元素(提示:min :: Ord a => a -> a -> a返回兩個值的較小者)。
  3. 使用摺疊(哪個?)來定義reverse :: [a] -> [a],它返回一個元素順序相反的列表。
請注意,所有這些都是 Prelude 函式,因此當你需要它們時,它們將始終近在咫尺。(另外,這意味著你將想要在 GHCi 中測試你的答案時使用略微不同的名稱。)
and [] = True
and (x:xs) = x && and xs

--As a fold
and = foldr (&&) True

or [] = False
or (x:xs) = x || or xs

--As a fold
or = foldr (||) False

maximum :: Ord a => [a] -> a  -- omitting type declaration results in "Ambiguous type variable" error
maximum = foldr1 max --foldl1 also works, but not if you want infinite lists. Not that you can ever be sure of the maximum of an infinite list.
minimum :: Ord a => [a] -> a
minimum = foldr1 min --Same as above.

--Using foldl'; you will want to import Data.List in order to do the same.
reverse :: [a] -> [a]
reverse = foldl' flippedCons []
    where
    flippedCons xs x = x : xs
練習
編寫你自己的scanr定義,首先使用遞迴,然後使用foldr。對scanl做同樣的事情,首先使用遞迴,然後使用foldl
-- with recursion
scanr2 step zero [] = [zero]
scanr2 step zero (x:xs) = step x (head prev):prev
  where prev = scanr2 step zero xs

scanl2 step zero [] = [zero]
scanl2 step zero (x:xs) = zero : scanl2 step (step zero x) xs

-- An alternative scanl with poorer performance. The problem is that
-- last and init, unlike head and tail, must run through the entire
-- list, and (++) does the same with its first argument.
scanl2Slow step zero [] = [zero]
scanl2Slow step zero xs = prev ++ [step (last prev) (last xs)]
  where prev = scanl2Slow step zero (init xs)

--with fold
scanr3 step zero xs = foldr step' [zero] xs
  where step' x xs = step x (head xs):xs

scanl3 step zero xs = (reverse . foldl step' [zero]) xs
  where step' xs x = step (head xs) x:xs

-- This implementation has poor performance due to (++), as explained
-- for scanl2Slow. In general, using (++) to append to a list
-- repeatdely is not a good idea.  
scanl3Slow step zero xs = foldl step' [zero] xs
  where step' xs x = xs ++ [step (last xs) x]

在上面高效的scanr實現中,我們使用headtail來避免必須在 where 子句中分解列表引數並進行模式匹配,只是為了在右側重新組裝它。在模式匹配章節中,我們將看到 as 模式如何讓我們在不需要head的情況下實現同樣的效果。

練習

定義以下函式

  • factList :: Integer -> [Integer],它返回一個從 1 到其引數的階乘列表。例如,facList 4 = [1,2,6,24]
更多內容待新增
factList n = scanl1 (*) [1..n]
factList' n = scanl (*) 1 [2..n]

過濾和列表推導

[編輯 | 編輯原始碼]
練習

編寫一個returnDivisible :: Int -> [Int] -> [Int]函式,該函式過濾一個整數列表,只保留可以被第一個引數中傳遞的整數整除的數字。對於整數 x 和 n,如果(mod x n) == 0,則 x 可以被 n 整除(注意,偶數測試是這種情況的特例)。

returnDivisible :: Int -> [Int] -> [Int]
returnDivisible n xs = [ x | x<-xs , (mod x n) == 0 ]

--or, if you prefer to use filter...
returnDivisible :: Int -> [Int] -> [Int]
returnDivisible n = filter (\x -> (mod x n) == 0)
練習
  • 使用列表推導語法和適當的保護(過濾器)編寫一個choosingTails :: [[Int]] -> [[Int]]函式,對於空列表,返回一個在頭部大於 5 之後尾部的列表
    choosingTails  [[7,6,3],[],[6,4,2],[9,4,3],[5,5,5]]
    -- [[6,3],[4,2],[4,3]]
    
  • 保護的順序重要嗎?你可以透過使用前面練習的函式來找出答案。
choosingTails :: [[Int]] -> [[Int]]
choosingTails ls = [ tail l | l <- ls, not (null l), head l > 5 ]

列表推導中的布林條件按順序計算,因此如果你交換了條件,如下所示

choosingTails ls = [ tail l | l <- ls, head l > 5, not (null l) ]

如果其中一個l原來是一個空列表,程式將嘗試對其應用 head,這會導致錯誤。首先放置not (null l)會導致程式在遇到空列表時,短路條件 - 也就是說,忽略第二個條件,因為第一個條件為假足以拒絕列表 - 因此避免執行 head [] 以及隨之而來的錯誤。或者,你也可以透過模式匹配來過濾。只有非空列表才匹配(l:ll),它被第一個元素l以及可能為空的尾部ll劃分

choosingTails ls = [ ll | (l:ll) <- ls, l > 5 ]    -- filter by pattern match
練習

在這部分,我們已經看到列表推導本質上是filtermap的語法糖。現在反向操作,並使用列表推導語法定義filtermap的替代版本。

alternativeFilter :: (a -> Bool) -> [a] -> [a]
alternativeFilter cond xs = [ x | x<-xs, cond x ]

alternativeMap :: (a -> b) -> [a] -> [b]
alternativeMap f xs = [ f x | x<-xs ]
--no need for any condition, as all x will be accepted
練習

重寫doubleOfFirstForEvenSeconds使用filtermap而不是列表推導。

doubleOfFirstForEvenSeconds' :: [(Int, Int)] -> [Int]
doubleOfFirstForEvenSeconds ps =
    let f pair = 2 * (fst pair)
        g pair = isEven (snd pair)
    in map f (filter g ps)

--isEven, naturally, remains the same.
isEven :: Int -> Bool
isEven n = (mod n 2 == 0)

doubleOfFirstForEvenSeconds' :: [(Int, Int)] -> [Int]
doubleOfFirstForEvenSeconds' ps = map ((2*) . fst) (filter (isEven . snd) ps)
華夏公益教科書