iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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? }

Test yourself

Q1. select returns…
Q2. map returns…
Q3. reduce(:+) computes…

Discussion

Loading…