2017年11月末、Kotlin 1.2がリリースされました。
- Kotlin 1.2 Released: Sharing Code between Platforms
- Kotlin 1.2 リリース: プラットフォーム間のコード共有
- What's New in Kotlin 1.2
Kotlin 1.2でも、Standard Libaryにいくつかの追加があります。
この投稿では、IterableやSequence、MutableCollectionなどコレクション関連のクラスの追加関数を紹介します。
zipWithNext : インデックスの隣り合う要素を合成したList・Sequenceを生成する
Iterable<T>
とSequence<T>
の拡張関数として、zipWithNextという「インデックスの隣り合う要素を合成したリストを生成する」関数が追加されました。
Iterable<T>
Iterable<T>#zipWithNext
の返り値型は、List<Pair<T, T>>
です。
Sequence<T>
Sequence<T>#zipWithNext
の返り値型は、Sequence<Pair<T, T>>
です。
val list : List<Int> = (0..5).toList()
assert(list == listOf(0, 1, 2, 3, 4, 5))
val result : List<Pair<Int, Int>> = list.zipWithNext()
assert(result == listOf(0 to 1, 1 to 2, 2 to 3, 3 to 4, 4 to 5))
引数に関数オブジェクトを渡し、どのように合成するかを指定することもできます。
val list : List<Int> = (0..5).toList()
assert(list == listOf(0, 1, 2, 3, 4, 5))
val result : List<String> = list.zipWithNext { a, b -> (a * b).toString() }
assert(result == listOf("0", "2", "6", "12", "20"))
SequenceのzipWithNextは、experimentalなkotlin.coroutines.experimental
の
- buildSequence
- yield
利用例としてとても分かりやすので、ぜひ一度ソースコードを見てみることをおすすめします。
val sequence : Sequence<Int> = generateSequence(0) { it + 1 }
val result : List<Pair<Int, Int>> = sequence.zipWithNext().take(3).toList()
assert(result == listOf(0 to 1, 1 to 2, 2 to 3))
chunked : まとめる数を指定し、要素をまとめたList・Sequenceを生成する
Iterable<T>
とSequence<T>
の拡張関数として、chunkedという「まとめる数を指定し、要素をまとめたList・Sequenceを生成する」関数が追加されました。
もし最後の要素が指定数に満たない場合、足りない要素だけでまとめられます。
Iterable<T>
Iterable<T>#chunked
の返り値型は、List<List<T>>
です。
Sequence<T>
Sequence<T>#chunked
の返り値型は、Sequence<List<T>>
です。
val list : List<Int> = (0..5).toList()
assert(list == listOf(0, 1, 2, 3, 4, 5))
val result : List<List<Int>> = list.chunked(3)
assert(result == listOf(listOf(0, 1, 2), listOf(3, 4, 5)))
もし最後の要素が指定数に満たない場合、足りない要素だけでまとめられます。
val list : List<Int> = (0..6).toList()
assert(list == listOf(0, 1, 2, 3, 4, 5, 6))
val result : List<List<Int>> = list.chunked(3)
assert(result == listOf(listOf(0, 1, 2), listOf(3, 4, 5), listOf(6)))
引数に関数オブジェクトをとり、List<T>
をさらに変換させることも可能です。
val list : List<Int> = (0..6).toList()
assert(list == listOf(0, 1, 2, 3, 4, 5, 6))
val result : List<Double> = list.chunked(3) { it.average() }
assert(result == listOf(1.0, 4.0, 6.0))
内部で次に紹介するwindowed
関数を呼び出しています。
windowed : まとめる数と移動数を指定し、要素をまとめたList・Sequenceを生成する
Iterable<T>
とSequence<T>
の拡張関数として、windowedという「まとめる数と移動数を指定し、要素をまとめたList・Sequenceを生成する」関数が追加されました。
いくつか引数を持ちます。
size : Int => まとめる数
まとめる数は0より大きくないといけません。0以下を渡すと例外を投げます。
val list : List<Int> = (0..3).toList()
assert(list == listOf(0, 1, 2, 3))
assert(list.windowed(2) == listOf(listOf(0, 1), listOf(1, 2), listOf(2, 3)))
assert(list.windowed(3) == listOf(listOf(0, 1, 2), listOf(1, 2, 3)))
assert(list.windowed(4) == listOf(listOf(0, 1, 2, 3)))
assert(list.windowed(1) == listOf(listOf(0), listOf(1), listOf(2), listOf(3)))
assert(list.windowed(5) == emptyList<Int>())
step : Int => 移動数。デフォルトは1。
移動数は0より大きくないといけません。0以下を渡すと例外を投げます。
sizeとstepはともにIntなので、名前付き引数呼び出しを使うことをお勧めします。
val list : List<Int> = (0..4).toList()
assert(list == listOf(0, 1, 2, 3, 4))
assert(list.windowed(size = 2, step = 1) == listOf(listOf(0, 1), listOf(1, 2), listOf(2, 3), listOf(3, 4)))
assert(list.windowed(size = 2, step = 2) == listOf(listOf(0, 1), listOf(2, 3)))
assert(list.windowed(size = 2, step = 3) == listOf(listOf(0, 1), listOf(3, 4)))
assert(list.windowed(size = 3, step = 1) == listOf(listOf(0, 1, 2), listOf(1, 2, 3), listOf(2, 3, 4)))
assert(list.windowed(size = 3, step = 2) == listOf(listOf(0, 1, 2), listOf(2, 3, 4)))
assert(list.windowed(size = 3, step = 3) == listOf(listOf(0, 1, 2)))
partialWindows : Boolean => まとまった結果がまとめる数より小さかった場合の処理の違い。デフォルトはfalse
val list : List<Int> = (0..4).toList()
assert(list == listOf(0, 1, 2, 3, 4))
assert(list.windowed(size = 2, step = 1, partialWindows = false) == listOf(listOf(0, 1), listOf(1, 2), listOf(2, 3), listOf(3, 4)))
assert(list.windowed(size = 2, step = 1, partialWindows = true) == listOf(listOf(0, 1), listOf(1, 2), listOf(2, 3), listOf(3, 4), listOf(4)))
assert(list.windowed(size = 2, step = 2, partialWindows = false) == listOf(listOf(0, 1), listOf(2, 3)))
assert(list.windowed(size = 2, step = 2, partialWindows = true) == listOf(listOf(0, 1), listOf(2, 3), listOf(4)))
assert(list.windowed(size = 2, step = 3, partialWindows = false) == listOf(listOf(0, 1), listOf(3, 4)))
assert(list.windowed(size = 2, step = 3, partialWindows = true) == listOf(listOf(0, 1), listOf(3, 4)))
assert(list.windowed(size = 3, step = 1, partialWindows = false) == listOf(listOf(0, 1, 2), listOf(1, 2, 3), listOf(2, 3, 4)))
assert(list.windowed(size = 3, step = 1, partialWindows = true) == listOf(listOf(0, 1, 2), listOf(1, 2, 3), listOf(2, 3, 4), listOf(3, 4), listOf(4)))
assert(list.windowed(size = 3, step = 2, partialWindows = false) == listOf(listOf(0, 1, 2), listOf(2, 3, 4)))
assert(list.windowed(size = 3, step = 2, partialWindows = true) == listOf(listOf(0, 1, 2), listOf(2, 3, 4), listOf(4)))
assert(list.windowed(size = 3, step = 3, partialWindows = false) == listOf(listOf(0, 1, 2)))
assert(list.windowed(size = 3, step = 3, partialWindows = true) == listOf(listOf(0, 1, 2), listOf(3, 4)))
Iterable<T>
Iterable<T>#windowed
の返り値型は、List<List<T>>
です。
Sequence<T>
Sequence<T>#windowed
の返り値型は、Sequence<List<T>>
です。
引数に関数オブジェクトをとり、List<T>
をさらに変換させることも可能です。
val list : List<Int> = (0..4).toList()
assert(list == listOf(0, 1, 2, 3, 4))
val result = list.windowed(size = 2, step = 1) { it.average() }
assert(result == listOf(0.5, 1.5, 2.5, 3.5))
先に紹介したchunked
は、次のようにwindowedを使い実装されています。
@SinceKotlin("1.2")
public fun <T> Iterable<T>.chunked(size: Int): List<List<T>> {
return windowed(size, size, partialWindows = true)
}
@SinceKotlin("1.2")
public fun <T> Iterable<T>.chunked(size: Int): List<List<T>> {
return windowed(size, size, partialWindows = true)
}
次の呼び出しは、chunkedと同じ結果になります。
val list : List<Int> = (0..4).toList()
assert(list == listOf(0, 1, 2, 3, 4))
assert(list.chunked(2) == list.windowed(size = 2, step = 2, partialWindows = true))
fill : 指定要素で要素を全部埋め変える
MutableList<T>
の拡張関数として、「指定要素で要素を全部埋め変える」fillが追加されました。
ドキュメント
関数の返り値型はUnitです。
内部では、java.util.Collectionsのfillメソッドを用いています。
val mutableList : MutableList<Int> = (0..4).toMutableList()
assert(mutableList == mutableListOf(0, 1, 2, 3, 4))
val unit : Unit = mutableList.fill(0) // 返り値型はUnit
assert(mutableList == mutableListOf(0, 0, 0, 0, 0))
各種Array系のクラスにもfillという拡張関数が存在します。
shuffle : 要素を並び替える
MutableList<T>
の拡張関数として、「要素を並び替える」shuffuleが追加されました。
引数として、追加でjava.util.Randomをとるオーバーロードも存在します。
内部では、java.util.Collectionsのshuffuleメソッドを用いています。
val mutableList : MutableList<Int> = (0..4).toMutableList()
assert(mutableList == mutableListOf(0, 1, 2, 3, 4))
val unit : Unit = mutableList.shuffle(java.util.Random(0)) // 返り値型はUnit
assert(mutableList == mutableListOf(4, 2, 1, 3, 0))
この関数は副作用をもち、返り値型はUnitなことがポイントです。副作用がなく、返り値型がList<T>
のshuffledと正しく使い分けてください。
shuffled : 要素を並び替えたリストを新たに生成する
Iterable<T>
の拡張関数として、「要素を並び替えたリストを新たに生成する」shuffledが追加されました。
この関数は副作用をもちません。そして返り値型はListなことがポイントです。新たにシャッフルされたListが生成されます。
「あれ、shuffledを呼び出したのに、シャッフルされていない」ということがないように気を付けてください。
引数として、追加でjava.util.Randomをとるオーバーロードも存在します。
内部では、java.util.Collectionsのshuffuleメソッドを用いています。
val list : List<Int> = (0..4).toList()
assert(list == listOf(0, 1, 2, 3, 4))
val shuffuled : List<Int> = list.shuffled(java.util.Random(0))
assert(shuffuled == listOf(4, 2, 1, 3, 0))
assert(list == listOf(0, 1, 2, 3, 4))
[要注意] replaceAll : 全要素を関数オブジェクトで指定した条件で入れ替える
Java8でListにデフォルト実装されているreplaceAllがKotlin1.2でも、MutableListで呼び出せるようになりました。
JRE8限定です。JVMでもJRE6やJRE7、またはJavaScriptなどではコンパイルエラーになることに気を付けてください。
Kotlin 1.2で追加されたJRE8限定の関数として、Map#getOrDefaultもあります。
まとめ
Kotlin 1.2で追加されたコレクション系の関数を紹介しました。
「あ、あったらいいな」・「お、これ便利だな」という関数があったのではないでしょうか?
筆者は、この記事の執筆の前の週に業務において、zipWithNextを使いたい場面がありました。
また、Kotlin 1.2でStandard Libaryにおいて、kotlin.coroutines.experimental
が使われていることにも注目です。
Kotlin Standary Libary、正しく使いこなしたいですね。