Conundrum:
something = {}
another = something
('a'...'f').each do |x|
puts "another = #{another} and x= #{x} and something = #{something}"
another = (another[x] ||= {})
end
puts something
Output:
=>another = {} and x= a and something = {}
=>another = {} and x= b and something = {"a"=>{}}
=>another = {} and x= c and something = {"a"=>{"b"=>{}}}
=>another = {} and x= d and something = {"a"=>{"b"=>{"c"=>{}}}}
=>another = {} and x= e and something = {"a"=>{"b"=>{"c"=>{"d"=>{}}}}}
Now at first glance it would seem like 'another' and 'something' are both pointing to the same hash object, but if that were case then in the output 'another' would match 'something'. So then how is the variable 'something' not an empty hash?
In Ruby, everything is an object and all objects are passed around using pointers (there are exceptions, but they don't apply here). When you do
something = {}
another = something
a single Hash object is created and both variables point to that same object. When you do
another[x] ||= {}
a single Hash object is created and a pointer to that object is added to the Hash pointed to by another
. Another way to write that would be
old_hash = another
new_hash = {}
old_hash[x] = new_hash
another = new_hash
Remember: all of those assignments are just moving around pointers to objects. It should now be clear that another
is pointing to the new (empty) hash. However, since old_hash
also contains a pointer to the new hash, any futures changes to the new hash (i.e. another
) will be seen by old_hash
. That is why something
grows with each iteration.
Your example is really no different from this simple situation:
x = {}
y = {}
z = {}
x["a"] = y
y["b"] = z
z["c"] = {}
puts x
# {"a"=>{"b"=>{"c"=>{}}}}
Your example just reverses the last three assignments - which makes no difference to the result.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With