Обработка коллекций: map, where, и итерируемые

Вместо ручных циклов Dart даёт цепочки методов, которые превращают одну коллекцию в другую коротко и наглядно.

Суть: map преобразует каждый элемент, where фильтрует, reduce/fold сворачивают коллекцию в одно значение. Это функциональный стиль — меньше кода, меньше ошибок.

Помимо трёх главных методов, в арсенале есть множество удобных помощников. firstWhere находит первый подходящий элемент, any и every проверяют, выполняется ли условие хотя бы для одного или для всех элементов, sort упорядочивает, а expand разворачивает вложенные списки в один плоский. Все они следуют той же философии: вы описываете, что хотите получить, а не как это перебрать вручную.

Допустим, у вас список цен, и нужно оставить только дорогие товары и прибавить к каждому налог. Можно написать цикл с временным списком и add, а можно одной строкой: prices.where((p) => p > 1000).map((p) => p * 1.2). Второй вариант читается как предложение и не оставляет места для опечаток в индексах.

final numbers = [1, 2, 3, 4, 5, 6];

// map — преобразовать каждый элемент
final doubled = numbers.map((n) => n * 2).toList();
// [2, 4, 6, 8, 10, 12]

// where — оставить подходящие
final evens = numbers.where((n) => n % 2 == 0).toList();
// [2, 4, 6]

// цепочка: чётные, затем удвоить
final result = numbers.where((n) => n % 2 == 0)
                      .map((n) => n * 10)
                      .toList();
// [20, 40, 60]

// reduce / fold — свернуть в одно значение
final sum = numbers.reduce((a, b) => a + b);  // 21

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

Методы map и where ленивы: они не создают новый список сразу, а возвращают Iterable — описание будущих вычислений. Реальная работа происходит, только когда вы вызываете .toList() или перебираете результат. Это экономит память при длинных цепочках. Поэтому в коде вы почти всегда видите .toList() в конце — он материализует результат.

  [1,2,3,4,5,6]
       |
       v  .where((n) => n % 2 == 0)     фильтр
  [2, 4, 6]   (пока Iterable, лениво)
       |
       v  .map((n) => n * 10)            преобразование
  [20, 40, 60]  (всё ещё Iterable)
       |
       v  .toList()                       материализация
  готовый List [20, 40, 60]

Во Flutter это критично: чтобы из списка данных получить список виджетов, вы пишете items.map((item) => Text(item)).toList(). Так данные превращаются в интерфейс одной строкой.

# Те же операции на Python: фильтр + преобразование + свёртка
numbers = [1, 2, 3, 4, 5, 6]

doubled = [n * 2 for n in numbers]                 # map
evens   = [n for n in numbers if n % 2 == 0]       # where
chained = [n * 10 for n in numbers if n % 2 == 0]  # where + map

print('doubled:', doubled)
print('evens:  ', evens)
print('chained:', chained)

total = 0
for n in numbers:        # аналог reduce/fold
    total += n
print('sum:', total)

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

  • Забыть .toList() там, где Flutter ждёт список (например, в children) — получите ошибку типа Iterable.
  • Менять исходную коллекцию внутри map. Эти методы должны быть «чистыми» — только возвращать новое значение, не трогая внешнее.
  • reduce на пустой коллекции бросает исключение; для безопасности используйте fold с начальным значением.

Best practices

  • Предпочитайте map/where ручным циклам — код становится декларативным и читается сверху вниз.
  • Используйте spread-оператор ... и ...? для слияния списков: [...a, ...b].
  • Длинные цепочки разбивайте по строкам с одним методом на строку — так видно каждый шаг.

Особенно важна чистота преобразований. Функция внутри map или where не должна менять внешние переменные или исходную коллекцию — она лишь вычисляет новое значение по входному. Такой стиль называют функциональным, и он резко снижает число коварных багов, потому что данные не меняются исподтишка. Во Flutter это особенно ценно: один и тот же список данных порождает предсказуемый список виджетов на каждой перерисовке.

Итог: функциональная обработка коллекций — главный инструмент превращения данных в интерфейс. Запомните связку where().map().toList() — вы будете писать её в каждом втором экране Flutter.

Проверьте себя
1. Что делает метод where в коллекции?
AПреобразует каждый элемент
BОставляет только элементы, удовлетворяющие условию
CСворачивает список в число
DСортирует элементы
2. Зачем в конце цепочки .map().where() обычно вызывают .toList()?
AЧтобы отсортировать
BЧтобы материализовать ленивый Iterable в готовый список
CЧтобы удалить дубликаты
DЭто необязательно и бесполезно