Ruby Hash of Arrays outputing {} [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.
If I do the following:
h = Hash.new(Array.new)
h['a'].push('apple')
puts h['a']
puts h
I get the following output:
apple
{}
I don't understand why puts h isn't outputing:
{"a"=>["apple"]}
Any help much appreciated...

Read this
new(obj) → new_hash
If obj is specified, this single object will be used for all default values.
Hash.new(Array.new) - by this line you have created a default array object. Which will be returned whenever you want to access a key, which not exist inside the hash.
h['a'].push('apple') - By this line, you actually adding/pushing a value to that default array object,but not adding any key to the hash. h['a'] is returning you the array you defined with Array.new, on that you are calling Array#push, that's all.Thus h['a'] is giving you the current content of that default array. puts h ofcourse will give you {}, as you didn't added the key 'a' to the hash.
See the same in action :
h = Hash.new(Array.new)
h.default # => []
h['a'].push('apple')
h.default # => ["apple"]
Now look the code again :
#adding a key
h['a'] = 'Bob'
h['a'] # => "Bob"
h # => {"a"=>"Bob"}
#again default vaule as you are trying to aceess a non-exist key
h['b'] # => ["apple"]
Worth to read Hash#[]
Element Reference— Retrieves the value object corresponding to the key object. If not found, returns the default value (see Hash::new for details).

Related

Return a default value of a hash [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I need help. I am doing a basic exercise in Ruby, but right now I don't understand defult that much.
I need to write a function called temp that reicive one argument (a hash). The function must return the same hash but with the next changes:
If the hash doesn't have the key :puppy return 4 as value.
If the hash doesn't have the key :cat return 6 as value.
Assuming this question is about default values, you're after either default or default_proc. You can make a new hash and give it a default proc via Hash.new.
def temp(h)
Hash.new do |hash, key|
if key == :puppy
h.fetch(:puppy, 4)
elsif key == :cat
h.fetch(:cat, 6)
else
h[key]
end
end
end
While Hash#default_proc is the right way to go, both answers here are overcomplicated for this particular task. The following is probably what you actually need here.
def temp(h)
h[:puppy] ||= 4
h[:cat] ||= 6
h
end
Your question is a little confusing.
If the hash doesn't have the key :puppy return 4 as value.
If the hash doesn't have the key :cat return 6 as value.
I think you want a function that returns a version of the passed-in hash that defaults the keys :puppy and :cat to 4 and 6, respectively, assuming they aren't already in the passed-in hash. This should work:
def temp(source)
return Hash.new do |h, k|
case k
when :puppy then 4
when :cat then 6
end
end.merge(source)
end
Here's how this works.
We're defining a method that takes as its one parameter a basis hash (which I called source).
When you create a hash with Hash.new and pass in a block of code, that block becomes the hash's "default proc". It will be called whenever a key that is not present in the hash is looked up, and its return value will appear to be the value stored at that key. When such a lookup happens, the block is passed two arguments: the hash itself and the key being requested. Here, I named those h and k. In the body, we check to see if k is either :puppy or :cat and return the appropriate value (4 or 6) if so. (If k is not either of those values, the case statement will automatically return nil.)
That creates a hash with the right default proc, but the hash is empty. We then populate it by calling Hash#merge on it with the passed-in source hash as argument; that will copy all of the keys from source into the new hash. The merge method call conveniently returns the hash it was called on, so we just return that result to the caller of our method.
Examples:
irb(main):001:0> h1 = temp( { a: 1, b: 2, c: 3 } )
=> {:a=>1, :b=>2, :c=>3}
irb(main):002:0> h1[:a]
=> 1
irb(main):003:0> h1[:d]
=> nil
irb(main):004:0> h1[:cat]
=> 6
irb(main):005:0> h1[:puppy]
=> 4
irb(main):006:0> h2 = temp( { a: 1, cat: :dog } )
=> {:a=>1, :cat=>:dog}
irb(main):007:0> h2[:a]
=> 1
irb(main):008:0> h2[:b]
=> nil
irb(main):009:0> h2[:cat]
=> :dog
irb(main):010:0> h2[:puppy]
=> 4

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