深入探討 F# List Module 所提供的 Function (A ~ F)
- Version
- List.allPairs
- List.append
- List.average
- List.averageBy
- List.choose
- List.chunkBySize
- List.collect
- List.compareWith
- List.concat
- List.contains
- List.countBy
- List.distinct
- List.distinctBy
- List.empty
- List.exactlyOne
- List.except
- List.exists
- List.exists2
- List.filter
- List.find
- List.findBack
- List.findIndex
- List.findIndexBack
- List.fold
- List.folder2
- List.foldBack
- List.foldBack2
- List.forall
- List.forall2
- Conclusion
- Reference
F# 的 List module 提供眾多 List 常用的 Higher Order Function,要能發揮 FP 的威力,首先必須能活用內建 function。
本文探討英文字母 A ~ F 開頭的 function。
Version
macOS High Sierra 10.13.3
.NET Core SDK 2.1.101
Rider 2017.3.1
F# 4.1
List.allPairs
將兩個 list 的 element 取 cartesian product
list1 : 'T1 list -> list2 : 'T2 list -> ('T1 * 'T2) list
1 | [ 1 .. 3 ] |
兩個 list 可以不同型別,反正最後會以 tuple 形式呈現。
List.append
傳入兩個 list,第 2 個 list 會 append 到第 1 個 list 之後,相當於
list1@list2
list1 : 'T list -> list2 : 'T list -> 'T list
1 | [ 1 .. 3 ] |
List.average
計算 list 中所有 element 的平均值
list : 'T list -> T
1 | [ 1.0 .. 10.0 ] |
List.averageBy
將 list 所有 element 先經過 project funtion 計算後,再取平均值
project : ('T -> 'U) -> list : 'T list -> 'U
1 | [ 1 .. 3 ] |
[ 1..3 ] 無法透過 List.average() 取平均值,先透過 List.averageBy() 轉成 float,再取平均值。
1 | [ 1.0 .. 3.0 ] |
先將 [1.0; 2.0; 3.0] project 成 [1.0; 4.0; 9.0] 之後,再取平均值。
List.choose
將 list 所有 element 經過 chooser function 塞選過,傳回 option 型別的 list
chooser : ('T -> 'U option) -> list : 'T list -> 'U list
1 | [ 1 .. 10] |
先找出偶數,在每個 element + 1。
傳統須先使用 List.filter() 找出 偶數,在用 List.map() 計算 +1。
1 | let chooser elm = |
若使用 List.choose(),則可在 chooser function 使用 Pattern Matching:
- 在 pattern 與 when 完成
List.filter()該做的事情 - 在
->之後完成List.map()該做的事情
也就是 List.choose() = List.filter() + List.map()。
List.chunkBySize
將 list 根據 size 切成小 list
chunkSize : int -> list : 'T list -> 'T list list
1 | [ 1 .. 10] |
注意 List.chunkBySize() 的回傳型態為 'T list list,表示回傳為 list,其 element 型態為 'T list。
List.collect
將 list 所有 element 經過 mapping function 轉換,其中 mapping function 會回傳新的 list,最後再將所有 list 攤平成新的 list
mapping : ('T -> 'U list) -> list : 'T list -> 'U list
1 | let mapping elm = [ |
mapping() 會為每個 element 產生新的 list,最後再打平成單一 list。
Q :
List.collect()與List.map()有何差異?
相同
- 都需使用 mapping function
相異
List.map()的 mapping function 傳回 value;但List.collect()的 mapping function 傳回 listList.map()產生的 list ,其 element 個數與原本 list 一樣大;但List.collect()產生的 list,其 element 個數大於等於原本 list
List.compareWith
自行定義 comparer function 比較兩個 list
comparer : ('T -> 'T -> int) -> list1 : 'T list -> list2 : 'T list -> int
1 | let list1 = [ 4 .. 6 ] |
comparer() 定義 element 間的比較方式:
- 若
elm1大於elm2,則傳回正數 - 若
elm1等於elm2,則傳回0 - 若
elm1小於elm2,則傳回負數
若簡單的比較,可使用數學的
兩數相減即可
List.compareWith() 將回傳第一個 comparer function 所傳回的 非零之數,可使用 Pattern Matching 對回傳值做判斷。
List.concat
將 n 個 list 合併為 1 個 list
lists : seq<'T list> -> 'T list
1 | let list1 = [ 1 .. 3 ] |
List.concat() 可合併 n 個 list,但必須將 n 個 list 包在同一個 list 傳入。
Q:
List.append()與List.map()有何差異?
List.append()只能合併 2 個 listList.concat()能合併 n 個 list
List.contains
判斷 list 的 element 是否包含某一 value
value : 'T -> source : 'T list -> bool
1 | [ 1 .. 3 ] |
List.countBy
將 list 根據 projection function 分組後,各自計算分組後 element 個數,類似 SQL 的
GROUP BY+count(*)
projection : ('T -> 'Key) -> list : 'T list -> ('Key * int) list
1 | [ 1 .. 100 ] |
將 [ 1 .. 100 ] % 3 分成三組,餘數為 1 有 34 個,餘數為 2 有 33 個,餘數為 0 有 33 個。
List.distinct
將 list 中重複的 element 去除
list : 'T list -> 'T list
1 | [ 1 ; 2; 2; 3] |
List.distinctBy
將 list 所有 element 經過 projection function 產生新 key 後,將重複 key 的去除
projection ('T -> 'Key) -> list : 'T list -> 'T list
1 | [ 1 .. 100 ] |
經過 projection function fun elm -> elm % 3 之後的 key 為 [ 0; 1; 2; 0; 1; 2 …],其不重複的 key 為 [ 0; 1; 2 ],但其對應的 element 為 [ 1; 2; 3]。
Q:
List.distinctBy()與List.map()後再List.distinct()有什麼不同?
1 | [ 1 .. 100] |
經過 mapping function fun elm -> elm % 3 之後的 element 為 [ 0; 1; 2; 0; 1; 2 …],其不重複的 element 為 [ 0; 1; 2 ]。
- Project function 是產生新的 key;mapping function 是產生新的 element
List.distinctBy()是排除重複的 key;List.map()+List.distinct()是排除重複的 element
List.empty
由泛型傳回指定 element 型別的 empty list
empty<'T> : 'T list
1 | List.empty<int> |
Q : List.Empty 與 List.empty 有何差異?
1 | type myListType = int list |
相同
- 均回傳 empty list
相異
List.Empty為Listtype 的 static method;List.empty()定義在 List moduleList.Empty須先定義 type;List.empty()可由泛型直接指定 element type
List.exactlyOne
從 list 回傳單一 value
list : 'T list -> 'T
1 | [ 1 ] |
若 list 的 Length 不為 1,將拋出 ArgumentException。
List.except
從 list 中排出另一個 list 中的 element
seq<'T> -> list : 'T list -> 'T list
1 | [ 1 .. 10 ] |
List.exists
判斷符合 predicate function 條件的 element 是否存在
predicate : ('T -> bool) -> list : 'T list -> bool
1 | [ 1 .. 10 ] |
List.exists2
判斷兩個 list 是的相同位置,至少有一個 element 相等,須自行提供 predicate function 作為判斷的依據
predicate : ('T1 -> 'T2 -> bool) -> list1 : 'T1 list -> list2 : 'T2 list -> bool
1 | let list1 = [ 1; 2; 3 ] |
[ 1; 2; 3 ] 與 [ 3; 2; 1 ] 中,至少第 1 個 element 都是 2,只要有一個 element 是相等的就為 true,否則為 false。
若 list1 與 list2 的 Length 不同,會拋出 ArgumentException。
List.filter
過濾出符合 predicate function 條件的 list
predicate : ('T -> bool) -> list : 'T list -> 'T list
1 | [ 1 .. 10 ] |
List.find
找出 list 中符合 predicate function 條件的第一個 element
predicate : ('T -> bool) -> list : 'T list -> 'T
1 | [ 1 .. 10 ] |
若找不到 element,會拋出 KeyNotFoundException。
Q:
List.find()與List.pick()有何差異?
相同
都用來找資料
找不到資料都會拋出
KeyNotFoundException
相異
List.find()的 predicate function 為'T -> boolList.pick()的 chooser function 為'T -> 'U option
若 fuction 回傳為
option,則適合用List.pick(),否則使用List.find()
List.find()與List.tryFind()有何差異?
相同
- 都使用 predicate function 為搜尋條件
相異
List.find()傳回'T;而List.tryFind()傳回'T option- 若找不到資料,
List.find()會拋出KeyNotFoundException;但List.tryFind()只會回傳None
實務上建議使用
List.tryFind()取代List.find(),較為安全
List.findBack
從 list 尾部找出符合 predicate function 條件的第一個 element
predicate : ('T -> bool) -> list : 'T list -> 'T
1 | [ 1 .. 10 ] |
若找不到 element,會拋出 KeyNotFoundException。
Q:
List.findBack()與List.tryFindBack()有何差異?
相同
- 都使用 predicate function 為搜尋條件
相異
List.findBack()傳回'T;而List.tryFindBack()傳回'T option若找不到資料,
List.findBack()會拋出KeyNotFoundException;但List.tryFindBack()只會回傳None
實務上建議使用
List.tryFindBack()取代List.findBack(),較為安全
List.findIndex
找出 list 中符合 predicate function 條件的第一 element 的 index
predicate : ('T -> bool) -> list : 'T list -> int
1 | [ 1 .. 10 ] |
若找不到 element,會拋出 KeyNotFoundException。
Q:
List.findIndex()與List.tryFindIndex()有何差異?
相同
- 都使用 predicate function 為搜尋條件
相異
List.findIndex()傳回int;而List.tryFindIndex()傳回int option若找不到資料,
List.findIndex()會拋出KeyNotFoundException;但List.tryFindIndex()只會回傳None
實務上建議使用
List.tryFindIndex()取代List.findIndex(),較為安全
List.findIndexBack
從 list 尾部找出符合 predicate function 條件的第一 element 的 index
predicate : ('T -> bool) -> list : 'T list -> int
1 | [ 1 .. 10 ] |
若找不到 element,會拋出 KeyNotFoundException。
Q:
List.findIndexBack()與List.tryFindIndexBack()有何差異?
相同
- 都使用 predicate function 為搜尋條件
相異
List.findIndexBack()傳回int;而List.tryFindIndexBack()傳回int option若找不到資料,
List.findIndexBack()會拋出KeyNotFoundException;但List.tryFindIndexBack()只會回傳None
實務上建議使用
List.tryFindIndexBack()取代List.findIndexBack(),較為安全
List.fold
List.reduce()的進階版,將 list 中每個 element 的值經過 folder function 累加
folder : ('State -> 'T -> 'State) -> state : 'State -> list : 'T list -> 'State
1 | [ |
Q :
List.fold()與List.reduce()有何差異?
1 | [ 4; 5; 3; 2 ] |
相同
- 都是
累加
相異
List.fold()可以設定初始值;List.reduce()不能設定初始值List.fold()可以不同型別,如(string * int) list-> int;List.reduce()從頭到尾必須相同型別,如int lint -> int
List.folder2
將兩個 list 透過 folder function 累加
folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> list1 : 'T list -> list2 : 'T list -> 'State
1 | let list1 = [ |
List.foldBack
List.reduce()的進階版,將 list 中每個 element 的值從尾部經過 folder function 累加
folder : ('T -> 'State -> State) -> list : 'T list -> state : 'State -> 'State
1 | let list1 = [ 1 .. 5 ] |
數值累加,從頭加到尾,或尾加到頭都一樣,有有些運算,不同方向累加結果會不一樣,此時就必須使用 List.foldBack() 了。
注意 signature 也不太一樣。
List.foldBack2
將兩個 list 透過 folder function 從尾部累加
folder : ('T1 -> 'T2 -> 'State -> 'State) -> list1 : 'T1 list -> list2 : 'T2 list -> state : 'State -> 'State
1 | let list1 = [ 1 .. 5 ] |
List.forall
判斷是否 list 的所有 element 都通過 predicate function 條件
predicate : ('T -> bool) -> list : 'T list -> bool
1 | [ 0 .. 2 .. 10 ] |
List.forall2
判斷是否兩個 list 的所有 element 都通過 predicate function 條件
predicate : ('T1 -> 'T2 -> bool) -> list1 : 'T1 list -> list2 : 'T2 list -> bool
1 | let list1 = [ 1 .. 10] |
Conclusion
- List module 所提供的 function 都必須非常熟練,因為這些都是常用的 Higher Order Function,算是 FP 的基本功