Filtern von Kotlin-Sammlungen

Filtern von Kotlin-Sammlungen

1. Überblick

Kotlin-Sammlungen sind leistungsstarke Datenstrukturen mit vielen nützlichen Methoden, die über Java-Sammlungen hinausgehen.

Wir werden eine Handvoll Filtermethoden behandeln, die so detailliert verfügbar sind, dass alle anderen Filtermethoden verwendet werden können, die in diesem Artikel nicht explizit behandelt werden.

Alle diese Methoden geben eine neue Sammlung zurück, wobei die ursprüngliche Sammlung unverändert bleibt.

Wir werden Lambda-Ausdrücke verwenden, um einige der Filter auszuführen. Weitere Informationen zu Lambdas finden Sie in unserem Kotlin Lambda-Artikel hier.

2. Drop

Wir beginnen mit einer grundlegenden Methode zum Reduzieren einer Sammlung. Durch das Löschen können wir einen Teil der Sammlung übernehmen und ein neuesListzurückgeben, dem die Anzahl der in der Anzahl aufgeführten Elemente fehlt:

@Test
fun whenDroppingFirstTwoItemsOfArray_thenTwoLess() {
    val array = arrayOf(1, 2, 3, 4)
    val result = array.drop(2)
    val expected = listOf(3, 4)

    assertIterableEquals(expected, result)
}

Andererseits nennen wirif we want to drop the last n elementsdropLast:

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

Jetzt schauen wir uns unsere erste Filterbedingung an, die ein Prädikat enthält.

Diese Funktion nimmt unseren Code und arbeitet die Liste rückwärts durch, bis wir ein Element erreichen, das die Bedingung nicht erfüllt:

@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 entfernte die letzten drei1fs aus der Liste, während die Methode jedes Element bis zur ersten Instanz durchlief, in der ein Array-Element nicht gleich1f war.

Die Methode beendet das Entfernen von Elementen, sobald ein Element die Bedingung des Prädikats nicht erfüllt.

dropWhile ist ein weiterer Filter, der ein Prädikat verwendet, aberdropWhile arbeitet mit dem Index0 → n unddropLastWhile arbeitet mit dem Indexn → 0.

Wenn wir versuchen, mehr Elemente zu löschen, als die Sammlung enthält, bleibt nur ein leeresList. übrig

3. Take

drop istdrop sehr ähnlich und hält die Elemente auf dem angegebenen Index oder Prädikat:

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

Der Unterschied zwischendrop undtake besteht darin, dassdrop die Elemente entfernt, währendtake die Elemente behält.

Wenn Sie versuchen, mehr Elemente aufzunehmen, als in der Sammlung verfügbar sind, werden nurList zurückgegeben, die der Größe der ursprünglichen Sammlung entsprechen

An important note here ist, dasstakeIf KEINE Erfassungsmethode ist. takeIf verwendet ein Prädikat, um zu bestimmen, ob einnull-Wert zurückgegeben werden soll oder nicht - denken Sie anOptional#filter.

Obwohl es den Anschein hat, als würde es das Funktionsnamenmuster erfüllen, um alle Elemente, die mit dem Prädikat übereinstimmen, in die zurückgegebenenList aufzunehmen, verwenden wirthe filter, um diese Aktion auszuführen.

4. Filter

Dasfilter erstellt ein neuesList basierend auf dem angegebenen Prädikat:

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

Beim Filtern haben wir eine Funktion, mit der wir die Ergebnisse unserer Filter verschiedener Arrays akkumulieren können. Es heißtfilterTo und führt eine veränderbare Listenkopie in ein bestimmtes veränderliches Array.

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

Dieses Beispiel nimmt; ein Array, eine Sequenz und eine Liste.

Es wendet dann dasselbe Prädikat auf alle drei an, um die in jeder Sammlung enthaltenen Primzahlen zu filtern:

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

Filter mit oder ohne Prädikat funktionieren auch gut mitMaps:

val originalMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
val filteredMap = originalMap.filter { it.value < 2 }

Ein sehr vorteilhaftes Paar von Filtermethoden istfilterNotNull undfilterNotNullTo, wasjust filter out all null elements. ergibt

