Enumerable
The Enumerable module gives Ruby’s collections their power: map, select, reduce, group_by, each_with_object, and dozens more. Anything that defines each can include it — arrays, hashes, ranges, lazy streams, your own classes.
map, select, reduce, group_by, lazy
EXAMPLE
# 1) The basics — Enumerable methods on arrays, hashes, ranges
[1, 2, 3, 4].map { |n| n * n } # [1, 4, 9, 16]
[1, 2, 3, 4].select(&:even?) # [2, 4]
[1, 2, 3, 4].reject(&:even?) # [1, 3]
[1, 2, 3, 4].reduce(0) { |acc, n| acc + n } # 10
[1, 2, 3, 4].sum # 10 (Ruby 2.4+)
[1, 2, 3].each_with_index.to_a # [[1, 0], [2, 1], [3, 2]]
(1..5).to_a # [1, 2, 3, 4, 5]
('a'..'e').to_a # ['a', 'b', 'c', 'd', 'e']
# 2) Working with hashes
scores = { mara: 95, sam: 72, alex: 88, kim: 64 }
scores.map { |name, s| [name, s + 5] }.to_h # adds 5 to each
scores.select { |_, s| s >= 80 } # { mara: 95, alex: 88 }
scores.reject { |_, s| s >= 80 } # { sam: 72, kim: 64 }
scores.sort_by { |_, s| -s }.to_h # sorted desc by value
scores.max_by { |_, s| s } # [:mara, 95]
scores.min_by { |_, s| s } # [:kim, 64]
scores.count { |_, s| s >= 80 } # 2
scores.values.sum / scores.size # average
# 3) Group, partition, tally
words = %w[apple banana cherry date elderberry]
words.group_by(&:length)
# => { 5=>["apple"], 6=>["banana", "cherry"], 4=>["date"], 10=>["elderberry"] }
words.partition { |w| w.length > 5 }
# => [["banana", "cherry", "elderberry"], ["apple", "date"]]
%w[a b a c a b].tally
# => { "a"=>3, "b"=>2, "c"=>1 }
# 4) Reduce variations
[1, 2, 3, 4].reduce(:+) # 10 — symbol shortcut for binary op
[1, 2, 3, 4].inject(0) { |sum, n| sum + n } # alias for reduce
[1, 2, 3, 4].sum # cleaner for adding
[1, 2, 3].reduce { |a, b| a * b } # 6 — no initial value
# Computing min/max + index
[3, 1, 4, 1, 5, 9, 2, 6].each_with_index.min_by { |v, _| v } # [1, 1]
# 5) each_with_object — accumulator without remembering to return it
[1, 2, 3].each_with_object({}) { |n, h| h[n] = n * n }
# => {1=>1, 2=>4, 3=>9}
# Compare to reduce — note the closing block expression:
[1, 2, 3].reduce({}) { |h, n| h[n] = n * n; h } # also works, but ugly
# 6) zip / chunk / chunk_while / slice_when
[1, 2, 3].zip([4, 5, 6]) # [[1,4],[2,5],[3,6]]
[1, 2, 3].zip([4, 5, 6], [7, 8, 9]) # [[1,4,7],[2,5,8],[3,6,9]]
[1, 1, 2, 2, 3].chunk_while { |a, b| a == b }.to_a
# => [[1, 1], [2, 2], [3]]
[1, 2, 5, 6, 10].slice_when { |a, b| b - a > 1 }.to_a
# => [[1, 2], [5, 6], [10]]
# 7) flat_map — map + flatten one level
users = [{ name: 'mara', tags: %w[ops admin] }, { name: 'sam', tags: %w[admin reader] }]
users.flat_map { |u| u[:tags] }.uniq.sort # ["admin", "ops", "reader"]
# 8) any?, all?, none?, one? — short-circuit predicates
scores.values.any? { |s| s < 60 } # false
scores.values.all? { |s| s.is_a?(Integer) } # true
[].none? # true
[1, 2, 1].one? { |n| n == 2 } # true
# 9) Lazy — work with infinite sequences or huge inputs without materialising
primes = (2..Float::INFINITY).lazy.select do |n|
(2..Math.sqrt(n)).none? { |d| (n % d).zero? }
end
primes.first(10) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
# Without .lazy, the source range would try to materialise infinity.
# 10) Make YOUR class enumerable
class Polygon
include Enumerable
def initialize(*points); @points = points; end
def each(&block); @points.each(&block); end
end
p = Polygon.new([0, 0], [1, 0], [1, 1], [0, 1])
p.count # 4
p.map { |x, y| [x + 1, y] }
p.partition { |x, y| x.even? }
# Including Enumerable + defining each → you get all those methods for free.
# Add <=> and include Comparable for sort/min/max on instances.
# 11) Performance tips
# • Prefer specific methods (sum, min_by) over reduce variants — usually faster, clearer
# • Chain map/select/reduce; Ruby creates intermediate arrays unless you use lazy
# • count(&:even?) iterates the whole collection — use .any? to early-out
# • Hash lookups in a select block (whitelist.include?(x)) — convert whitelist to a Set first
# 12) Common bugs
# • map vs each — map returns a new array, each returns the receiver
# • inject with no initial value crashes on empty input
# • select on a Hash returns a Hash (Ruby 2.0+) — older code converted via .each_pair
# • Mutating the array you're iterating — undefined behaviour; collect changes and apply after
# • Forgetting .to_a on lazy chains when you actually need the array
# • sort returning a new array — the original is unchanged unless you use sort!
Why it matters
Lean on the specific Enumerable methods — group_by, tally, partition, each_with_object, flat_map, chunk_while — before reaching for raw reduce. They’re named for what they do, faster than the equivalent fold, and the chained pipeline reads top-to-bottom like a data pipeline rather than a puzzle.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
[1,2,3,4].select { |n| n.even? } # [2,4]
[1,2,3].map { |n| n * n } # [1,4,9]
[1,2,3].reduce(:+) # 6
Try it Yourself »
Exercise
Keep only odd numbers.
[1,2,3,4].
{ |n| n.odd? }
Six letters.
Discussion
Loading…