Arrays & Hashes Deep Dive

Ruby Fundamentals — Arrays & Hashes Deep Dive
Ruby Fundamentals
Course 1 · Chapter 5 · Arrays & Hashes Deep Dive

🧮 Arrays & Hashes Deep Dive

Chapter 4's 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

PHPPythonRuby
Syntaxforeach ($arr as $i => $v)for i, v in enumerate(arr)arr.each_with_index do |v, i|
Order of pairindex, valueindex, valuevalue, index (reversed!)
⚠ The argument order is reversed from PHP/Python

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

OperationPHPPythonRuby
Transformarray_map()map() / comprehension.map
Filterarray_filter()filter() / comprehension.select
Filter (inverse)Negate the condition manuallyNegate the condition manually.reject
Fold to one valuearray_reduce()functools.reduce().reduce / .inject
Index while iterating$i => $venumerate().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.

→ Solution

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.

→ Solution

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.

→ Solution

💎 Reach for Enumerable Before a Manual Loop

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.