Filtrage des collections de Kotlin

Filtrage des collections de Kotlin

1. Vue d'ensemble

Les collections Kotlin sont des structures de données puissantes avec de nombreuses méthodes bénéfiques qui les placent au-delà des collections Java.

Nous allons couvrir une poignée de méthodes de filtrage disponibles avec suffisamment de détails pour pouvoir utiliser toutes les autres que nous ne couvrons pas explicitement dans cet article.

Toutes ces méthodes renvoient une nouvelle collection, laissant la collection d'origine non modifiée.

Nous utiliserons des expressions lambda pour exécuter certains des filtres. Pour en savoir plus sur les lambdas, consultez notre article sur Kotlin Lambda ici.

2. Drop

Nous allons commencer par un moyen simple de réduire une collection. La suppression nous permet de prendre une partie de la collection et de renvoyer un nouveauList manquant le nombre d'éléments répertoriés dans le nombre:

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

    assertIterableEquals(expected, result)
}

D'autre part,if we want to drop the last n elements, nous appelonsdropLast:

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

Nous allons maintenant examiner notre première condition de filtre qui contient un prédicat.

Cette fonction prendra notre code et remontera dans la liste jusqu'à atteindre un élément qui ne remplit pas la condition:

@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 a supprimé les trois derniers1fs de la liste pendant que la méthode parcourait chaque élément jusqu'à la première instance où un élément du tableau n'était pas égal à1f.

La méthode arrête de supprimer des éléments dès qu'un élément ne remplit pas la condition du prédicat.

dropWhile est un autre filtre qui prend un prédicat maisdropWhile fonctionne à partir de l'index0 → n etdropLastWhile fonctionne à partir de l'indexn → 0.

Si nous essayons de supprimer plus d'éléments que la collection ne contient, il nous restera juste unList. vide

3. Take

Très similaire àdrop,take conservera les éléments jusqu'à l'index ou le prédicat donné:

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

La différence entredrop ettake est quedrop supprime les éléments, tandis quetake conserve les éléments.

Tenter de prendre plus d'articles que ce qui est disponible dans la collection - renverra simplement unList qui est de la même taille que la collection originale

An important note here signifie quetakeIf n'est PAS une méthode de collecte. takeIf utilise un prédicat pour déterminer s'il faut renvoyer une valeurnull ou non - pensez àOptional#filter.

Bien qu'il puisse sembler que cela réponde au modèle de nom de fonction pour prendre tous les éléments qui correspondent au prédicat dans lesList renvoyés, nous utilisonsthe filter pour effectuer cette action.

4. Filter

Lefilter crée un nouveauList basé sur le prédicat fourni:

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

Lors du filtrage, nous avons une fonction qui nous permet d’accumuler les résultats de nos filtres de différents tableaux. Il s'appellefilterTo et prend une copie de liste mutable dans un tableau mutable donné.

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

Cet exemple prend; un tableau, une séquence et une liste.

Il applique ensuite le même prédicat à tous les trois pour filtrer les nombres premiers contenus dans chaque collection:

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

Les filtres avec ou sans prédicat fonctionnent également bien avecMaps:

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

Une paire très avantageuse de méthodes de filtrage estfilterNotNull etfilterNotNullTo, ce quijust filter out all null elements.

Enfin, si jamais nous devons utiliser l'index de l'élément de collection,filterIndexed and filterIndexedTo provide the ability to use a predicate lambda with both the element and its position index.

5. Slice

Nous pouvons également utiliser une plage pour effectuer le découpage en tranches. Pour effectuer une tranche, nous définissons simplement unRange que notre tranche souhaite extraire:

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

La tranche peut aller vers le haut ou vers le bas.

Lors de l'utilisation deRanges, nous pouvons également définir la taille du pas de plage.

En utilisant unrange sans étapes et en découpant au-delà des limites d'une collection, nous allons créer de nombreux objetsnull dans notre résultatList.

Cependant, dépasser les limites d'une collection en utilisanta Range with steps peut déclencher unArrayIndexOutOfBoundsException:

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

6. Distinct

Un autre filtre que nous allons examiner dans cet article est distinct. 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)
}

Nous avons également la possibilité d'utiliser une fonction de sélection. The selector returns the value we’re going to evaluate for uniqueness.

Nous allons implémenter une petite classe de données appeléeSmallClass pour explorer l'utilisation d'un objet dans le sélecteur:

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

en utilisant un tableau deSmallClass:

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

Nous pouvons utiliser différents champs dans lesdistinctBy:

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

La fonction n'a pas besoin de renvoyer directement une propriété de variable,we can also perform calculations to determine our distinct values.

Par exemple, pour les nombres correspondant à chaque plage de 10 (0 à 9, 10 à 19, 20 à 29, etc.), nous pouvons arrondir à la dizaine la plus proche, ce qui correspond à la valeur que notre sélecteur:

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

7. Morceaux

Une caractéristique intéressante de Kotlin 1.2 estchunked. La segmentation prend une seule collectionIterable et crée un nouveauList de blocs correspondant à la taille définie. This doesn’t work with Arrays; only Iterables.

Nous pouvons chunk avec soit simplement une taille de chunk à extraire:

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

    val fragments = dnaFragment.chunked(3)

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

Ou une taille et un transformateur:

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

L'exemple de sélecteur ci-dessus de fragments d'ADN est extrait de la documentation Kotlin sur leshere disponibles fragmentés.

Lorsque vous passezchunked une taille qui n'est pas un diviseur de la taille de notre collection. Dans ces cas, le dernier élément de notre liste de morceaux sera simplement une liste plus petite.

Veillez à ne pas supposer que chaque morceau a la taille réelle et qu'il rencontre unArrayIndexOutOfBoundsException!

8. Conclusion

Tous les filtres Kotlin nous permettent d’appliquer lambdas pour déterminer si un élément doit être filtré ou non. Not all of these functions can be used on Maps, cependant, toutes les fonctions de filtre qui fonctionnent surMaps fonctionneront surArrays.

La documentation sur les collections Kotlin nous indique si nous pouvons utiliser une fonction de filtrage uniquement sur les tableaux ou les deux. La documentation peut être trouvéehere.

Comme toujours, tous les exemples sont disponiblesover on GitHub.