1. 概要

Kotlinコレクションは、Javaコレクションを超えてそれらを配置する多くの有益なメソッドを備えた強力なデータ構造です。

この記事で明示的にカバーしていない他のすべてを利用できるように、十分に詳細に利用可能ないくつかのフィルタリング方法について説明します。

これらのメソッドはすべて、元のコレクションを変更せずに、新しいコレクションを返します。

ラムダ式を使用して、いくつかのフィルターを実行します。 ラムダの詳細については、こちらのKotlinLambdaの記事をご覧ください。

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はインデックス0->n から機能し、dropLastWhileはインデックスnから機能します->0

コレクションに含まれているよりも多くの要素を削除しようとすると、空のリストが残ります。

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)
}

droptakeの違いは、 drop がアイテムを削除するのに対し、takeはアイテムを保持することです。

コレクションで利用可能なアイテムよりも多くのアイテムを取得しようとすると、元のコレクションと同じサイズのリストが返されます。

ここでの重要な注意は、takeIfNOT収集メソッドであるということです。 takeIf は述語を使用して、 null 値を返すかどうかを決定します– Optional#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と呼ばれ、指定された可変配列に可変リストのコピーを取ります。

これにより、複数のコレクションを取得して、それらを1つの累積コレクションにフィルタリングできます。

この例では、次のようになります。 配列、シーケンス、およびリスト。

次に、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 }

非常に有益なフィルターメソッドのペアは、filterNotNullfilterNotNullToで、すべてのnull要素をフィルターで除外します。

最後に、コレクションアイテムのインデックスを使用する必要がある場合は、 filterIndexedとfilterIndexedToを使用して、要素とその位置インデックスの両方で述語ラムダを使用する機能を提供します。

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を作成します。 これはアレイでは機能しません。 Iterablesのみ。

抽出するチャンクのサイズのいずれかでチャンクできます。

@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フラグメントのセレクターの例は、チャンクされた利用可能なここに関するKotlinのドキュメントから抽出されています。

チャンクを渡すとき、コレクションサイズの約数ではないサイズ。 そのような場合、チャンクのリストの最後の要素は単純に小さなリストになります。

すべてのチャンクがフルサイズであると想定しないように注意してください。ArrayIndexOutOfBoundsException!

8. 結論

すべてのKotlinフィルターを使用すると、ラムダを適用して、アイテムをフィルター処理する必要があるかどうかを判断できます。 これらの関数のすべてがマップで使用できるわけではありませんが、マップで機能するすべてのフィルター関数は配列で機能します。

Kotlinコレクションのドキュメントには、配列のみまたは両方でフィルター関数を使用できるかどうかに関する情報が記載されています。 ドキュメントはここにあります。

いつものように、すべての例はGitHubから入手できます。