Advanced Collections
Ruby's Enumerable methods are incredibly powerful. Once you learn them, you'll use them everywhere. They let you transform, filter, and process data with clean, readable code. Let's dive into the most useful ones.
numbers = [1, 2, 3, 4, 5]
# map transforms each element
squares = numbers.map { |n| n ** 2 }
puts squares.inspect # => [1, 4, 9, 16, 25]
# select keeps elements that match
evens = numbers.select { |n| n.even? }
puts evens.inspect # => [2, 4]
# reject removes elements that match
odds = numbers.reject { |n| n.even? }
puts odds.inspect # => [1, 3, 5]
Try it Yourself ->
reduce and inject
reduce (also called inject) combines all elements into a single value. It takes a starting value and a block that accumulates the result. This is perfect for sums, products, or any aggregation.
numbers = [1, 2, 3, 4, 5]
# Sum with initial value
total = numbers.inject(0) { |sum, n| sum + n }
puts total # => 15
# Product
product = numbers.inject(1) { |prod, n| prod * n }
puts product # => 120
# Find the longest word
words = ["ruby", "is", "awesome", "really"]
longest = words.inject { |memo, word| word.length > memo.length ? word : memo }
puts longest # => "awesome"
# Using symbols with inject
puts numbers.inject(:+) # => 15
puts numbers.inject(:*) # => 120
Try it Yourself ->
flat_map and group_by
flat_map maps and flattens the result in one step. group_by organizes elements into a hash based on a block's return value. Both are incredibly useful for real data processing.
# flat_map: map + flatten
sentences = ["hello world", "ruby is fun"]
words = sentences.flat_map { |s| s.split(" ") }
puts words.inspect # => ["hello", "world", "ruby", "is", "fun"]
# group_by: organize by criteria
people = [
{ name: "Alice", dept: "Engineering" },
{ name: "Bob", dept: "Marketing" },
{ name: "Charlie", dept: "Engineering" },
{ name: "Diana", dept: "Marketing" }
]
by_dept = people.group_by { |p| p[:dept] }
puts by_dept.keys.inspect # => ["Engineering", "Marketing"]
puts by_dept["Engineering"].inspect
# => [{name: "Alice", ...}, {name: "Charlie", ...}]
Try it Yourself ->
Method Chaining
The real power of Ruby collections is chaining. You can chain map, select, reject, and other methods together to create data pipelines. Each method returns an array, so you can keep going.
orders = [
{ product: "Widget", amount: 25, status: "shipped" },
{ product: "Gadget", amount: 50, status: "pending" },
{ product: "Widget", amount: 75, status: "shipped" },
{ product: "Gizmo", amount: 100, status: "shipped" }
]
# Get total of shipped Widget orders
total = orders
.select { |o| o[:product] == "Widget" }
.select { |o| o[:status] == "shipped" }
.map { |o| o[:amount] }
.sum
puts total # => 100
# Sort by, zip, min, max
names = ["Charlie", "Alice", "Bob"]
sorted = names.sort_by { |n| n.length }
puts sorted.inspect # => ["Bob", "Alice", "Charlie"]
scores = [85, 92, 78, 95, 88]
puts scores.min # => 78
puts scores.max # => 95
puts scores.min_by { |s| -s } # => 95 (max via min_by)
Try it Yourself ->
uniq, compact, flatten
These utility methods clean up your arrays. uniq removes duplicates. compact removes nil values. flatten converts nested arrays into a single flat array.
# uniq: remove duplicates
colors = ["red", "blue", "red", "green", "blue"]
puts colors.uniq.inspect # => ["red", "blue", "green"]
# compact: remove nils
data = [1, nil, 2, nil, 3]
puts data.compact.inspect # => [1, 2, 3]
# flatten: nested arrays become flat
nested = [[1, 2], [3, [4, 5]], 6]
puts nested.flatten.inspect # => [1, 2, 3, 4, 5, 6]
puts nested.flatten(1).inspect # => [1, 2, 3, [4, 5], 6]
# Combining them all
messy = [[1, 2], nil, [2, 3], nil, [3, 4]]
clean = messy.compact.uniq.flatten
puts clean.inspect # => [1, 2, 3, 4]
Try it Yourself ->