跳轉到內容

高階函式

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

試卷 2 - ⇑ 函數語言程式設計基礎 ⇑

← 列表處理 高階函式



高階函式 - 一個函式,它將函式作為引數或返回一個函式作為結果,或者兩者都做


高階函式 是將一個或多個函式作為引數或返回一個函式作為結果的函式。 三個常見的高階函式是 map、filter 和 reduce/fold 函式

擴充套件:函子與非列表對映

雖然這裡面的例子是關於使用 map、filter 和 reduce/fold 函式處理列表,但其他資料型別可以用來代替列表。例如,你可以將一個函式對映到樹的元素上。在 Haskell 中,任何 函子 型別類的東西都可以用於 map 函式中。

map 函式

[編輯 | 編輯原始碼]
map 函式 - 一個高階函式,其中一個給定的函式被應用於一個給定列表的每個元素,從而產生一個新的輸出列表


map 函式被賦予一個列表和一個要應用於該列表的函式。 它將這個給定的函式應用於給定列表中的每個元素,建立一個新的列表,然後返回該列表。 另一種定義 map 的方法是,它將給定列表的每個項對映到輸出列表

.> map (+10) [1,2,3,4,5]
[11, 12, 13, 14, 15]
.> map (^2) [20, 3, 51, 6, 3, 7]
[400,9,2601,36,9,49]

.> map (++ "!") ["scary", "weird", "spooky", "kooky"]
["scary!", "weird!", "spooky!", "kooky!"]
.> map ("NOT " ++) ["scary", "weird", "spooky", "kooky"]
["NOT scary", "NOT weird", "NOT spooky", "NOT kooky"]

.> map (replicate 2) ['a', 'b', 'c']
["aa","bb","cc"]
.> map (replicate 2) ["a", "b", "c"]
[["a","a"],["b","b"],["c","c"]]

map 也可以作用於一個列表的列表,其中被對映的函式依次被應用於每個子列表。 例如,如果你想從一組列表中找到最小值,你可以寫

.> map minimum [[1, 3], [2, 7, 5],[9, 6]]
[1,2,6]

這裡要注意,我們似乎得到一個比開始時更小的列表。 雖然就每個現在只有一個專案的內部列表的長度而言這是真的,但傳遞給 map 函式的原始列表有三個專案(列表 [1, 3][2, 7, 5][9, 6]),返回的列表也有三個專案([1,2,6])。

filter 函式

[編輯 | 編輯原始碼]
filter 函式 - 一個高階函式,它返回一個給定列表中滿足給定條件的元素


filter 函式被傳遞一個列表和過濾條件,它將過濾條件應用於列表,返回一個匹配過濾條件的專案列表。 過濾條件有時被稱為 謂詞函式,其中將 TRUE 或 FALSE 返回從將條件應用於原始列表的每個元素。 在 Haskell 中,這是使用 filter 命令執行的,例如

.> let list1 = [1,2,3,4,5,6,7,8,9]
.> filter (>5) list1

這檢視 list1 中的每個專案,並檢查它們是否大於 5。 將那些大於 5 的設定為True,而那些小於 5 的設定為False

[1,2,3,4,5,6,7,8,9]
[F,F,F,F,F,T,T,T,T]

它返回所有為True 的專案列表

[6,7,8,9]

使用 oddeven 命令的其他 filter 示例

.> let list1 = [1,2,3,4,5,6,7,8,9]
.> let list2 = filter even list1 
.> list2
[2, 4, 6, 8]
.> filter odd list1 
[1, 3, 5, 7, 9]
.> filter odd list2
[]

其他程式語言可能沒有 filter 命令,但可能透過使用 select caseswitchif then else 來實現類似的輸出。

reduce 或 fold 函式

[編輯 | 編輯原始碼]
reduce/fold 函式 - 一個高階函式,它遞迴地將一個函式應用於一個列表的元素,直到該列表為空


reduce 或 fold 函式作為輸入接受一個列表、一個要應用於列表的函式和一個起始值。 有兩種型別的 fold 命令,foldlfoldr,這裡面的例子使用的是 foldl

fold 將給定的函式應用於列表的第一個元素(從左到右開始)和起始值,然後將 fold 命令應用於此的結果和列表的其餘部分。 它遞迴地執行此操作,直到 fold 命令被應用於一個空列表,此時它返回計算出的值。 另一種描述方式,來自 Haskell wiki

