Arrays & Hashes Deep Dive
๐งฎ Arrays & Hashes Deep Dive
yield and blocks weren't an isolated feature โ they're the mechanism behind almost every method covered in this chapter. Ruby's Enumerable module gives both Array and Hash a shared set of block-powered methods for transforming, filtering, and reducing collections, and idiomatic Ruby leans on them far more heavily than PHP or Python lean on their equivalents.
๐งฉ One Module, Two Collection Types
Both Array and Hash include a module called Enumerable (modules get their own full chapter later, but the short version: a module is a bundle of methods that gets mixed into a class). That's why map, select, and reduce all work the same way on an array and a hash โ they're not separately implemented for each type, they're inherited from the same shared source.
๐ map โ Transform Every Element
numbers = [1, 2, 3, 4] doubled = numbers.map { |n| n * 2 } puts doubled # => [2, 4, 6, 8] -- a brand new array, numbers is untouched
map (aliased collect) runs the block once per element and returns a new array built from the block's return values โ the same idea as PHP's array_map or a Python list comprehension, just spelled as a method call with a block instead of a separate function or comprehension syntax.
๐ select and reject โ Filter Elements
numbers = [1, 2, 3, 4, 5, 6] numbers.select { |n| n.even? } # => [2, 4, 6] -- keep where block is true numbers.reject { |n| n.even? } # => [1, 3, 5] -- keep where block is false, the opposite
select (aliased filter) keeps elements where the block returns something truthy; reject is its exact opposite. Neither PHP nor Python has a clean one-word "opposite of filter" โ you'd write array_filter($arr, fn($n) => !($n % 2 == 0)) in PHP, or [n for n in nums if n % 2 != 0] in Python, negating the condition yourself either way.
โ reduce (aka inject) โ Fold to a Single Value
numbers = [1, 2, 3, 4] total = numbers.reduce(0) { |sum, n| sum + n } puts total # => 10 # Shorthand: pass a symbol for the operator directly numbers.reduce(:+) # => 10 -- same result, no block needed at all
reduce (Ruby also accepts the alias inject) takes a starting value and a block that combines each element into a running accumulator โ directly comparable to PHP's array_reduce or Python's functools.reduce. The reduce(:+) shorthand โ passing a symbol naming the operator instead of a block โ is Ruby-specific and worth recognizing even before it makes intuitive sense.
๐ข each_with_index โ Iterate With a Counter
fruits = ["apple", "banana", "cherry"] fruits.each_with_index do |fruit, index| puts "#{index}: #{fruit}" end # 0: apple # 1: banana # 2: cherry
Iterating With an Index: PHP vs Python vs Ruby
| PHP | Python | Ruby | |
|---|---|---|---|
| Syntax | foreach ($arr as $i => $v) | for i, v in enumerate(arr) | arr.each_with_index do |v, i| |
| Order of pair | index, value | index, value | value, index (reversed!) |
each_with_index yields |value, index| โ value first, index second. Coming from enumerate() or $i => $v, it's extremely easy to write |index, fruit| out of habit and get confused when the output looks scrambled. Double-check the order any time this method misbehaves.
โ๏ธ Method Chaining
Because every Enumerable method returns a new collection (or a single value, for reduce), they chain together into a readable pipeline โ arguably the single most idiomatic thing you can do with Ruby collections:
numbers = [1, 2, 3, 4, 5, 6] result = numbers .select { |n| n.even? } .map { |n| n * 10 } .reduce(:+) puts result # => 120 ([2,4,6] -> [20,40,60] -> summed)
PHP and Python can chain similarly (Python especially, with generator expressions), but neither reads quite as close to plain English as a Ruby Enumerable chain โ select, then map, then reduce, each step legible on its own line.
๐๏ธ Hash Iteration
prices = { apple: 1.50, banana: 0.75, cherry: 3.00 } prices.each do |item, price| puts "#{item}: $#{price}" end # map on a Hash returns an Array by default expensive = prices.select { |item, price| price > 1 } puts expensive # => {apple: 1.5, cherry: 3.0} -- select keeps Hash shape
.each do |key, value| destructures each pair automatically โ the block receives both the symbol key and its value in one step, similar to Python's .items() but without needing to call a separate method to get pairs in the first place (PHP's foreach ($assoc as $key => $value) is the closest direct match).
๐ Enumerable Method Equivalents
PHP vs Python vs Ruby
| Operation | PHP | Python | Ruby |
|---|---|---|---|
| Transform | array_map() | map() / comprehension | .map |
| Filter | array_filter() | filter() / comprehension | .select |
| Filter (inverse) | Negate the condition manually | Negate the condition manually | .reject |
| Fold to one value | array_reduce() | functools.reduce() | .reduce / .inject |
| Index while iterating | $i => $v | enumerate() | .each_with_index |
๐ป Coding Challenges
Challenge 1: A select + map Pipeline
Given an array of integers from 1 to 20, chain .select and .map to produce a new array containing only the multiples of 3, each one squared. Print the result.
Goal: Practice chaining two Enumerable methods into one readable pipeline.
Challenge 2: reduce for a Shopping Total
Given an array of hashes, each with :item and :price keys, use .reduce to compute the total price of everything in the array. Try it both with an explicit block and with the reduce(:+) shorthand after first extracting just the prices with .map.
Goal: See reduce used on real-shaped data, not just a flat array of numbers.
Challenge 3: Hash Iteration and Filtering
Given a hash of student names (symbol keys) to test scores (integer values), use .each to print every student's pass/fail status (pass is 60+), then use .select to build a new hash containing only the students who passed.
Goal: Practice destructuring hash pairs in a block and filtering a hash while keeping its shape.
If you catch yourself writing result = [] followed by a .each loop that pushes into it, pause โ that's very often a .map or .select in disguise. Idiomatic Ruby treats the manual accumulator pattern as a code smell precisely because Enumerable already has a named, chainable method for almost every common transformation.
๐ฏ What's Next
Next chapter: Classes & Objects โ defining your own classes, initialize, instance variables, and the attr_accessor family of shortcuts.