Arrays, hashes and ranges

Ruby · Essentials · 12 min

Nearly all Ruby data, from JSON to Rails params, is handled as arrays and hashes.

An Array is an ordered, growable list of any objects: nums = [3, 1, 2]. Index from the front or back (nums[0], nums[-1]), slice with nums[0, 2] or nums[1..], and add with push/<<, unshift, pop, shift. Out-of-range reads return nil; fetch(i) raises instead. %w[a b c] builds an array of strings.

A Hash maps keys to values and keeps insertion order. With symbol keys, use the shorthand { name: "Ada", age: 36 }; other keys use =>: { "id" => 1 }. Note that :name and "name" are different keys, a common source of nil surprises with parsed JSON.

  • h[:missing] returns nil (or the hash's default); h.fetch(:k) raises KeyError, and h.fetch(:k, 0) gives a fallback.
  • Hash.new(0) makes missing keys read as 0, perfect for counters. For mutable defaults use the block form Hash.new { |h, k| h[k] = [] }.
  • key?, keys, values, delete, merge, dig(:a, :b) and to_a are everyday tools.

Ranges like 1..5 (inclusive) and 1...5 (exclusive end) represent intervals; endless 1.. and beginless ..10 work too. Convert with to_a, test membership with include? or cover?.

Destructure arrays with first, *rest = list. Since Ruby 3.1 you can omit a hash value when a local variable has the same name: { name:, age: }.

Example

nums = [3, 1, 2]
nums << 5
p nums[-1], nums[1..2], nums[10]   # 5, [1, 2], nil
first, *rest = nums
p first, rest                      # 3, [1, 2, 5]

user = { name: "Ada", "role" => "admin" }
p user[:name], user["name"]        # "Ada", nil: symbol != string
p user.fetch(:age, 0)              # 0

counts = Hash.new(0)
%w[a b a c a].each { |w| counts[w] += 1 }
p counts                           # {"a" => 3, "b" => 1, "c" => 1}

groups = Hash.new { |h, k| h[k] = [] }
groups[:even] << 2
p groups                           # {even: [2]}

name, age = "Bo", 7
p({ name:, age: })                 # {name: "Bo", age: 7}
p (1...4).to_a, (1..).first(2)     # [1, 2, 3], [1, 2]

Remember

  • Symbol keys and string keys are different
  • Use fetch when a missing key is a bug; Hash.new(0) for counters
  • Ranges: .. includes the end, ... excludes it

Watch out for

  • Using Hash.new([]) for grouping: every key shares the same array object. Use the block form.

Check yourself

  1. What does Hash.new([]) cause when you do h[:a] << 1; h[:b] << 2?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.ruby-lang.org/en/3.4/Hash.html