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
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
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
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
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:
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
.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.