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.