Kotlinコレクションのフィルタリング
1概要
Kotlinコレクションは強力なデータ構造であり、Javaコレクションを超えた多くの有益な方法があります。
この記事では明示的に説明していない他のすべての方法を利用できるように、十分に詳細に説明した少数のフィルタリング方法について説明します。
これらのメソッドはすべて新しいコレクションを返し、元のコレクションは変更されません。
いくつかのフィルタを実行するために、ラムダ式を使用します。ラムダについてもっと読むために、ここで我々のKotlin Lambdaの記事を見てください。
2
ドロップ
コレクションを切り取る基本的な方法から始めましょう。ドロップすることで、コレクションの一部を取り出して、数にリストされている要素の数が足りない新しい
List
を返すことができます。
@Test
fun whenDroppingFirstTwoItemsOfArray__thenTwoLess() {
val array = arrayOf(1, 2, 3, 4)
val result = array.drop(2)
val expected = listOf(3, 4)
assertIterableEquals(expected, result)
}
一方、** 最後のn個の要素を削除したい場合は、
dropLast
を呼び出します。
@Test
fun givenArray__whenDroppingLastElement__thenReturnListWithoutLastElement() {
val array = arrayOf("1", "2", "3", "4")
val result = array.dropLast(1)
val expected = listOf("1", "2", "3")
assertIterableEquals(expected, result)
}
それでは、述語を含む最初のフィルタ条件を見てみましょう。
この関数は、条件を満たさない要素に到達するまで、コードを取り出してリストを逆方向に処理します。
@Test
fun whenDroppingLastUntilPredicateIsFalse__thenReturnSubsetListOfFloats() {
val array = arrayOf(1f, 1f, 1f, 1f, 1f, 2f, 1f, 1f, 1f)
val result = array.dropLastWhile { it == 1f }
val expected = listOf(1f, 1f, 1f, 1f, 1f, 2f)
assertIterableEquals(expected, result)
}
dropLastWhile
は、配列要素が
1f
と等しくない最初のインスタンスまでメソッドが各項目を循環するにつれて、リストから最後の3つの
__1f
__を削除しました。
要素が述語の条件を満たさなくなるとすぐに、メソッドは要素の削除を停止します
dropWhile
は述語をとる別のフィルタですが、
dropWhile
はindex
0 – > n
から機能し、
dropLastWhile
はindex
n – > 0
から機能します。
-
コレクションに含まれているよりも多くの要素を削除しようとすると、空の__Listが残ります
3
取る
drop
と非常によく似ていますが、
take
は与えられたインデックスまたは述語まで要素を保持します。
@Test
fun `when predicating on 'is String', then produce list of array up until predicate is false`() {
val originalArray = arrayOf("val1", 2, "val3", 4, "val5", 6)
val actualList = originalArray.takeWhile { it is String }
val expectedList = listOf("val1")
assertIterableEquals(expectedList, actualList)
}
-
drop
と
take
の違いは、
drop
はアイテムを削除するのに対して、
take
はアイテムを保持するということです。
コレクションで利用可能な数よりも多くのアイテムを取得しようとしています – 元のコレクションと同じサイズの
List
が返されるだけです。
-
ここでの重要な注意点は、
takeIf
は[.under]#NOT#コレクションメソッドではないということです。
takeIf
は述部を使用して
null
値を返すかどうかを決定します –
Optional#filter
を考えてください。
述語に一致するすべての項目を返される
List
に入れることは関数名のパターンを満たすと思われるかもしれませんが、そのアクションを実行するには
the
filter__を使います。
4
フィルタ
filter
は提供された述語に基づいて新しい
List
を作成します。
@Test
fun givenAscendingValueMap__whenFilteringOnValue__ThenReturnSubsetOfMap() {
val originalMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
val filteredMap = originalMap.filter { it.value < 2 }
val expectedMap = mapOf("key1" to 1)
assertTrue { expectedMap == filteredMap }
}
フィルタリングするとき、異なる配列のフィルタの結果を累積することを可能にする関数があります。それは
filterTo
と呼ばれ、与えられた可変配列への可変リストのコピーをとります。
-
これにより、いくつかのコレクションを取得し、それらを単一の累積コレクション** にフィルタリングすることができます。
この例では、配列、シーケンス、リスト
その後、3つすべてに同じ述語を適用して、各コレクションに含まれる素数をフィルタリングします。
@Test
fun whenFilteringToAccumulativeList__thenListContainsAllContents() {
val array1 = arrayOf(90, 92, 93, 94, 92, 95, 93)
val array2 = sequenceOf(51, 31, 83, 674__506__111, 256__203__161, 15__485__863)
val list1 = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
val primes = mutableListOf<Int>()
val expected = listOf(2, 3, 5, 7, 31, 83, 15__485__863, 256__203__161, 674__506__111)
val primeCheck = { num: Int -> Primes.isPrime(num) }
array1.filterTo(primes, primeCheck)
list1.filterTo(primes, primeCheck)
array2.filterTo(primes, primeCheck)
primes.sort()
assertIterableEquals(expected, primes)
}
述語の有無にかかわらずフィルタも
Maps
でうまく機能します。
val originalMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
val filteredMap = originalMap.filter { it.value < 2 }
非常に有益なフィルタメソッドのペアは
filterNotNull
と
filterNotNullTo
で、これは** すべての
null
要素を単に除外するだけです。
最後に、コレクションアイテムのインデックスを使用する必要がある場合、**
filterIndexed
と
filterIndexedTo
は、要素とその位置インデックスの両方で述語lambdaを使用する機能を提供します。
5
スライス
スライスを実行するために範囲を使用することもできます。スライスを実行するには、スライスが抽出したい
Range
を定義するだけです。
@Test
fun whenSlicingAnArrayWithDotRange__ThenListEqualsTheSlice() {
val original = arrayOf(1, 2, 3, 2, 1)
val actual = original.slice(3 downTo 1)
val expected = listOf(2, 3, 2)
assertIterableEquals(expected, actual)
}
スライスは上にも下にも移動できます。
-
Ranges
を使うときは、範囲ステップサイズも設定することができます。
ステップなしで
range
を使用し、コレクションの範囲を超えてスライスすると、結果
List.
に多数の
null
オブジェクトを作成します
ただし、
a
Range
とsteps
を使用してコレクションの範囲を超えると
ArrayIndexOutOfBoundsException
が発生する可能性があります。
@Test
fun whenSlicingBeyondRangeOfArrayWithStep__thenOutOfBoundsException() {
assertThrows(ArrayIndexOutOfBoundsException::class.java) {
val original = arrayOf(12, 3, 34, 4)
original.slice(3..8 step 2)
}
}
6. 異なる
この記事で取り上げるもう1つのフィルタは明確です。
このメソッドを使って私たちのリストからユニークなオブジェクトを集めることができます
:
@Test
fun whenApplyingDistinct__thenReturnListOfNoDuplicateValues() {
val array = arrayOf(1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9)
val result = array.distinct()
val expected = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
assertIterableEquals(expected, result)
}
セレクタ機能を使用することもできます。
セレクタは、一意性を評価しようとしている値を返します。
セレクタ内のオブジェクトを操作する方法を探るために、
SmallClass
という小さなデータクラスを実装します。
data class SmallClass(val key: String, val num: Int)
SmallClass
の配列を使う:
val original = arrayOf(
SmallClass("key1", 1),
SmallClass("key2", 2),
SmallClass("key3", 3),
SmallClass("key4", 3),
SmallClass("er", 9),
SmallClass("er", 10),
SmallClass("er", 11))
distinctBy
内ではさまざまなフィールドを使用できます。
val actual = original.distinctBy { it.key }
val expected = listOf(
SmallClass("key1", 1),
SmallClass("key2", 2),
SmallClass("key3", 3),
SmallClass("key4", 3),
SmallClass("er", 9))
この関数は変数のプロパティを直接返す必要はありません。
異なる値を決定するための計算も可能です
例えば、各10の範囲(0 – 9、10 – 19、20 – 29など)の数値には、最も近い10に切り捨てることができます。これが、セレクターが設定した値です。
val actual = array.distinctBy { Math.floor(it.num/10.0) }
7. チャンク
Kotlin 1.2の1つの興味深い機能は
チャンク
です。チャンキングは単一の
Iterable
コレクションを取り、定義されたサイズに一致するチャンクの新しい
List
を作成することです。
これは
Arrays
では動作しません。
__Iterable
__s
のみです。
抽出するチャンクのサイズだけでチャンクできます
@Test
fun givenDNAFragmentString__whenChunking__thenProduceListOfChunks() {
val dnaFragment = "ATTCGCGGCCGCCAA"
val fragments = dnaFragment.chunked(3)
assertIterableEquals(listOf("ATT", "CGC", "GGC", "CGC", "CAA"), fragments)
}
またはサイズとトランス:
@Test
fun givenDNAString__whenChunkingWithTransformer__thenProduceTransformedList() {
val codonTable = mapOf(
"ATT" to "Isoleucine",
"CAA" to "Glutamine",
"CGC" to "Arginine",
"GGC" to "Glycine")
val dnaFragment = "ATTCGCGGCCGCCAA"
val proteins = dnaFragment.chunked(3) { codon ->
codonTable[codon.toString()]?: error("Unknown codon")
}
assertIterableEquals(listOf(
"Isoleucine", "Arginine",
"Glycine", "Arginine", "Glutamine"), proteins)
}
上記のDNAフラグメントのセレクターの例は、チャンクされたhttps://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/chunked.html[こちら]にあるKotlinのドキュメントから抜粋したものです。
-
chunked
を渡すとき、私たちのコレクションのサイズの約数ではないサイズ。そのような場合、私たちのチャンクリストの最後の要素は単に小さなリストになります。
すべてのチャンクがフルサイズであると仮定して__ArrayIndexOutOfBoundsExceptionが発生しないように注意してください。
8結論
すべてのKotlinフィルタを使用して、アイテムをフィルタ処理するかどうかを決定するためにラムダを適用できます。
これらの関数すべてが
Maps
で使用できるわけではありませんが、
Maps
で機能するすべてのフィルター機能は
Arrays
で機能します。
Kotlinコレクションのドキュメントには、配列だけでフィルタ関数を使用できるのか、その両方で使用できるのかについての情報があります。ドキュメントはhttps://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/index.html[ここ]にあります。
いつものように、すべての例はhttps://github.com/eugenp/tutorials/tree/master/core-kotlin/src/test/kotlin/com/baeldung/kotlin[over on GitHub]で利用可能です。