深入探討 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
為List
type 的 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 -> bool
List.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 的基本功