Why does Hash.new({}) hide hash members? [duplicate] - ruby

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 7 years ago.
Ok, so I wanted to create a hash which has an empty hash as the default value. A bit weird, I know, but let's say that I thought it might be useful.
So here's what I did:
>> a = Hash.new({})
=> {}
>> a[:a][:b] = 5
=> 5
>> a
=> {}
>> a[:a]
=> {:b=>5}
>> a.keys
=> []
>> a.size
=> 0
>> a[:a].size
=> 1
In other words, I don't see hash member when I inspect the hash, but I can access it by its key.
Is this expected behavior? What is going on here?
BTW, this is Ruby 1.9.1, haven't tried earlier versions.
Edit: simplified example as it doesn't have to be a hash of hashes of hashes...

It is expected behaviour (across all ruby versions). And if you experiment a bit further, you'll see that you always access the same hash, no matter which key you use:
>> a[:a][:b] = 1
=> 1
>> a[:c][:d] = 2
=> 2
>> a[:d]
=> {:b=>1, :d=>2}
The way Hash.new with a default argument works is: If you do hash[key] it checks whether that key exists in the hash. If it does, it returns the value for that key. If not it returns the default value. It does not add the key to the hash and it will return the same default object (not a copy) every time.
To get what you want, you want to specify a default block instead. That way, the block will be executed every time you access a key that is not in the hash. Inside the block you can create a new Hash and set the key to "point" to that hash. Like so:
Hash.new { |h,k| h[k] = {} }

Related

Hash shows as empty after adding array of objects to an object key, even though elements are accessible [duplicate]

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 4 years ago.
I'm trying to have a hash whose value is an array.
I use the new method with an empty array as the default value, so it was my assumption that #hash[a][0] = b would first get an empty array (from #hash[a]) and then assign the 0 index value to b.
For some reason, the hash shows as empty, with size zero, even though the items can be accessed as expected. Can anyone explain why?
Another person has pointed out that it will work if I use #hash = {}, but this requires instantiating the empty array for each key I add, which is an inconvenience, and I'm curious how the value can be accessed despite the size remaining zero.
class Test
def initialize
#hash = Hash.new []
end
def run
a = Object.new
b = Object.new
#hash[a][0] = b
puts #hash # outputs {}
puts #hash.size # outputs 0
puts #hash[a].inspect # outputs [#<Object:0x00007fe2bb80caa0>]
puts #hash[a][0].inspect # outputs #<Object:0x00007fe2bb80caa0>
end
end
test = Test.new
test.run
After a little assistance on a Slack channel, I now understand.
When you call [] with a key that doesn't exist, it'll return a new array instance. You are then calling [0] on that new array and that value is returned, but that new array isn't actually assigned to anything, so it disappears and you see no new key/val on your hash.
Every time you call this, it returns the same array instance, and hence why I was able to access the values I had assigned even though the hash didn't show them as being saved.

Ruby hash defaults: where do nested values go? [duplicate]

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 6 years ago.
I wanted to use Ruby's default hash values to allow me to more easily nest hashes without having to manually initialize them. I thought it'd be nice to be able to dig a level down for each key safely without having pre-set the key as a hash. However, I find that when I do this, the data gets stored somewhere, but is not visible by accessing the top-level hash. Where does it go, and how does this work?
top = Hash.new({}) #=> {}
top[:first][:thing] = "hello" #=> "hello"
top[:second] = {thing: "world"} #=> {:thing => "world"}
top #=> {:second => {:thing => "world"}}
top[:first] #=> {:thing => "hello"}
You want to know where your inserted hash is? Maybe you have heard about Schroedingers cat:
h = Hash.new({})
h[:box][:cat] = "Miau"
=> "Miau"
h
=> {}
The cat seem to be dead....
h[:schroedingers][:cat]
=> "Miau"
The cat seem still to be alive, but in a different reality....
Ok, if nothing helps, "Read The Fine Manual". For Hash.new, we read:
If obj is specified, this single object will be used for all default values.
So when you write h[:box], a object is returned, and this object is another hash, and it happen to empty.
Into this empty hash, you write an key-value.
Now this other hash is no longer empty, it has a key-value pair. And it is returned every time you search for a key is not found in your original hash.
You can access the default value via a variety of #default methods
http://ruby-doc.org/core-2.2.3/Hash.html#method-i-default
top.default
=> {:thing=>"hello"}
You can also tell it how you want it to act, example:
irb(main):058:0> top = Hash.new {|h,k| h[k] = {}; h[k]}
=> {}
irb(main):059:0> top[:first][:thing] = "hello"
=> "hello"
irb(main):060:0> top[:second] = {thing: "world"}
=> {:thing=>"world"}
irb(main):061:0> top
=> {:first=>{:thing=>"hello"}, :second=>{:thing=>"world"}}

