В чём разница между map, collect, select, reject, find и each?
each итерирует для побочных эффектов. map возвращает новый массив трансформированных значений. select/reject фильтруют. find возвращает первый подходящий элемент или nil.
Обзор методов
Все перечисленные методы — часть модуля Enumerable (кроме each, который определяется в самой коллекции). Ключевое правило: выбирайте метод по намерению, а не только по результату.
each — побочные эффекты
users = [{ name: 'Alice', role: 'admin' }, { name: 'Bob', role: 'user' }]
users.each do |u|
puts "Processing #{u[:name]}"
AuditLog.record(u) # побочный эффект
end
# => возвращает исходный массив users
each возвращает исходную коллекцию. Используйте его только ради побочных эффектов (запись в БД, логирование, отправка события).
map / collect — трансформация
names = users.map { |u| u[:name] }
# => ["Alice", "Bob"]
ids = users.map.with_index(1) { |u, i| "#{i}. #{u[:name]}" }
# => ["1. Alice", "2. Bob"]
map и collect — псевдонимы. Всегда создают новый массив той же длины. Если нужно изменить исходный — map!.
select / filter — фильтрация
admins = users.select { |u| u[:role] == 'admin' }
# => [{ name: 'Alice', role: 'admin' }]
# Эквивалент:
admins = users.filter { |u| u[:role] == 'admin' } # Ruby 2.6+
Возвращает новый массив элементов, для которых block вернул truthy-значение. select! изменяет исходный массив.
reject — обратный select
non_admins = users.reject { |u| u[:role] == 'admin' }
# => [{ name: 'Bob', role: 'user' }]
find / detect — первый подходящий элемент
first_admin = users.find { |u| u[:role] == 'admin' }
# => { name: 'Alice', role: 'admin' }
missing = users.find { |u| u[:role] == 'superadmin' }
# => nil
# С default-значением через Proc:
missing = users.find(-> { { name: 'Unknown' } }) { |u| u[:role] == 'superadmin' }
# => { name: 'Unknown' }
find и detect — псевдонимы. Останавливаются при первом совпадении. Возвращают элемент или nil.
Сравнительная таблица
each→ исходная коллекция, используйте для I/O и побочных эффектов.map→ новый массив той же длины, для трансформации каждого элемента.select→ подмножество, для фильтрации по условию (truthy).reject→ подмножество, для фильтрации по условию (falsy).find→ один элемент или nil, для поиска первого совпадения.
Производительность и lazy
# Без lazy — обработает всё:
(1..Float::INFINITY).select { |n| n.odd? }.first(5) # зависнет!
# С lazy — остановится вовремя:
(1..Float::INFINITY).lazy.select { |n| n.odd? }.first(5)
# => [1, 3, 5, 7, 9]
Подводные камни
- each вместо map — если внутри
eachвы собираете массив через<<, замените наmap. Код чище, Ruby оптимизирует аллокацию. - map.flatten(1) vs flat_map —
flat_mapэффективнее и читаемее, когда block возвращает массив. - select + first vs find —
array.select { }.firstпроходит весь массив,findостанавливается. Для больших коллекций разница существенная. - Мутация внутри each — изменение коллекции во время итерации
each— undefined behavior. Используйтеselect!/reject!или соберите изменения отдельно. - collect — устаревшее имя —
collect— алиасmapиз Smalltalk-наследия. В современном Ruby-коде пишитеmap. - find возвращает nil, не [] — не путайте с
select:findвозвращает один объект или nil,select— всегда массив. - Лямбда как default у find — аргумент-callable (не значение) вызывается если ничего не найдено:
find(lambda { default }).
Common mistakes
- Сводить enumerable iteration methods к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: объектная динамическая модель Ruby, где почти всё является объектом, а методы ищутся через ancestor chain.
- Не отделять validation, authorization, transaction boundary и business logic.
- Менять похожие API местами без учёта семантики ошибок и ownership.
What the interviewer is testing
- Объясняет enumerable iteration methods через конкретную точку lifecycle в Ruby.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.