Why h.replace(h.invert) loses your keys
Question
What's the return value of this Ruby 3 code?
h = { a: 1, b: 2 }
h.replace(h.invert)
h[:a] # => ???
The correct answer is
-
2 -
:b -
It raises
NoMethodError -
nilCorrect
Explanation
TL;DR
h.invert returns a new hash with keys and values swapped: { 1 => :a, 2 => :b }. h.replace then overwrites the contents of h in place with that inverted hash. The key :a no longer exists, so h[:a] returns nil.
Step by step
Hash#invert builds and returns a new hash; it never mutates the receiver:
h = { a: 1, b: 2 }
h.invert # => {1 => :a, 2 => :b}
h # => {a: 1, b: 2}
Hash#replace is the mutator. It swaps the contents of h for the contents of its argument while keeping the same object:
h = { a: 1, b: 2 }
id = h.object_id
h.replace(h.invert) # => {1 => :a, 2 => :b}
h # => {1 => :a, 2 => :b}
h.object_id == id # => true
After the replacement, :a is no longer a key; it is now a value. Looking up a missing key with Hash#[] returns the hash's default, which is nil here:
h = { 1 => :a, 2 => :b }
h[:a] # => nil
h[1] # => :a
Edge cases
Hash#invert keeps only the last pair when values collide, so an inversion can lose data even before replace gets involved:
{ a: 1, b: 1 }.invert # => {1 => :b}
If a missing key should be an error rather than a silent nil, use Hash#fetch: h.fetch(:a) raises KeyError and surfaces this kind of bug immediately.
Share this quiz
Comments
No comments yet. Be the first.