iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 13
0

在前面的章節裡有提到如何從 Collection 裡取值,不過在該章節裡我們討論的都是如何取「單一」值,但實務上常常要取的是多個值或是一段範圍的值,這時就要使用到 Collection 裡跟截取有關的功能了。在這個章節裡就要跟大家討論這些 API。

「拿」與「丟」

假如需要從 Collection 的前、後「拿」幾個元素出來的話,用 take 開頭的 method 非常直覺,想要從前面拿就用 take(n)、想要從後面拿就用 takeLast(n),參數 n 代表要拿幾個。要注意的是,takeLast(n) 是從後面拿 n 個,不是從後面開始拿 n 個,所以 Collection 仍會維持原本的順序,不會反轉。

val numbers = listOf("one", "two", "three", "four", "five", "six")
numbers.take(3) // [one, two, three] 
numbers.takeLast(3) // [four, five, six] 

反過來的概念,假如是要從 Collection 的前、後「丟掉」幾個元素的話,用 drop 開頭的 method 即可,想從前面丟就用 drop(n)、想要從後面丟就用 dropLast(n),參數 n 代表要丟幾個。

val numbers = listOf("one", "two", "three", "four", "five", "six")
numbers.drop(1) // [two, three, four, five, six] 
numbers.dropLast(5) // [one] 

假如給定的 n 值大於 Collection 本身的長度的話,則 take() 會取出整個 Collection、而 drop() 就會清空整個 Collection 的內容。

val numbers = listOf("one", "two", "three", "four", "five", "six")
numbers.take(100) // ["one", "two", "three", "four", "five", "six"]

val numbers = listOf("one", "two", "three", "four", "five", "six")
numbers.drop(100) // []

當然,在取值的時候也用條件篩選。一如以往,只要是可以傳入條件的 method,都是透過傳入 Lambda 來實作的。takeWhile(predicate) 會以 Lambda 過濾,只要回傳 true 的就會被拿出,直到碰到第一個 false 的元素停下。假如第一個元素就是 false 的話,Collection 就會被清空。takeLastWhile(predicate) 的行為與 takeWhile(predicate) 相同,只是反過來從後面過濾。dropWhile(predicate)dropLastWhile(predicate) 就是 takeWhile(predicate)takeLastWhile(predicate) 的反向,即以 Lambda 從前面或後面過濾到第一個不滿足條件的元素停下。

val numbers = listOf("one", "two", "three", "four", "five", "six")
numbers.takeWhile { !it.startsWith('f') } // [one, two, three] 
numbers.takeLastWhile { it != "three" } // [four, five, six] 
numbers.dropWhile { it.length == 3 } // [three, four, five, six] 
numbers.dropLastWhile { it.contains('i') } // [one, two, three, four] 

截取一段範圍

假如想要從 Collection 裡取出一段範圍,可以用 slice() 把它「切」出來。只要傳入想要取出的 Range(還可以指定 Range 每一步的間隔),或傳入一個 index Collection即可。

val numbers = listOf("one", "two", "three", "four", "five", "six")    

// 給一段 Range,取出 index 介於 1、2、3 的元素
numbers.slice(1..3) // [two, three, four]

// 給一段 Range,每一步間隔 2 格,也就是 index 是 0、2、4 的元素
numbers.slice(0..4 step 2) // [one, three, five]

// 給指定的 index,依照該 index Collection 回傳元素
numbers.slice(setOf(3, 5, 0)) // [four, six, one]

切成小塊

假如你想把 Collection 切成一段一段的小塊,不必自己手動用迴圈動刀,Collection 的 chunked() 可以幫你。chunked() 有 2 種用法,一種用法很單純的傳入切塊的大小,就會回傳包含一段段 List 的 List;另一種用法是在傳入切塊大小的同時再傳入一個 Lambda 做轉型,回傳的就會轉型之後的 List 結果。

val numbers = (0..13).toList()

// 每 3 個元素切成一段
numbers.chunked(3) // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13]]

// 每 3 個元素切成一段,再把各段加總
numbers.chunked(3) { it.sum() } // [3, 12, 21, 30, 25]

以一段範圍移動

我們也可以先設定一段範圍,然後以這個範圍逐步移動來取出元素。可以想像面前有 5 個雕像,我們拿著一次只能看到 3 個雕像的窗戶,從第一個雕像逐步移動,移動時把每一步可以看到的範圍收成一個子 List,這種方式稱為 windowed()。跟 chunck() 不同,windowed() 因為預設每次移動一格,所以回傳的子 List 裡的第一個元素會是父 List 裡的每一個元素,父 List 裡的元素也會重複地出現在子 List 裡。

val numbers = listOf("one", "two", "three", "four", "five")    
numbers.windowed(3) // [[one, two, three], [two, three, four], [three, four, five]]

若上述的行為不符合需求,windowed() 也提供 3 個參數可做彈性調整。step 設定每一次移動的格數;partialWindows 則是一個 Boolean,在分段的時候,若想移除不足一段的子 List 則傳 false,反之則傳 true;若想要做轉型的話,最後一個參數可以傳入一個 Lambda。

val numbers = (1..10).toList()
numbers.windowed(3, step = 2, partialWindows = false) // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9]]
numbers.windowed(3, step = 2, partialWindows = true) // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]]
numbers.windowed(3) { it.sum() } // [6, 9, 12, 15, 18, 21, 24, 27]

切成 Pair

假如想要的切法是將前後相鄰的兩個元素切成一個個 Pair 的話,可以用 zipWithNext()。它會將 Collection 裡的每一個元素跟下一個元素做成一個 Pair,也可以傳入一個 Lambda 來決定要怎麼做轉型。

val numbers = listOf("one", "two", "three", "four", "five")    
numbers.zipWithNext() // [(one, two), (two, three), (three, four), (four, five)]
numbers.zipWithNext() { s1, s2 -> s1.length > s2.length} // [false, false, true, false]

表格整理

在這個章節裡,我們討論了很多 Collection 的截取操作,要特別注意的是,這些操作都可以直接應用在唯讀的 Collection 上,所以操作後都會回傳一個新的 Collection,我們要用新的變數來裝操作後結果。換句話說,這些操作都不會傷到原本 Collection 的內容。

為了一覽這些 API 在不同 Collection 上的行為,以下用表格來整理本章所討論到的 method:

行為 Array List Set Map
take() 從頭取出元素 v v v x
takeLast() 從尾取出元素 v v x x
drop() 從頭刪除元素 v v v x
dropLast() 從尾刪除元素 v v x x
takeWhile {} 從頭依條件取出元素 v v v x
takeLastWhile {} 從尾依條件取出元素 v v x x
dropWhile{} 從頭依條件刪除元素 v v v x
dropLastWhile{} 從尾依條件刪除元素 v v x x
slice() 取出一段 v v x x
chunked() 每間隔 n 分段 x v v x
windowed() 逐步移動取出 n 個元素 x v v x
zipWithNext() 將相鄰兩個元素做成 Pair x v v x

參考資料


上一篇
第十二天:Collection 操作之修改
下一篇
第十四天:Collection 操作之過濾
系列文
新手也能懂的 Kotlin Collection 賞玩門道31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言