Filtrando coleções do Kotlin
1. Visão geral
As coleções Kotlin são estruturas de dados poderosas com muitos métodos benéficos que as colocam além das coleções Java.
Vamos cobrir alguns métodos de filtragem disponíveis com detalhes suficientes para poder utilizar todos os outros que não abordamos explicitamente neste artigo.
Todos esses métodos retornam uma nova coleção, deixando a coleção original inalterada.
Estaremos usando expressões lambda para realizar alguns dos filtros. Para ler mais sobre lambdas, dê uma olhada no nosso artigo Kotlin Lambda aqui.
2. Drop
Começaremos com uma maneira básica de aparar uma coleção. A eliminação nos permite pegar uma parte da coleção e retornar um novoList sem o número de elementos listados no número:
@Test
fun whenDroppingFirstTwoItemsOfArray_thenTwoLess() {
val array = arrayOf(1, 2, 3, 4)
val result = array.drop(2)
val expected = listOf(3, 4)
assertIterableEquals(expected, result)
}
Por outro lado,if we want to drop the last n elements, chamamosdropLast:
@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)
}
Agora, veremos nossa primeira condição de filtro que contém um predicado.
Essa função leva nosso código e trabalha para trás na lista até chegarmos a um elemento que não atende à condição:
@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 removeu os três1fs finais da lista conforme o método percorria cada item até a primeira instância em que um elemento da matriz não era igual a1f.
O método para de remover elementos assim que um elemento não atende à condição do predicado.
dropWhile é outro filtro que usa um predicado, masdropWhile funciona a partir do índice0 → nedropLastWhile funciona a partir do índicen → 0.
Se tentarmos eliminar mais elementos do que a coleção contém, ficaremos apenas com umList. vazio
3. Take
Muito semelhante adrop,take manterá os elementos até o índice ou predicado fornecido:
@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)
}
A diferença entredropetake é quedrop remove os itens, enquantotake mantém os itens.
A tentativa de pegar mais itens do que os disponíveis na coleção - retornará apenas umList que é do mesmo tamanho da coleção original
An important note here é quetakeIf NÃO é um método de coleta. takeIf usa um predicado para determinar se deve retornar um valornull ou não - pense emOptional#filter.
Embora possa parecer que atenderia ao padrão de nome de função para levar todos os itens que correspondem ao predicado emList retornado, usamosthe filter para executar essa ação.
4. Filter
Ofilter cria um novoList com base no predicado fornecido:
@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 }
}
Ao filtrar, temos uma função que nos permite acumular os resultados de nossos filtros de diferentes matrizes. É denominadofilterToe obtém uma cópia da lista mutável para um determinado array mutável.
This allows us to take several collections and filter them into a single, accumulative collection.
Este exemplo leva; uma matriz, uma sequência e uma lista.
Em seguida, aplica o mesmo predicado a todos os três para filtrar os números primos contidos em cada coleção:
@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)
}
Filtros com ou sem predicado também funcionam bem comMaps:
val originalMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
val filteredMap = originalMap.filter { it.value < 2 }
Um par de métodos de filtro muito benéfico éfilterNotNullefilterNotNullTo que serájust filter out all null elements.
Por fim, se precisarmos usar o índice do item de coleção,filterIndexed and filterIndexedTo provide the ability to use a predicate lambda with both the element and its position index.
5. Slice
Também podemos usar um intervalo para realizar o fatiamento. Para realizar uma fatia, apenas definimos umRange que nossa fatia deseja extrair:
@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)
}
A fatia pode subir ou descer.
Ao usarRanges, também podemos definir o tamanho do intervalo.
Usando umrange sem etapas e cortando além dos limites de uma coleção, criaremos muitos objetosnull em nosso resultadoList.
No entanto, ir além dos limites de uma coleção usandoa Range with steps pode acionar umArrayIndexOutOfBoundsException:
@Test
fun whenSlicingBeyondRangeOfArrayWithStep_thenOutOfBoundsException() {
assertThrows(ArrayIndexOutOfBoundsException::class.java) {
val original = arrayOf(12, 3, 34, 4)
original.slice(3..8 step 2)
}
}
6. Distinto
Outro filtro que veremos neste artigo é distinto. 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)
}
Também temos a opção de usar uma função seletora. The selector returns the value we’re going to evaluate for uniqueness.
Implementaremos uma pequena classe de dados chamadaSmallClass para explorar o trabalho com um objeto dentro do seletor:
data class SmallClass(val key: String, val num: Int)
usando uma matriz 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))
Podemos usar vários campos emdistinctBy:
val actual = original.distinctBy { it.key }
val expected = listOf(
SmallClass("key1", 1),
SmallClass("key2", 2),
SmallClass("key3", 3),
SmallClass("key4", 3),
SmallClass("er", 9))
A função não precisa retornar diretamente uma propriedade variável,we can also perform calculations to determine our distinct values.
Por exemplo, para números para cada intervalo de 10 (0 - 9, 10 - 19, 20-29, etc.), podemos arredondar para o próximo 10, e esse é o valor que nosso seletor:
val actual = array.distinctBy { Math.floor(it.num / 10.0) }
7. Em pedaços
Um recurso interessante do Kotlin 1.2 échunked. Chunking é pegar uma única coleçãoIterable e criar um novoList de pedaços que correspondam ao tamanho definido. This doesn’t work with Arrays; only Iterables.
Podemos dividir com apenas um tamanho de bloco para extrair:
@Test
fun givenDNAFragmentString_whenChunking_thenProduceListOfChunks() {
val dnaFragment = "ATTCGCGGCCGCCAA"
val fragments = dnaFragment.chunked(3)
assertIterableEquals(listOf("ATT", "CGC", "GGC", "CGC", "CAA"), fragments)
}
Ou um tamanho e um transformador:
@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)
}
O exemplo acima do seletor de fragmentos de DNA é extraído da documentação Kotlin emhere disponível em blocos.
Ao passarchunked um tamanho que não é um divisor do tamanho da nossa coleção. Nesses casos, o último elemento em nossa lista de blocos será simplesmente uma lista menor.
Tenha cuidado para não presumir que cada pedaço é do tamanho total e encontrar umArrayIndexOutOfBoundsException!
8. Conclusão
Todos os filtros Kotlin permitem aplicar lambdas para determinar se um item deve ser filtrado ou não. Not all of these functions can be used on Maps, entretanto, todas as funções de filtro que funcionam emMaps funcionarão emArrays.
A documentação das coleções do Kotlin nos fornece informações sobre se podemos usar uma função de filtro apenas em matrizes ou em ambas. A documentação pode ser encontradahere.
Como sempre, todos os exemplos estão disponíveisover on GitHub.