map и filter: преобразование данных
Две операции — map и filter — заменяют большинство циклов и делают код похожим на описание намерения.
«Не „как пройти по списку“, а „что я хочу получить“ — вот разница между циклом и map.»
map применяет функцию к каждому элементу и собирает результаты в новую коллекцию. filter оставляет только те элементы, для которых функция вернула true. Оба — функции высшего порядка, оба не меняют исходную коллекцию.
val nums = List(1, 2, 3, 4, 5)
val doubled = nums.map(x => x * 2) // List(2, 4, 6, 8, 10)
val evens = nums.filter(x => x % 2 == 0) // List(2, 4)
val squares = nums.map(x => x * x) // List(1, 4, 9, 16, 25)Можно использовать краткую запись с _: nums.map(_ * 2).
Цепочки операций
Сила в том, что операции соединяются в цепочку, и каждая описывает один шаг преобразования.
val nums = List(1, 2, 3, 4, 5, 6)
val result = nums
.filter(_ % 2 == 0) // оставить чётные -> 2, 4, 6
.map(_ * 10) // умножить на 10 -> 20, 40, 60
println(result) // List(20, 40, 60)Сравнение с обычным циклом
ЦИКЛ (как): MAP/FILTER (что):
res = [] nums
for x in nums: .filter(_ % 2 == 0)
if x % 2 == 0: .map(_ * 10)
res.add(x * 10)
-> вручную и многословно -> декларативно и краткоmap на Map и Set
val ages = Map("Аня" -> 25, "Иван" -> 30)
val older = ages.map((name, age) => (name, age + 1))
println(older) // Map(Аня -> 26, Иван -> 31)Та же идея на Python ▶
# map и filter на Python
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums)) # [2,4,6,8,10]
evens = list(filter(lambda x: x % 2 == 0, nums)) # [2,4]
# идиоматичнее — генераторы списков
doubled = [x * 2 for x in nums]
evens = [x for x in nums if x % 2 == 0]
# цепочка: чётные, затем *10
result = [x * 10 for x in nums if x % 2 == 0]
print(result) # [20, 40]Как работает под капотом (JVM)
map и filter внутри обходят коллекцию и строят новую через builder. Функция, которую вы передаёте, — это объект Function1 с методом apply, вызываемый для каждого элемента (как мы видели в разделе про функции). Для List результат — новый связный список. Важно: каждая операция в цепочке создаёт промежуточную коллекцию. Для больших данных это бывает накладно — тогда используют view или LazyList, чтобы вычислять лениво и не создавать промежутки.
Частые ошибки
- Путать
mapиforeach.mapсобирает результаты,foreachтолько выполняет действие и возвращаетUnit. - Побочные эффекты внутри
map.mapдля преобразования, а не для печати; для эффектов естьforeach. - Длинные цепочки на огромных коллекциях. Множество промежуточных коллекций — повод задуматься о
view.
Best practices
- Предпочитайте
map/filterручным циклам — код читается как описание задачи. - Соединяйте операции в цепочки, каждая — один понятный шаг.
- Для тяжёлых данных рассмотрите ленивые
view, чтобы избежать промежуточных коллекций.
От «как» к «что»: сдвиг мышления
Переход от циклов к map и filter — это не просто другой синтаксис, а другой способ думать о задаче. Цикл описывает механику: заведи аккумулятор, пройди по индексам, на каждом шаге проверь и добавь. map и filter описывают намерение: «преобразуй каждый элемент так», «оставь подходящие под это условие». Читатель кода сразу видит цель, а не реконструирует её из деталей итерации.
Цепочки операций усиливают этот эффект. Каждое звено выполняет один понятный шаг, и вся трансформация читается сверху вниз как рецепт. Это резко снижает количество ошибок: в цикле легко перепутать границы или забыть инициализацию, а в декларативной цепочке каждый шаг изолирован и проверяем. Когда данные большие, на помощь приходят ленивые представления, которые позволяют сохранить ту же читаемость без создания промежуточных коллекций.
Маленькая, но важная привычка — выбирать правильную операцию по намерению: map, когда нужно преобразовать каждый элемент с сохранением количества; filter, когда нужно отобрать; foreach, когда нужен только побочный эффект. Чёткое разделение этих ролей делает цепочки преобразований предсказуемыми, а код — понятным с первого взгляда.
Итоги. map преобразует каждый элемент, filter отбирает по условию; они соединяются в декларативные цепочки и не меняют исходные данные. Дальше — свёртка через fold и reduce.