Using nested hash and a hash as default value isn't working as expected [duplicate]

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 6 years ago.
I want to understand why a in the following is an empty hash after a[:a] is called, which is not empty:
a = Hash.new({a:1}) # => {}
a[:a][:a] += 1 # => 2
a # => {}
a[:a] # => {:a=>2}
I know that {a: 1} is shared between every inexistent key (a[:something_inexistent]). But why isn't it displayed as a key of a? If possible, using Ruby's internal would be much appreciated.
It is because a[:a] is never assigned. When you do a[:a], it is not returning the stored value, but is returning the default value.
Unlike this, when you instead have:
a = Hash.new{|h, k| h[k] = {a: 1}}
then, whenever a missing key is called, the block is executed, which assigns the value to the key, and a will no longer be an empty hash.

Creating a Hash with values as arrays and default value as empty array [duplicate]

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 6 years ago.
I want to create a Hash in Ruby with default values as an empty array
So, I coded
x = Hash.new([])
However, when I try to push a value into it
x[0].push(99)
All the keys get 99 pushed into that array. How do I resolve this?
Lakshmi is right. When you created the Hash using Hash.new([]), you created one array object.
Hence, the same array is returned for every missing key in the Hash.
That is why, if the shared array is edited, the change is reflected across all the missing keys.
Using:
Hash.new { |h, k| h[k] = [] }
Creates and assigns a new array for each missing key in the Hash, so that it is a unique object.
h = Hash.new{|h,k| h[k] = [] }
h[0].push(99)
This will result in {0=>[99]}
When Hash.new([]) is used, a single object is used as the default value (i.e. value to be returned when a hash key h[0] does not return anything), in this case one array.
So when we say h[0].push(99), it pushes 99 into that array but does not assign h[0] anything. So if you output h you will still see an empty hash {}, while the default object will be [99].
Whereas, when a block is provided i.e. Hash.new{|h,k| h[k] = [] } a new object is created and is assigned to h[k] every time a default value is required.
h[0].push(99) will assign h[0] = [] and push value into this new array.

How to remove duplicates from array of hashes based on keys only?

How would you go about removing duplicates based on the key?
values = [{"a"=>"1"}, {"a"=>"2"}, {"b"=>"1"}, {"a"=>"4"}]
How can I ignore the value and run uniq based on key so that it returns:
[{'a' => '1'}, {'b' => '1'}]
Assuming you don't care which value gets clobbered, just run them into a hash (which does have unique keys and is therefore probably the right collection class for this case):
h = {}
values.each{|i|i.each{|k,v|h[k] = v}}
puts h # => {"a"=>"4", "b"=>"1"}
... or if you want the first of each key:
h = {}
values.each{|i|i.each{|k,v|h[k] = v unless h[k]}}
If you want to get back to a Array:
h.each{|k,v|a << {k=>v}}
The following will work only in ruby 1.9, so it might be useless.
Hash[values.map(&:first).reverse].map{|a| Hash[*a]}
If you need it in the original order,
values & Hash[values.map(&:first).reverse].map{|a| Hash[*a]}
Without introducing any intermediate variables the following 1 liner will do the trick:
> [{"a"=>"1"}, {"a"=>"2"}, {"b"=>"1"}, {"a"=>"4"}].inject({}) {|s,h| s.merge(h) unless s.keys.include? h.keys}.inject([]) {|s,h| s << {h[0]=>h[1]}}
=> [{"a"=>"4"}, {"b"=>"1"}]

Resources