Преобразование коллекций
Стандартная библиотека Kotlin предоставляет целый набор функций-расширений для преобразования коллекций. Они создают новые коллекции из существующих на основе предоставляемых правил преобразования. В этом разделе находится общий обзор подобных функций.
Map
Группа функций, корнем которых является map() . Она позволяет преобразовать исходную коллекцию путём применения заданной лямбда-функции к каждому её элементу, объединяя результаты в новую коллекцию. При этом порядок элементов сохраняется. Этот процесс преобразования также называют маппингом (ориг. mapping). Если для преобразования коллекции вам требуется знать индекс элементов, то используйте функцию mapIndexed() .
fun main() < val numbers = setOf(1, 2, 3) println(numbers.map < it * 3 >) // [3, 6, 9] println(numbers.mapIndexed < idx, value ->value * idx >) // [0, 2, 6] >
Если какой-либо элемент или элементы могут быть преобразованы в значение равное null , то вместо map() можно использовать функцию mapNotNull() , которая отфильтрует такие элементы и они не попадут в новую коллекцию. Соответственно, вместо mapIndexed() можно использовать mapIndexedNotNull() .
fun main() < val numbers = setOf(1, 2, 3) println(numbers.mapNotNull < if ( it == 2) null else it * 3 >) // [3, 9] println(numbers.mapIndexedNotNull < idx, value ->if (idx == 0) null else value * idx >) // [2, 6] >
Ассоциативные списки можно преобразовывать двумя способами: преобразовывать ключи, не изменяя значения, и наоборот. Для преобразования ключей используйте функцию mapKeys() ; в свою очередь mapValues() преобразует значения. Они обе используют функцию преобразования, которая в качестве аргумента принимает пару «ключ-значение», поэтому вы можете работать как с ключом, так и со значением.
fun main() < val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11) println(numbersMap.mapKeys < it.key.uppercase() >) // println(numbersMap.mapValues < it.value + it.key.length >) // >
Zip
Данная группа функций преобразования — zipping — берёт два списка и создаёт из их элементов пары. При этом пары создаются из элементов с одинаковыми индексами. В стандартной библиотеке Kotlin для такого преобразования есть функция-расширение zip() .
Если функцию zip() вызвать для одной коллекции и при этом передать ей в качестве аргумента другую коллекцию, то в результате вы получите список типа List> . Элементы коллекции-получателя будут первыми элементами в этих объектах Pair .
Если коллекции имеют разные размеры, то zip() вернёт новую коллекцию, длина которой равняется минимальной из исходных коллекций; последние элементы бОльшей коллекции будут отсечены.
Также zip() можно вызывать в инфиксной форме a zip b .
Вместе с коллекцией zip() можно передать функцию преобразования, которая принимает два параметра: элемент коллекции-получателя и элемент коллекции-аргумента, при этом оба элемента располагаются в своих коллекциях по одинаковому индексу. В этом случае вернётся List с содержимым из результатов вычисления функции преобразования.
fun main() < val colors = listOf("red", "brown", "grey") val animals = listOf("fox", "bear", "wolf") println(colors.zip(animals) < color, animal ->"The $> is $color" >) // [The Fox is red, The Bear is brown, The Wolf is grey] >
Для списка типа List> можно выполнить обратное преобразование — unzipping, которое разбирает пары на два отдельных списка:
- В первый список помещаются первые элементы каждого объекта Pair .
- Во второй список помещаются вторые элементы каждого объекта Pair .
Чтобы «распаковать» такой список вызовите для него функцию unzip() .
Associate
Функции преобразования данного типа позволяют создавать ассоциативные списки из элементов коллекции и связанных с ними значений. При этом элементы могут быть как ключами ассоциативного списка, так и значениями.
Основная функция здесь — associateWith() . Она создаёт Map , в которой элементы исходной коллекции являются ключами, а значения вычисляются с помощью функции преобразования. Если встречаются два равных элемента, то в ассоциативный список попадёт только последний из них.
Для создания ассоциативного списка, в котором элементы коллекции будут выступать в роли значений, существует функция associateBy() . Она принимает функцию, которая в свою очередь возвращает ключ, основанный на значении элемента. Если встречаются два равных элемента, то в ассоциативный список попадёт только последний из них.
Также associateBy() можно вызвать с функцией, преобразующей значения.
fun main() < val numbers = listOf("one", "two", "three", "four") println(numbers.associateBy < it.first().uppercaseChar() >) // println(numbers.associateBy( keySelector = < it.first().uppercaseChar() >, valueTransform = < it.length >)) // >
Есть еще один способ создания ассоциативного списка, при котором ключи и значения тем или иным образом создаются на основе элементов коллекции — при помощи функции associate() . Она принимает лямбда-функцию, которая возвращает объект Pair . Этот объект и представляет собой пару «ключ-значение».
Обратите внимание, что associate() создаёт недолговечные объекты Pair , которые могут повлиять на производительность. Поэтому используйте associate() только тогда, когда производительность для вас не критична, либо если эта функция предпочтительнее остальных.
Примером последнего является ситуация, когда ключ и значение для него создаются одновременно на основе элемента.
fun main() < data class FullName (val firstName: String, val lastName: String) fun parseFullName(fullName: String): FullName < val nameParts = fullName.split(" ") if (nameParts.size == 2) < return FullName(nameParts[0], nameParts[1]) >else throw Exception("Wrong name format") > val names = listOf("Alice Adams", "Brian Brown", "Clara Campbell") println(names.associate < name ->parseFullName(name).let < it.lastName to it.firstName >>) // >
В примере выше сначала вызывается функция преобразования элемента, а затем происходит создание пары на основе полученного от этой функции результата.
Flatten
При работе с вложенными коллекциями вам могут пригодиться функции, обеспечивающие «плоский» доступ к их элементам.
Первая функция — flatten() . Её можно использовать для коллекции, содержащей другие коллекции, например, для List , в котором находятся Set -ы. Функция возвращает объединённый List , состоящий из всех элементов всех вложенных коллекций.
Другая функция — flatMap() , которая обеспечивает гибкий способ работы с вложенными коллекциями. Она принимает функцию, которая маппит элемент исходной коллекции в другую коллекцию. В качестве результата flatMap() возвращает объединённый список из всех обработанных элементов. По сути flatMap() ведёт себя как вызов map() (с возвращением коллекции в качестве результата маппинга) и flatten() .
data class StringContainer(val values: List) fun main() < val containers = listOf( StringContainer(listOf("one", "two", "three")), StringContainer(listOf("four", "five", "six")), StringContainer(listOf("seven", "eight")) ) println(containers.flatMap < it.values >) // [one, two, three, four, five, six, seven, eight] >
Преобразование коллекции в String
С помощью функций joinToString() и joinTo() содержимое коллекции можно преобразовать в читаемый вид — String .
joinToString() на основе предоставленных аргументов объединяет элементы коллекции в строку. joinTo() делает то же самое, но добавляет результат к переданному объекту Appendable .
При вызове этих функций с аргументами по умолчанию, они вернут результат, аналогичный вызову toString() для коллекции: элементы будут представлены в виде String , разделённые между собой запятой с пробелом.
Но также вы можете кастомизировать строковое представление коллекции, указав необходимые вам параметры при помощи специальных аргументов — separator , prefix , и postfix . Преобразованная строка будет начинаться с префикса ( prefix ) и заканчиваться постфиксом ( postfix ). А separator будет добавлен после каждого элемента, кроме последнего.
Если коллекция большая, то вы можете указать limit — количество элементов, которые будут включены в результат. При этом все элементы, превышающие limit , будут заменены одним значением аргумента truncated .
fun main() < val numbers = (1..100).toList() println(numbers.joinToString( limit = 10, truncated = "") ) // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, >
Ну и наконец, чтобы кастомизировать представление самих элементов, передайте в качестве аргумента функцию transform .
fun main() < val numbers = listOf("one", "two", "three", "four") println(numbers.joinToString < "Element: $" >) // Element: ONE, Element: TWO, Element: THREE, Element: FOUR >
Collection transformation operations
The Kotlin standard library provides a set of extension functions for collection transformations. These functions build new collections from existing ones based on the transformation rules provided. In this page, we’ll give an overview of the available collection transformation functions.
Map
The mapping transformation creates a collection from the results of a function on the elements of another collection. The basic mapping function is map() . It applies the given lambda function to each subsequent element and returns the list of the lambda results. The order of results is the same as the original order of elements. To apply a transformation that additionally uses the element index as an argument, use mapIndexed() .
If the transformation produces null on certain elements, you can filter out the null s from the result collection by calling the mapNotNull() function instead of map() , or mapIndexedNotNull() instead of mapIndexed() .
When transforming maps, you have two options: transform keys leaving values unchanged and vice versa. To apply a given transformation to keys, use mapKeys() ; in turn, mapValues() transforms values. Both functions use the transformations that take a map entry as an argument, so you can operate both its key and value.
Zip
Zipping transformation is building pairs from elements with the same positions in both collections. In the Kotlin standard library, this is done by the zip() extension function.
When called on a collection or an array with another collection (or array) as an argument, zip() returns the List of Pair objects. The elements of the receiver collection are the first elements in these pairs.
If the collections have different sizes, the result of the zip() is the smaller size; the last elements of the larger collection are not included in the result.
zip() can also be called in the infix form a zip b .
You can also call zip() with a transformation function that takes two parameters: the receiver element and the argument element. In this case, the result List contains the return values of the transformation function called on pairs of the receiver and the argument elements with the same positions.
When you have a List of Pair s, you can do the reverse transformation – unzipping – that builds two lists from these pairs:
- The first list contains the first elements of each Pair in the original list.
- The second list contains the second elements.
To unzip a list of pairs, call unzip() .
Associate
Association transformations allow building maps from the collection elements and certain values associated with them. In different association types, the elements can be either keys or values in the association map.
The basic association function associateWith() creates a Map in which the elements of the original collection are keys, and values are produced from them by the given transformation function. If two elements are equal, only the last one remains in the map.
For building maps with collection elements as values, there is the function associateBy() . It takes a function that returns a key based on an element’s value. If two elements’ keys are equal, only the last one remains in the map.
associateBy() can also be called with a value transformation function.
Another way to build maps in which both keys and values are somehow produced from collection elements is the function associate() . It takes a lambda function that returns a Pair : the key and the value of the corresponding map entry.
Note that associate() produces short-living Pair objects which may affect the performance. Thus, associate() should be used when the performance isn’t critical or it’s more preferable than other options.
An example of the latter is when a key and the corresponding value are produced from an element together.
Here we call a transform function on an element first, and then build a pair from the properties of that function’s result.
Flatten
If you operate nested collections, you may find the standard library functions that provide flat access to nested collection elements useful.
The first function is flatten() . You can call it on a collection of collections, for example, a List of Set s. The function returns a single List of all the elements of the nested collections.
Another function – flatMap() provides a flexible way to process nested collections. It takes a function that maps a collection element to another collection. As a result, flatMap() returns a single list of its return values on all the elements. So, flatMap() behaves as a subsequent call of map() (with a collection as a mapping result) and flatten() .
String representation
If you need to retrieve the collection content in a readable format, use functions that transform the collections to strings: joinToString() and joinTo() .
joinToString() builds a single String from the collection elements based on the provided arguments. joinTo() does the same but appends the result to the given Appendable object.
When called with the default arguments, the functions return the result similar to calling toString() on the collection: a String of elements’ string representations separated by commas with spaces.
To build a custom string representation, you can specify its parameters in function arguments separator , prefix , and postfix . The resulting string will start with the prefix and end with the postfix . The separator will come after each element except the last.
For bigger collections, you may want to specify the limit – a number of elements that will be included into result. If the collection size exceeds the limit , all the other elements will be replaced with a single value of the truncated argument.
Finally, to customize the representation of elements themselves, provide the transform function.