2017年11月末、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、正しく使いこなしたいですね。