iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 21
0
Software Development

新手也能懂的 Kotlin Collection 賞玩門道系列 第 21

第二十一天:Collection 操作之聚合

在做 Collection 操作時,還有一個很常見的情境,就是要將 Collection 內所有元素做運算,最後回傳「一個值」,這種操作我們就統一稱做聚合(Aggregation)。在這個章節裡,我們就要來討論這一系列的操作。

數值計算操作

這種聚合操作最常見的就是拿來計算,比方說計算 Collection 裡的數量、Collection 裡所有數值的總合、平均值…等。在 Kotlin 標準函式庫裡直接採用 count()sum()average() 等在大多數程式語言裡相同的命名。

val numbers = listOf(6, 42, 10, 4)

numbers.count() // 4
numbers.sum() // 62
numbers.average() // 15.5

假如你想要指定計算總和的方式,sumBy() 可以用傳入一個 Lambda 來達成。不過 sumBy() 回傳的是 Int,假如你希望回傳的是更精準的 Double 的話,用 sumByDouble()

val numbers = listOf(5, 42, 10, 4)
numbers.sumBy { it * 2 } // 122
numbers.sumByDouble { it.toDouble() / 2 } // 30.5 

數值比較操作

聚合操作裡也很常見的就是做數值比較,比方說在 Collection 裡找出最大值(maxOrNull())或是最小值(minOrNull())。

val numbers = listOf(6, 42, 10, 4)
numbers.maxOrNull() // 42
numbers.minOrNull() // 4

當然,一如往常的,這兩個 method 也有可以傳入 Lambda 自行定義行為的 minByOrNull()maxByOrNull(),假如找出最大/最小值的邏輯需要客製化的話,別忘了用這個版本。

val numbers = listOf(5, 42, 10, 4)
val min3Remainder = numbers.minByOrNull { it % 3 } // 42
val max3Remainder = numbers.maxByOrNull { it % 3 } // 5

另外,也有 maxWithOrNull()minWithOrNull() 這兩個 method 可以用 Comparator 物件做為比較基準。

val strings = listOf("one", "two", "three", "four")
val longestString = strings.maxWithOrNull(compareBy { it.length }) // three
val shortestString = strings.minWithOrNull(compareBy { it.length }) // one

值得一提的是,這些 method 在許多舊版本的範例裡是都沒有 OrNull 的。不過這些 method 將在新版本裡被棄用,記得要改用比較安全的 OrNull 版本喔!

全客製聚合

假如需要更客製化的操作,那就要使用 reduce()fold() 了。這兩個 method 可以傳入一個 Lambda,在這個 Lambda 裡會接到之前所有的總合以及當前的元素兩個參數,你可以指定計算行為後回傳。而 reduce()fold() 的差別,就在於 fold() 可以指定初始值。

val numbers = listOf(5, 2, 10, 4)

val sum = numbers.reduce { sum, element -> sum + element } // 21
val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 } // 42

預設 reduce()fold() 的計算方向是從左到右,但假如你是要從右算到左的話,可以用 reduceRight()foldRight()。假如你希望 Lambda 可以拿到 Collection 的 index 的話,可以用 reduceIndexed()foldIndexed()。當然假如要反向計算的話,也有對應的 reduceRightIndexed()foldRightIndexed()

val numbers = listOf(5, 2, 10, 4)

val sumDoubledRight = numbers.foldRight(0) { element, sum -> sum + element * 2 } // 42
val sumEven = numbers.foldIndexed(0) { index, sum, element -> if (index % 2 == 0) sum + element else sum } // 15
val sumEvenRight = numbers.foldRightIndexed(0) { index, element, sum -> if (index % 2 == 0) sum + element else sum } // 15

另外要注意的是,所有的 reduce 操作遇到空 Collection 時會拋 Exception。假如希望直接回傳 null 的話,可以把所有 method 以 OrNull 結尾。

表格整理

這個章節討論的操作都有一個共同點,就是最後都只會回傳一個值。數值計算和比較非常的實用,可以省下自己用迴圈計算的功;而 reduce()fold() 則是在 Functional Programing 裡很常用到的動作。為了一覽這些 API 在不同 Collection 上的行為,以下用表格來整理本章所討論到的 method:

行為 Array List Set Map
count() 計算個數 v v v v
sum() 計算總和 v v v x
sumBy{} 依條件計算總和回傳 Int v v v x
sumByDouble{} 依條件計算總和回傳 Double v v v x
average() 求平均值 v v v x
minOrNull() 求最小值 v v v x
maxOrNull() 求最大值 v v v x
minByOrNull{} 依條件求最小值 v v v v
maxByOrNull{} 依條件求最大值 v v v v
minWithOrNull{} 依比較物件求最小值 v v v v
maxWithOrNull{} 依比較物件求最大值 v v v v
reduce{} 依 Lambda 聚合 v v v x
reduceOrNull{} 依 Lambda 聚合若空則回傳 null v v v x
reduceRight{} 由右向左依 Lambda 聚合 v v x x
reduceRightOrNull{} 由右向左依 Lambda 聚合若空則回傳 null v v x x
reduceIndexed{} 依 Lambda 聚合(有 index) v v v x
reduceIndexedOrNull{} 依 Lambda 聚合若空則回傳 null (有 index) v v v x
reduceRightIndexed{} 由右向左依 Lambda 聚合(有 index) v v x x
reduceRightIndexedOrNull{} 由右向左依 Lambda 聚合若空則回傳 null(有 index) v v x x
fold(init) {} 依 Lambda 以初始值聚合 v v v x
foldRight(init) {} 由右向左依 Lambda 以初始值聚合 v v x x
foldIndexed(init) {} 依 Lambda 以初始值聚合(有 index) v v v x
foldRightIndexed(init) {} 由右向左依 Lambda 以初始值聚合(有 index) v v x x

參考資料


上一篇
第二十天:Collection 操作之轉換
下一篇
第二十二天:Collection 差異及相互轉型
系列文
新手也能懂的 Kotlin Collection 賞玩門道31

尚未有邦友留言

立即登入留言