Wenn wir jemals den Index des Sammlungselements verwenden müssen,filterIndexed and filterIndexedTo provide the ability to use a predicate lambda with both the element and its position index.

5. Slice

Wir können auch einen Bereich zum Schneiden verwenden. Um ein Slice auszuführen, definieren wir einfach einRange, das unser Slice extrahieren möchte:

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

Die Scheibe kann entweder nach oben oder nach unten gehen.

Bei Verwendung vonRanges können wir auch die Bereichsschrittgröße festlegen.

Wenn Sie einrange ohne Schritte verwenden und über die Grenzen einer Sammlung hinaus schneiden, werden in unserem ErgebnisList. vielenull-Objekte erstellt

Wenn Sie jedoch mita Range with steps die Grenzen einer Sammlung überschreiten, kann diesArrayIndexOutOfBoundsException auslösen:

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

6. Deutlich

Ein weiterer Filter, auf den wir in diesem Artikel eingehen, ist anders. 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)
}

Wir haben auch die Möglichkeit, eine Auswahlfunktion zu verwenden. The selector returns the value we’re going to evaluate for uniqueness.

Wir implementieren eine kleine Datenklasse namensSmallClass, um die Arbeit mit einem Objekt im Selektor zu untersuchen:

data class SmallClass(val key: String, val num: Int)

Verwenden eines Arrays vonSmallClass:

val original = arrayOf(
  SmallClass("key1", 1),
  SmallClass("key2", 2),
  SmallClass("key3", 3),
  SmallClass("key4", 3),
  SmallClass("er", 9),
  SmallClass("er", 10),
  SmallClass("er", 11))

Wir können verschiedene Felder innerhalb derdistinctBy verwenden:

val actual = original.distinctBy { it.key }
val expected = listOf(
  SmallClass("key1", 1),
  SmallClass("key2", 2),
  SmallClass("key3", 3),
  SmallClass("key4", 3),
  SmallClass("er", 9))

Die Funktion muss keine variable Eigenschaftwe can also perform calculations to determine our distinct values direkt zurückgeben.

Zum Beispiel können wir auf Zahlen für jeden 10-Bereich (0 - 9, 10 - 19, 20 - 29 usw.) auf die nächsten 10 abrunden, und das ist der Wert, den unser Selektor:

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

7. Chunked

Ein interessantes Merkmal von Kotlin 1.2 istchunked. Beim Chunking wird eine einzelneIterable-Sammlung verwendet und ein neuerList von Chunks erstellt, die der definierten Größe entsprechen. This doesn’t work with Arrays; only Iterables.

Wir können entweder einfach eine Chunk-Größe zum Extrahieren verwenden:

@Test
fun givenDNAFragmentString_whenChunking_thenProduceListOfChunks() {
    val dnaFragment = "ATTCGCGGCCGCCAA"

    val fragments = dnaFragment.chunked(3)

    assertIterableEquals(listOf("ATT", "CGC", "GGC", "CGC", "CAA"), fragments)
}

Oder eine Größe und ein Transformator:

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

Das obige Auswahlbeispiel für DNA-Fragmente stammt aus der Kotlin-Dokumentation zu verfügbaren verfügbarenhere.

Beim Übergeben vonchunkedist eine Größe, die kein Teiler unserer Sammlungsgröße ist. In diesen Fällen ist das letzte Element in unserer Liste der Chunks einfach eine kleinere Liste.

Achten Sie darauf, dass Sie nicht davon ausgehen, dass jeder Block die volle Größe hat, und treffen Sie aufArrayIndexOutOfBoundsException!

8. Fazit

Mit allen Kotlin-Filtern können wir Lambdas anwenden, um zu bestimmen, ob ein Element gefiltert werden soll oder nicht. Not all of these functions can be used on Maps funktionieren jedoch alle Filterfunktionen, die mitMaps arbeiten, mitArrays.

Die Dokumentation zu den Kotlin-Sammlungen gibt uns Auskunft darüber, ob wir eine Filterfunktion nur für Arrays oder für beide verwenden können. Die Dokumentation finden Sie unterhere.

Wie immer sind alle Beispieleover on GitHub verfügbar.