strange Hash behavior for nested assignments with defaults [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.
console output:
2.1.3 :011 > b = Hash.new( Hash.new([]) )
=> {}
2.1.3 :012 > b[:a][:b] << 'hello'
=> ["hello"]
2.1.3 :013 > b
=> {}
2.1.3 :014 > b.size
=> 0
2.1.3 :015 > b.keys
=> []
2.1.3 :016 > b[:a][:b]
=> ["hello"]
Why is that I can access the value stored at b[:a][:b] yet b has a size of 0 and no keys?

new(obj) → new_hash
If obj is specified, this single object will be used for all default values.
Now Hash.new([]) is holding the default Array object. Now b[:a][:b] << 'hello' you are entering, the "hello" to the default Array.The default value is being returned, when the key doesn't exist in the Hash.
Don't think you are adding keys to the Hash objects, with this b[:a][:b] << 'hello' line.
b[:a] is giving the default Hash object, which is Hash.new([]). Now on this Hash object you are calling Hash#[] using the key :b, but as :b is the non existent key, it is giving the default Array object.
That's why b, b.size and b.keys all are proving that Hash is empty.
Finally.
Why is that I can access the value stored at b[:a][:b] yet b has a size of 0 and no keys?
Because, you added the value "Hello" to the default Array, as I mentioned above. That value is coming when you are using the line b[:a][:b].

Related

rails nested hash with default values [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 2 years ago.
I'd like to create a new Hash with nested default values. I thought it should be like
h = Hash.new(count: 0, rating: 0)
So I can do stuff like
h['a'][:count] += 1
h['a'][:rating] += 1
and so on. But when I try it in the console it looks like this:
2.3.1 :046 > h = Hash.new(count: 0, rating: 0)
=> {}
2.3.1 :047 > h["a"]
=> {:count=>0, :rating=>0}
2.3.1 :048 > h["a"][:count]
=> 0
2.3.1 :049 > h["a"][:count] += 1
=> 1
2.3.1 :050 > h["b"][:count] += 1
=> 2
2.3.1 :051 > h
=> {}
So my questions are:
Why is h["b"][:count] += 1 returning 2 and not 1?
why is h empty?
Thanks in advance!
The doc for Hash::new explains the three ways of initializing a hash and, in your case, you are using an object in the Hash constructor:
If obj is specified, this single object will be used for all default values.
If you want that each missing key creates it's own object, create the hash with a block, like this:
h = Hash.new { |h,k| h[k] = { count: 0, rating: 0 } }
Then:
2.6.3 :012 > h
=> {}
2.6.3 :013 > h['a'][:count] = 5
=> 5
2.6.3 :015 > h
=> {"a"=>{:count=>5, :rating=>0}}
You can find this behaviour documented in the Hash::new documentation:
new → new_hash
new(obj) → new_hash
new {|hash, key| block } → new_hash
Returns a new, empty hash. If this hash is subsequently accessed by a
key that doesn't correspond to a hash entry, the value returned
depends on the style of new used to create the hash. In the first
form, the access returns nil. If obj is specified, this single
object will be used for all default values. If a block is
specified, it will be called with the hash object and the key, and
should return the default value. It is the block's responsibility to
store the value in the hash if required.
h = Hash.new("Go Fish")
h["a"] = 100
h["b"] = 200
h["a"] #=> 100
h["c"] #=> "Go Fish"
# The following alters the single default object
h["c"].upcase! #=> "GO FISH"
h["d"] #=> "GO FISH"
h.keys #=> ["a", "b"]
# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"] #=> "Go Fish: c"
h["c"].upcase! #=> "GO FISH: C"
h["d"] #=> "Go Fish: d"
h.keys #=> ["c", "d"]
In your example h["a"] and h["b"] both return the exact same default hash object. Meaning that if you change h["a"], h["b"] is also changed. Since you never set h["a"] or h["b"] the hash appears empty.
To assign a new hash on access you'll have to use the block syntax as shown by luis.parravicini.

Initialize Ruby hash with empty array as default [duplicate]

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Initializing a Hash with empty array unexpected behaviour [duplicate]
(1 answer)
Closed 4 years ago.
When I initialize a Ruby hash with a default value like 0 and create a new entry in the hash and increment it behaves as expected:
irb(main):001:0> h1 = Hash.new(0)
=> {}
irb(main):002:0> h1[:foo] += 1
=> 1
irb(main):003:0> h1
=> {:foo=>1}
irb(main):004:0> h1[:foo]
=> 1
Notice how h1 #=> {:foo=>1} and h1[:foo] #=> 1. That's what I was expecting to see.
Except if I use a default value of an empty array then this is what happens:
irb(main):005:0> h2 = Hash.new([])
=> {}
irb(main):006:0> h2[:foo] << "cats"
=> ["cats"]
irb(main):007:0> h2
=> {}
irb(main):008:0> h2[:foo]
=> ["cats"]
Notice how h2 #=> {} and h2[:foo] #=> ["cats"]. I don't know why this is happening.
What is going on here?
Why does h2 look like an empty hash but then still has a value with the key :foo?
If I use some a block then the expected behavior happens:
irb(main):001:0> h3 = Hash.new {|hash, key| hash[key] = [] }
=> {}
irb(main):002:0> h3[:foo] << "cats"
=> ["cats"]
irb(main):003:0> h3
=> {:foo=>["cats"]}
irb(main):004:0> h3[:foo]
=> ["cats"]
Notice how h2 #=> {:foo=>["cats"]} and h3[:foo] #=> ["cats"]. That's what I was expecting to see.
The answer is in Hash.new. In short:
If obj ([] in your case) is specified, this single object will be used for all default values. If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block's responsibility to store the value in the hash if required.
Initialise with a block to set non-nil defaults.

Initializing a Hash with empty array unexpected behaviour [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 3 years ago.
I want to initialize a Hash with an empty Array and for every new key push a certain value to that array.
Here's what I tried to do:
a = Hash.new([])
# => {}
a[1] << "asd"
# => ["asd"]
a
# => {}
The expected output for a was {1 => ["asd"]} but that didn't happen. What am I missing here?
Ruby version:
ruby 2.0.0p598 (2014-11-13 revision 48408) [x86_64-linux]
Just do
a = Hash.new { |h, k| h[k] = [] }
a[1] << "asd"
a # => {1=>["asd"]}
Read the below lines from the Hash::new documentation. It really explains why you didn't get the desired result.
new(obj) → new_hash
If obj is specified, this single object will be used for all default values.
new {|hash, key| block } → new_hash
If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block’s responsibility to store the value in the hash if required.
You can test by hand :
a = Hash.new([])
a[1].object_id # => 2160424560
a[2].object_id # => 2160424560
Now with the above style of Hash object creation, you can see every access to an unknown key, returning back the same default object. Now the other way, I meant block way :
b = Hash.new { |h, k| [] }
b[2].object_id # => 2168989980
b[1].object_id # => 2168933180
So, with the block form, every unknown key access, returning a new Array object.

Ruby Hash.new weirdness [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Strange ruby behavior when using Hash.new([])
This is a simple one, as I'm lost for words.
Why is this happening:
1.9.3-p194 :001 > h = Hash.new([])
=> {}
1.9.3-p194 :002 > h[:key1] << "Ruby"
=> ["Ruby"]
1.9.3-p194 :003 > h
=> {}
1.9.3-p194 :004 > h.keys
=> []
1.9.3-p194 :005 > h[:key1]
=> ["Ruby"]
When you create a hash like this:
h = Hash.new([])
it means, whenever the hash is accessed with a key that has not been defined yet, its going to return:
[]
Now when you do :
h[:key1] << "Ruby"
h[:key1] has returned [] , to which "Ruby" got pushed, resulting in ["Ruby"], as output, as that is the last object returned. That has also got set as the default value to return when 'h' is accessed with an undefined key.
Hence, when you do :
h[:key1] or h[:key2] or h[:whatever]
You will get
"Ruby"
as output.
Hope this helps.
Look at the documentation of Hash.new
new → new_hash
new(obj) → new_hash
new {|hash, key| block } → new_hash
If this hash is subsequently accessed by a key that doesn’t correspond to a hash entry, the value returned depends on the style of new used to create the hash.
In the first form, the access returns nil.
If obj is specified, this single object will be used for all default values.
If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block’s responsibility to store the value in the hash if required.
irb(main):015:0> h[:abc] # ["Ruby"]
So ["Ruby"] is returned as default value instead of nil if key is not found.
This construction Hash.new([]) returns default value but this value is not initialized value of hash. You're trying to work with hash assuming that the default value is a part of hash.
What you need is construction which will initialize the hash at some key:
hash = Hash.new { |h,k| h[k] = [] }
hash[:key1] << "Ruby"
hash #=> {:key1=>["Ruby"]}
You actually did not set the value with h[:keys] << "Ruby". You just add a value for the returned default array of a not found key. So no key is created.
If you write this, it will be okay:
h = Hash.new([])
h[:keys1] = []
h[:keys1] << "Ruby"
I have to admit this tripped me out too when I read your question. I had a look at the docs and it became clear though.
If obj is specified, this single object will be used for all default values.
So what you actually doing is modifying this one single array object that is used for the default values, without ever assigning to the key!
Check it out:
h = Hash.new([])
h[:x] << 'x'
# => ['x']
h
# => {}
h[:y]
# => ['x'] # CRAZY TIMES
So you need to do assignment somehow - h[:x] += ['x'] might be the way to go.

Is this correct behaviour for a Ruby hash with a default value? [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 7 years ago.
hash = Hash.new(Hash.new([]))
hash[1][2] << 3
hash[1][2] # => [3]
hash # => {}
hash.keys # => []
hash.values # => []
What's going on? Ruby's hiding data (1.9.3p125)
What's going on? Ruby's hiding data (1.9.3p125)
Ruby hides neither data nor its docs.
Default value you pass into the Hash constructor is returned whenever the key is not found in the hash. But this default value is never actually stored into the hash on its own.
To get what you want you should use Hash constructor with block and store default value into the hash yourself (on both levels of your nested hash):
hash = Hash.new { |hash, key| hash[key] = Hash.new { |h, k| h[k] = [] } }
hash[1][2] << 3
p hash[1][2] #=> [3]
p hash #=> {1=>{2=>[3]}}
p hash.keys #=> [1]
p hash.values #=> [{2=>[3]}]
It's simple. If you pass an object to a Hash constructor, it'll become a default value for all missing keys in that hash. What's interesting is that this value is mutable. Observe:
hash = Hash.new(Hash.new([]))
# mutate default value for nested hash
hash[1][2] << 3
# default value in action
hash[1][2] # => [3]
# and again
hash[1][3] # => [3]
# and again
hash[1][4] # => [3]
# set a plain hash (without default value)
hash[1] = {}
# what? Where did my [3] go?
hash[1][2] # => nil
# ah, here it is!
hash[2][3] # => [3]
I get a try with this in irb.
Seams Ruby does not tag element as "visible" except affecting value over default explicitly via = for instance.
hash = Hash.new(Hash.new([]))
hash[1] = Hash.new([])
hash[1][2] = [3]
hash
#=> {1=>{2=>[3]}}
May be some setters are missing this "undefaulting" behavior ...

Resources