在前面的章節裡有提到如何從 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 的話,可以用 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 |