map, filter и преобразования

Учимся преобразовывать и отбирать элементы коллекций без ручных циклов.

map создаёт новую коллекцию, применяя функцию к каждому элементу, а filter — отбирает элементы, удовлетворяющие условию.

Циклы для обработки коллекций часто многословны. Kotlin предлагает декларативный стиль: вы описываете, что нужно сделать с элементами, а не как их перебирать.

map — преобразование

map проходит по коллекции и возвращает новую, где каждый элемент заменён результатом лямбды.

fun main() {
    val numbers = listOf(1, 2, 3, 4)
    val squares = numbers.map { it * it }
    println(squares)

    val names = listOf("аня", "боря")
    println(names.map { it.uppercase() })
}

Вывод:

[1, 4, 9, 16]
[АНЯ, БОРЯ]

filter — отбор

filter оставляет только элементы, для которых лямбда вернула true.

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6)
    val evens = numbers.filter { it % 2 == 0 }
    println(evens)
}

Вывод:

[2, 4, 6]

Цепочки операций

Операции возвращают новую коллекцию, поэтому их удобно соединять в цепочку: отфильтровать, преобразовать, отсортировать, взять первые несколько.

fun main() {
    val numbers = listOf(5, 3, 8, 1, 9, 2)
    val result = numbers
        .filter { it > 2 }
        .map { it * 10 }
        .sorted()
    println(result)
}

Вывод:

[30, 50, 80, 90]

Полезные операции

ОперацияЧто делает
sortedBy { ... }сортировка по ключу
take(n)первые n элементов
any { ... }есть ли хоть один подходящий
count { ... }сколько элементов подходит

Как работает под капотом

Каждая операция вроде map или filter создаёт новую коллекцию, не меняя исходную. В цепочке из нескольких операций промежуточные списки создаются на каждом шаге. Для коротких коллекций это незаметно, но для очень больших данных существуют Sequence — «ленивые» последовательности, обрабатывающие элементы по одному без промежуточных списков. Для учебных задач обычных операций над List вполне достаточно.

Частые ошибки

  • Ждать, что map изменит исходный список. Он возвращает новую коллекцию; исходная не меняется.
  • Путать map и filter. map преобразует каждый элемент, filter отбирает подходящие.
  • Писать вручную цикл там, где хватит одной операции. list.filter { ... } короче и понятнее.

Итог

  • map преобразует каждый элемент, filter отбирает по условию.
  • Операции возвращают новую коллекцию и соединяются в цепочки.
  • Исходная коллекция при этом не меняется.
  • Для очень больших данных есть ленивые Sequence.
Проверьте себя
1. Что вернёт listOf(1, 2, 3).map { it * 10 }?
A[1, 2, 3]
B[10, 20, 30]
C60
D[10]
2. В чём разница между map и filter?
Amap отбирает элементы, filter преобразует
Bmap преобразует каждый элемент, filter отбирает по условию
CОни делают одно и то же
Dmap работает только с числами
3. Изменяет ли map исходную коллекцию?
AДа, всегда
BНет, возвращает новую коллекцию
CТолько если она mutable
DЗависит от размера