Add something to hash value if key exists? - ruby

I have a Hash in Ruby:
hash = Hash.new
It has some key value pairs in it, say:
hash[1] = "One"
hash[2] = "Two"
If the hash contains a key 2, then I want to add "Bananas" to its value. If the hash doesn't have a key 2, I want to create a new key value pair 2=>"Bananas".
I know I can do this by first checkng whether the hash has the key 2 by using has_key? and then act accordingly. But this requires an if statement and more than one line.
So is there a simple, elegant one-liner for achieving this?

This works:
hash[2] = (hash[2] || '') + 'Bananas'
If you want all keys to behave this way, you could use the "default" feature of Ruby's Hash:
hash = {}
hash.default_proc = proc { '' }
hash[2] += 'Bananas'

You could set the default value of the hash to an empty string, then make use of the << operator to concat whatever new values are passed:
h = Hash.new("")
#=> {}
h[2] << "Bananas"
#=> "Bananas"
h
#=> {2=>"Bananas"}
h[2] << "Bananas"
#=> "BananasBananas"
Per #rodrigo.garcia's comment, another side effect of this approach is that Hash.new() sets the default return value for the hash (which may or may not be what you want). In the example above, that default value is an empty string, but it doesn't have to be:
h2 = Hash.new(2)
#=> {}
h2[5]
#=> 2

(hash[2] ||= "").concat("Bananas")

Technically, both your roads lead to the same place. So Hash[2] = "bananas" produces the same result as first checking the hash for key 2. However, if you actually need the process of checking the hash for some reason a way to do that is use the .has_key? method, and a basic if conditional.
Suppose there is a hash,
`Hash = { 1 => "One", 2 => "Two" }`
setup a block of code based on the truth-value of a key search,
if hash.has_key?(2) == true
hash[2] = "bananas"
else
hash[2] = "bananas"
end
or more simply,
hash.has_key?(2) == true ? hash[2] = "bananas" : hash[2] = "bananas"

You can initialise the hash with a block and then directly concatenate:
hash = Hash.new {|hash, key| hash[key] = ""}
hash[1] << "One"
hash[2] << "Two"
hash[2] << "Bananas"
{1=>"One", 2=>"TwoBananas"}

Related

Working with Hashes that have a default value

Am learning to code with ruby. I am learning about hashes and i dont understand this code: count = Hash.new(0). It says that the 0 is a default value, but when i run it on irb it gives me an empty hash {}. If 0 is a default value why can't i see something like count ={0=>0}. Or is the zero an accumulator but doesn't go to the keys or values? Thanks
0 will be the fallback if you try to access a key in the hash that doesn't exist
For example:
count = Hash.new -> count['key'] => nil
vs
count = Hash.new(0) -> count['key'] => 0
To expand on the answer from #jeremy-ramos and comment from #mu-is-too-short.
There are two common gotcha's with defaulting hash values in this way.
1. Accidentally shared references.
Ruby uses the exact same object in memory that you pass in as the default value for every missed key.
For an immutable object (like 0), there is no problem. However you might want to write code like:
hash = Hash.new([])
hash[key] << value
or
hash = Hash.new({})
hash[key][second_key] = value
This will not do what you'd expect. Instead of hash[unknown_key] returning a new, empty array or hash it will return the exact same array/hash object for every key.
so doing:
hash = Hash.new([])
hash[key1] << value1
hash[key2] << value2
results in a hash where key1 and key2 both point to the same array object containing [value1, value2]
See related question here
Solution
To solve this you can create a hash with a default block argument instead (which is called whenever a missing key is accessed and lets you assign a value to the missed key)
hash = Hash.new{|h, key| h[key] = [] }
2. Assignment of missed keys with default values
When you access a missing key that returns the default value, you might expect that the hash will now contain that key with the value returned. It does not. Ruby does not modify the hash, it simply returns the default value. So, for example:
hash = Hash.new(0) #$> {}
hash.keys.empty? #$> true
hash[:foo] #$> 0
hash[:foo] == 0 #$> true
hash #$> {}
hash.keys.empty? #$> true
Solution
This confusion is also addressed using the block approach, where they keys value can be explicitly set.
The Hash.new docs are not very clear on this. I hope that the example below clarifies the difference and one of the frequent uses of Hash.new(0).
The first chunk of code uses Hash.new(0). The hash has a default value of 0, and when new keys are encountered, their value is 0. This method can be used to count the characters in the array.
The second chunk of code fails, because the default value for the key (when not assigned) is nil. This value cannot be used in addition (when counting), and generates an error.
count = Hash.new(0)
puts "count=#{count}"
# count={}
%w[a b b c c c].each do |char|
count[char] += 1
end
puts "count=#{count}"
# count={"a"=>1, "b"=>2, "c"=>3}
count = Hash.new
puts "count=#{count}"
%w[a b b c c c].each do |char|
count[char] += 1
# Fails: in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)
end
puts "count=#{count}"
SEE ALSO:
What's the difference between "Hash.new(0)" and "{}"
TL;DR When you initialize hash using Hash.new you can setup default value or default proc (the value that would be returned if given key does not exist)
Regarding the question to understand this magic firstly you need to know that Ruby hashes have default values. To access default value you can use Hash#default method
This default value by default :) is nil
hash = {}
hash.default # => nil
hash[:key] # => nil
You can set default value with Hash#default=
hash = {}
hash.default = :some_value
hash[:key] # => :some_value
Very important note: it is dangerous to use mutable object as default because of side effect like this:
hash = {}
hash.default = []
hash[:key] # => []
hash[:other_key] << :some_item # will mutate default value
hash[:key] # => [:some_value]
hash.default # => [:some_value]
hash # => {}
To avoid this you can use Hash#default_proc and Hash#default_proc= methods
hash = {}
hash.default_proc # => nil
hash.default_proc = proc { [] }
hash[:key] # => []
hash[:other_key] << :some_item # will not mutate default value
hash[:other_key] # => [] # because there is no this key
hash[:other_key] = [:symbol]
hash[:other_key] << :some_item
hash[:other_key] # => [:symbol, :some_item]
hash[:key] # => [] # still empty array as default
Setting default cancels default_proc and vice versa
hash = {}
hash.default = :default
hash.default_proc = proc { :default_proc }
hash[:key] # => :default_proc
hash.default = :default
hash[:key] # => :default
hash.default_proc # => nil
Going back to Hash.new
When you pass argument to this method, you initialize default value
hash = Hash.new(0)
hash.default # => 0
hash.default_proc # => nil
When you pass block to this method, you initialize default proc
hash = Hash.new { 0 }
hash.default # => nil
hash[:key] # => 0