-- [] represents the empty list, 
-- and (x:xs) represents the list starting with x and where the rest of the list is xs. 
-- if the list is empty, the result is the initial value; else
-- we recurse immediately, making the new initial value the result
-- of combining the old initial value with the first element.
rule 1. foldl f z []     = z 
rule 2. foldl f z (x:xs) = foldl f (f z x) xs

為了看到它的實際效果,請檢視 haskell 命令 foldl (*) 1 [1,2,3,4],此程式碼將遞迴地將列表中的元素乘在一起

foldl (*) 1 [1,2,3,4]

這與規則 2. 匹配。 f = *,z = 1,x = 1,xs = [2,3,4]。 由於命令右側的列表 xs 不是空的,因此我們將執行以下操作

foldl f z (x:xs) = foldl f (f z x) xs

這將進行下一次呼叫

foldl (*) (* 1 1) [2,3,4]

列表 xs 不是空的,因此我們將再次應用規則 2.,其中 f = *,z = * 1 1,x = 2,xs = [3,4]。 下一次呼叫是

foldl (*) (* (* 1 1) 2) [3,4]

列表 xs 不是空的,因此我們將再次應用規則 2.,其中 f = *,z = * (* 1 1) 2,x = 3,xs = [4]。 下一次呼叫是

foldl (*) (* (* (* 1 1) 2) 3) [4]

列表 xs 仍然不是空的,因此我們將再次應用規則 2.,其中 f = *,z = * (* (* 1 1) 2) 3,x = 4,xs = []。 因此,下一次 foldl 呼叫是

foldl (*) (* (* (* (* 1 1) 2) 3) 4) []

列表 xs 現在是空列表 []。 這與規則 1. 匹配,並返回 z

(* (* (* (* 1 1) 2) 3) 4)

我們可以將 波蘭表示法(例如 * 1 1)改寫為 中綴表示法(例如 1 * 1),並計算出括號,從最裡面的括號開始

((((1*1)*2)*3)*4)
(((1*2)*3)*4)
((2*3)*4)
(6*4)
24

使用中綴表示法的更簡潔的加總示例

foldl (+) 0 [1,2,3,4]
foldl (+) (0+1) [2,3,4]
foldl (+) ((0+1)+2) [3,4]
foldl (+) (((0+1)+2)+3) [4]
foldl (+) ((((0+1)+2)+3)+4) []
((((0+1)+2)+3)+4)
((((1)+2)+3)+4)
(((3)+3)+4)
((6)+4)
10


考試問題

什麼是高階函式?

答案

一個函式,它將函式作為引數或返回一個函式作為結果,或者兩者都做


Haskell 命令 filter (>5) [32, 32, 1, 3, 5, 31, 3, 5, 212] 的輸出是什麼?

答案

[32, 32, 31, 212]

Haskell 命令 filter (odd) [32, 32, 1, 3, 5, 31, 3, 5, 212] 的輸出是什麼?

答案

[1, 3, 5, 31, 3, 5]


Haskell 命令 map ('+' :) ["good", "excellent", "brill"] 的輸出是什麼?

答案

["+good","+excellent","+brilliant"] 注意:在 '+' 運算子上使用單引號意味著我們可以使用前置命令。 它將 '+' 視為單個專案。 此程式碼不能與 "+" 一起使用,因為它將 "+" 視為一個列表,而前置命令無法處理列表作為第一個專案。


Haskell 命令 map (+5) (filter (<30) [29, 31, 30]) 的輸出是什麼?

答案

[34]


foldl (+) 6 [9, 2, 13] 的結果是什麼?

答案

30


foldl (*) 1 [2, 3, 4] 的結果是什麼?

答案

24


foldl (^) 1 [2, 3, 4] 的結果是什麼?

答案

1

foldl (^) 2 [2, 1, 2, 1] 的結果是什麼?

答案

16


寫下執行 foldl (^) 2 [3, 2, 1] 所涉及的所有 foldl 函式呼叫

答案

foldl (^) 2 [3, 2, 1]
foldl (^) (2^3) [2, 1]
foldl (^) ((2^3)^2) [1]
foldl (^) (((2^3)^2)^1) []
(((2^3)^2)^1)
(((8)^2)^1)
((64)^1)
64


描述一等公民和高階函式之間的區別

答案

一等公民是一個可以作為引數傳遞或由函式返回的物件。 高階函式可以接受另一個函式作為引數。


華夏公益教科書