Kotlinコレクションのフィルタリング

Kotlinコレクションのフィルタリング

1. 概要

Kotlinコレクションは強力なデータ構造であり、Javaコレクションに加えてそれを超える多くの有益なメソッドを備えています。

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

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

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

2. Drop

コレクションをトリミングする基本的な方法から始めます。 ドロップすると、コレクションの一部を取得して、番号にリストされている要素の数が欠落している新しい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)
}

一方、if we want to drop the last n elementsでは、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つの1fsを削除しました。

このメソッドは、要素が述語の条件を満たさなくなるとすぐに要素の削除を停止します。

dropWhileは述語を取る別のフィルターですが、dropWhileはインデックス0 → nから機能し、dropLastWhileはインデックスn → 0から機能します。

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

3. Take

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はアイテムを保持することです。

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

An important note hereは、takeIfが収集メソッドではないことを意味します。 takeIfは述語を使用して、null値を返すかどうかを決定します–Optional#filterを考えてください。

述語に一致するすべてのアイテムを返されたListに取り込むことは関数名のパターンを満たしているように見えるかもしれませんが、そのアクションを実行するためにthe filterを使用します。

4. Filter

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と呼ばれ、指定された可変配列に可変リストのコピーを取ります。

This allows us to take several collections and filter them into a single, accumulative collection

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

次に、同じ述語を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()

    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で、これはjust filter out all null elements.になります。

最後に、コレクションアイテムのインデックスを使用する必要がある場合は、filterIndexed and filterIndexedTo provide the ability to use a predicate lambda with both the element and its position index.

5. Slice

範囲を使用してスライスを実行することもできます。 スライスを実行するには、スライスが抽出する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 with stepsを使用してコレクションの境界を超えると、ArrayIndexOutOfBoundsExceptionがトリガーされる可能性があります。

@Test
fun whenSlicingBeyondRangeOfArrayWithStep_thenOutOfBoundsException() {
    assertThrows(ArrayIndexOutOfBoundsException::class.java) {
        val original = arrayOf(12, 3, 34, 4)
        original.slice(3..8 step 2)
    }
}

6. はっきりした

この記事で取り上げる別のフィルターは明確です。 We can use this method to collect unique objects from our list

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

セレクター関数を使用するオプションもあります。 The selector returns the value we’re going to evaluate for uniqueness.

セレクター内でオブジェクトを操作する方法を調べるために、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))

この関数は、変数プロパティwe can also perform calculations to determine our distinct valuesを直接返す必要はありません。

たとえば、10個の範囲(0〜9、10〜19、20〜29など)ごとの数値に対して、最も近い10に切り捨てることができます。これがセレクターの値です。

val actual = array.distinctBy { Math.floor(it.num / 10.0) }

7. チャンク

Kotlin 1.2の興味深い機能の1つは、chunkedです。 チャンキングとは、単一のIterableコレクションを取得し、定義されたサイズに一致するチャンクの新しいListを作成することです。 This doesn’t work with Arrays; only 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フラグメントのセレクターの例は、チャンク化された使用可能なhereに関するKotlinのドキュメントから抽出されています。

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

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

8. 結論

すべてのKotlinフィルターでは、ラムダを適用して、アイテムをフィルターするかどうかを決定できます。 ただし、Not all of these functions can be used on Mapsは、Mapsで機能するすべてのフィルター関数がArraysで機能します。

Kotlinコレクションのドキュメントは、配列のみまたは両方でフィルター関数を使用できるかどうかに関する情報を提供します。 ドキュメントはhereにあります。

いつものように、すべての例は利用可能なover on GitHubです。