How to solve the "retrieve_values" problem

I'm working on this problem:
Write a method retrieve_values that takes in two hashes and a key. The method should return an array containing the values from the two hashes that correspond with the given key.
def retrieve_values(hash1, hash2, key)
end
dog1 = {"name"=>"Fido", "color"=>"brown"}
dog2 = {"name"=>"Spot", "color"=> "white"}
print retrieve_values(dog1, dog2, "name") #=> ["Fido", "Spot"]
puts
print retrieve_values(dog1, dog2, "color") #=> ["brown", "white"]
puts
I came up with a working solution:
def retrieve_values(hash1, hash2, key)
arr = []
hash1.each { |key| } && hash2.each { |key| }
if key == "name"
arr << hash1["name"] && arr << hash2["name"]
elsif key == "color"
arr << hash1["color"] && arr << hash2["color"]
end
return arr
end
I then looked at the 'official' solution:
def retrieve_values(hash1, hash2, key)
val1 = hash1[key]
val2 = hash2[key]
return [val1, val2]
end
What is wrong with my code? Or is it an acceptable "different" approach?
Line with hash1.each { |key| } && hash2.each { |key| } just does nothing it is not needed even in your solution.
This part a bit difficult to read arr << hash1["name"] && arr << hash2["name"]. It mutates the array two times in one line, this kind of style could lead to bugs.
Also, your code sticks only to two keys name and color:
dog1 = {"name"=>"Fido", "color"=>"brown", "age" => 1}
dog2 = {"name"=>"Spot", "color"=> "white", "age" => 2}
> retrieve_values(dog1, dog2, "age")
=> []
The official solution will return [1, 2].
You don't need here to explicitly use return keyword, any block of code returns the last evaluated expression. But it is a matter of style guide.
It is possible to simplify even the official solution:
def retrieve_values(hash1, hash2, key)
[hash1[key], hash2[key]]
end

How to assign to deeply nested Hash without using many 'nil' guards

I have a nested hash, to which I need to add more deeply nested property/value pairs.
Sample A:
a = {}
a['x']['y']['z'] << 8
Normally I'd have to do this:
Sample B:
a = {}
a['x'] ||= a['x'] = {}
a['x']['y'] ||= a['x']['y'] = {}
a['x']['y']['z'] ||= a['x']['y']['z'] = []
Otherwise, I will get undefined method '<<' for nil:NillClass.
Is there some type of shorthand or function along the lines of code A instead of code B?
The most elegant solution for the deep-nested hash of any depth would be:
hash = Hash.new { |h, k| h[k] = h.dup.clear }
or, even better (credits to #Stefan)
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
That way one might access any level:
hash[:a1][:a2][:a3][:a4] = :foo
#⇒ {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
The idea is to clone the default_proc within the hash itself.
To give a little backgroud, there's a method Hash#dig which is present since ruby 2.3. With this you can safely attempt to read any number of keys:
{a: {b: {c: 1}}}.dig :a, :b, :c # => 1
{}.dig :a, :b, :c # => nil
of course, this doesn't solve your problem. You're looking for a write version. This has been proposed but rejected in Ruby core in the form of Hash#bury.
This method does almost exactly what you are looking for, however it can only set nested hash values and not append to nested arrays:
# start with empty hash
hash = {}
# define the inner array
hash.bury :x, :y, :z, []
# add to the inner array
hash[:x][:y][:z] << :some_val
You can get this method through the ruby-bury gem, or alternatively you can take their implementation from their source code
Here are a couple of ways that could be done.
arr = [:a1, :a2, :a3, :a4, :foo]
Use Enumerable#reduce (aka inject)
def hashify(arr)
arr[0..-3].reverse_each.reduce(arr[-2]=>arr[-1]) { |h,x| { x=>h } }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
Use recursion
def hashify(arr)
first, *rest = arr
rest.size == 1 ? { first=>rest.first } : { first=>hashify(rest) }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
You can consider using the get and set methods from the rodash gem in order to set a deeply nested value into a hash with a default value.
require 'rodash'
a = {}
key = ['x', 'y', 'z']
default_value = []
value = 8
current_value = Rodash.get(a, key, default_value)
Rodash.set(a, key, current_value << value)
a
# => {"x"=>{"y"=>{"z"=>[8]}}}
I use the following to deeply set/initialize arrays/hashes using a list of keys. If they keys are ints, it assumes indexing into an array, otherwise it assumes a hash:
def deep_set(target, path, value)
key = path.shift
return target[key] = value if path.empty?
child = target[key]
return deep_set(child, path, value) if child
deep_set(
target[key] = path[0].is_a?(Integer) ? [] : {},
path,
value,
)
end
Here is an example of using it:
target = {first: [:old_data]}
deep_set(target, [:first, 1, 1, :lol], 'foo')
puts target # {:first=>[:old_data, [nil, {:lol=>"foo"}]]}
It uses the fact that ruby allows you to set-and-expand arrays on the fly, which is a bit wonky, but works nicely here.

How do I add a key/value pair to the beginning of a hash?

My code is:
hash = { two: 2, three: 3 }
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash = temp_hash
puts hash
end
hash_add(hash, 'one', 1)
Within the method, puts hash returns { :one => 1, :two => 2, :three => 3 }, but when hash1 is put to the method, it remains unchanged afterward. It's like the assignment isn't carrying itself outside of the function.
I guess I could return the updated hash and set the hash I want to change to it outside the method:
hash = hash_add(hash, 'one', 1)
But I just don't see why the assignment I give to the hash does not stick outside of the method.
I have this, which works:
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash.clear
temp_hash.each do |key, value|
hash[key] = value
end
end
Which gives me what I'm wanting when this method is called, but it just seems a little excessive to have to rebuild the hash like that.
How about this?
hash1 = { two: 2, three: 3 }
#add a new key,value
hash1 = Hash[:one,1].merge!(hash1) #=> {:one=>1, :two=>2, :three=>3}
Example #2:
h = { two: 2, three: 3 }
def hash_add(h,k,v)
Hash[k.to_sym,v].merge!(h)
end
h = hash_add(h, 'one', 1) #=> {:one=>1, :two=>2, :three=>3}
Ruby passes objects to methods by value, but the value is the reference to the object, so when you set hash=temp_hash within the add_hash method, that change only applies inside the method. The value of hash outside the method is unchanged.
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash = temp_hash
hash
end
h2 = hash_add(hash, 'one', 1)
hash
=> {:two=>2, :three=>3}
h2
=>{:one=>1, :two=>2, :three=>3}
If you want hash to be updated, you need to replace the contents of hash rather than re-point hash at a new object as you did with the clear and re-adding the values. You can also do it with the replace method.
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash.replace temp_hash
end
There are some good diagrams about pass by value in "Is Ruby pass by reference or by value?"
NOTE: this answer is old from times when Ruby 1.8 was still around.
In general, the class Hash in Ruby does not provide ordering. Behavior might differ between Ruby versions / implementations.
See also: Hash ordering preserved between iterations if not modified?
If you want ordering, you need to use the class OrderedHash which is provided through ActiveSupport
See: http://apidock.com/rails/ActiveSupport/OrderedHash
At the end of the function you are just putsing the hash, not returning it. Perhaps if you changed puts hash to return hash it would work (I haven't tried it myself).
temp_hash is a local variable, which gets deleted once the function returns.

Is there a way to initialize hash's values lazily?

I have a hash like:
h = {
a: '/users/sign_up',
b: "/user/#{#user.id]}"
}
Later I do h[:b].
Hash values are initialized when hash itself is initialized. But I'd want #user.id to be invoked every time when h[:b] is invoked.
It seems to be not possible to do it with Ruby's hash. But is there some workaround to do it?
You can use lambdas for the values of the hash, and call the lambda when the actual value is needed, e.g.:
h = {
a: ->{'/users/sign_up'},
b: ->{"/user/#{#user.id}"}
}
h[:b].call
h = {}
h.default_proc = proc do |hash, key|
key == :b ? "/user/#{#user.id}" : nil
end
h[:a] #=> nil
h[:b] #=> "/user/<id>"

Resources