How to solve the "retrieve_values" problem - ruby

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

Related

Ruby create an array of hashes

I've got a variable I'd like to use as a key to a hash that contains its own key and array.
e.g.
custArray = Array.new
custArray << {"c1001" => {"purchases" => ["prod01"]}}
I want to be able to do something like:
if custArray[:c1001].exists?
custArray[{:c1001["purchases"]} << "prod02"]
end
but I'm just totally stuck.
You can resolve it with:
if c = custArray.find { |h| h.key? 'c1001' }
c.dig('c1001', 'purchases') << "prod2"
end
Or if you can have more than one result with this key:
custArray.select { |h| h.key? 'c1001' }.each do |c|
c.dig('c1001', 'purchases') << "prod2"
end
If you only want to update the first instance of the array you can do:
target = custArray.find { |hash| hash.key? 'c1001' }
target['c1001']['purchases'] << 'prod02' if target
If you want to update all instances of the array you can do (backslashes are for console purposes only):
custArray \
.select { |hash| hash.key? 'c1001' } \
.each { |hash| hash['c1001']['purchases'] << 'prod02' }
You can use select from the array of hashes to see if the key is there:
target = custArray.find { |h| h.key? 'c1001' }
target['c1001']['purchases'] << "prod02" unless target.nil?
Or if array contains multiple hashes with the same key:
custArray.select { |h| h.key? 'c1001' }.each do |h|
h['c1001']['purchases'] << "prod02"
end
Also, you can write something similar to code that you already provide
custArray.each do |h|
h['c1001']['purchases'] << 'prod02' if h.keys.include?('c1001')
end
That allows reducing the count of iteration loops
custArray.find { |h| h.key?('c1001') }&.dig('c1001', 'purchases')&.push("prod02")
#=> ["prod01", "prod02"]
custArray
#=> [{"c1001"=>{"purchases"=>["prod01", "prod02"]}}]
custArray.find { |h| h.key?('c1002') }&.dig('c1002', 'purchases')&.push("prod02")
#=> nil
custArray
#=> [{"c1001"=>{"purchases"=>["prod01"]}}]
custArray.find { |h| h.key?('c1001') }&.dig('c1001', 'popsicles')&.push("prod02")
#=> nil
custArray
#=> [{"c1001"=>{"purchases"=>["prod01"]}}]
& is Ruby's Safe Navigation Operator. See also Hash#dig. Both made their debut in Ruby v2.3.

How to flatten a hash, making each key a unique value?

I want to take a hash with nested hashes and arrays and flatten it out into a single hash with unique values. I keep trying to approach this from different angles, but then I make it way more complex than it needs to be and get myself lost in what's happening.
Example Source Hash:
{
"Name" => "Kim Kones",
"License Number" => "54321",
"Details" => {
"Name" => "Kones, Kim",
"Licenses" => [
{
"License Type" => "PT",
"License Number" => "54321"
},
{
"License Type" => "Temp",
"License Number" => "T123"
},
{
"License Type" => "AP",
"License Number" => "A666",
"Expiration Date" => "12/31/2020"
}
]
}
}
Example Desired Hash:
{
"Name" => "Kim Kones",
"License Number" => "54321",
"Details_Name" => "Kones, Kim",
"Details_Licenses_1_License Type" => "PT",
"Details_Licenses_1_License Number" => "54321",
"Details_Licenses_2_License Type" => "Temp",
"Details_Licenses_2_License Number" => "T123",
"Details_Licenses_3_License Type" => "AP",
"Details_Licenses_3_License Number" => "A666",
"Details_Licenses_3_Expiration Date" => "12/31/2020"
}
For what it's worth, here's my most recent attempt before giving up.
def flattify(hashy)
temp = {}
hashy.each do |key, val|
if val.is_a? String
temp["#{key}"] = val
elsif val.is_a? Hash
temp.merge(rename val, key, "")
elsif val.is_a? Array
temp["#{key}"] = enumerate val, key
else
end
print "=> #{temp}\n"
end
return temp
end
def rename (hashy, str, n)
temp = {}
hashy.each do |key, val|
if val.is_a? String
temp["#{key}#{n}"] = val
elsif val.is_a? Hash
val.each do |k, v|
temp["#{key}_#{k}#{n}"] = v
end
elsif val.is_a? Array
temp["#{key}"] = enumerate val, key
else
end
end
return flattify temp
end
def enumerate (ary, str)
temp = {}
i = 1
ary.each do |x|
temp["#{str}#{i}"] = x
i += 1
end
return flattify temp
end
Interesting question!
Theory
Here's a recursive method to parse your data.
It keeps track of which keys and indices it has found.
It appends them in a tmp array.
Once a leaf object has been found, it gets written in a hash as value, with a joined tmp as key.
This small hash then gets recursively merged back to the main hash.
Code
def recursive_parsing(object, tmp = [])
case object
when Array
object.each.with_index(1).with_object({}) do |(element, i), result|
result.merge! recursive_parsing(element, tmp + [i])
end
when Hash
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key])
end
else
{ tmp.join('_') => object }
end
end
As an example:
require 'pp'
pp recursive_parsing(data)
# {"Name"=>"Kim Kones",
# "License Number"=>"54321",
# "Details_Name"=>"Kones, Kim",
# "Details_Licenses_1_License Type"=>"PT",
# "Details_Licenses_1_License Number"=>"54321",
# "Details_Licenses_2_License Type"=>"Temp",
# "Details_Licenses_2_License Number"=>"T123",
# "Details_Licenses_3_License Type"=>"AP",
# "Details_Licenses_3_License Number"=>"A666",
# "Details_Licenses_3_Expiration Date"=>"12/31/2020"}
Debugging
Here's a modified version with old-school debugging. It might help you understand what's going on:
def recursive_parsing(object, tmp = [], indent="")
puts "#{indent}Parsing #{object.inspect}, with tmp=#{tmp.inspect}"
result = case object
when Array
puts "#{indent} It's an array! Let's parse every element:"
object.each_with_object({}).with_index(1) do |(element, result), i|
result.merge! recursive_parsing(element, tmp + [i], indent + " ")
end
when Hash
puts "#{indent} It's a hash! Let's parse every key,value pair:"
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key], indent + " ")
end
else
puts "#{indent} It's a leaf! Let's return a hash"
{ tmp.join('_') => object }
end
puts "#{indent} Returning #{result.inspect}\n"
result
end
When called with recursive_parsing([{a: 'foo', b: 'bar'}, {c: 'baz'}]), it displays:
Parsing [{:a=>"foo", :b=>"bar"}, {:c=>"baz"}], with tmp=[]
It's an array! Let's parse every element:
Parsing {:a=>"foo", :b=>"bar"}, with tmp=[1]
It's a hash! Let's parse every key,value pair:
Parsing "foo", with tmp=[1, :a]
It's a leaf! Let's return a hash
Returning {"1_a"=>"foo"}
Parsing "bar", with tmp=[1, :b]
It's a leaf! Let's return a hash
Returning {"1_b"=>"bar"}
Returning {"1_a"=>"foo", "1_b"=>"bar"}
Parsing {:c=>"baz"}, with tmp=[2]
It's a hash! Let's parse every key,value pair:
Parsing "baz", with tmp=[2, :c]
It's a leaf! Let's return a hash
Returning {"2_c"=>"baz"}
Returning {"2_c"=>"baz"}
Returning {"1_a"=>"foo", "1_b"=>"bar", "2_c"=>"baz"}
Unlike the others, I have no love for each_with_object :-). But I do like passing a single result hash around so I don't have to merge and remerge hashes over and over again.
def flattify(value, result = {}, path = [])
case value
when Array
value.each.with_index(1) do |v, i|
flattify(v, result, path + [i])
end
when Hash
value.each do |k, v|
flattify(v, result, path + [k])
end
else
result[path.join("_")] = value
end
result
end
(Some details adopted from Eric, see comments)
Non-recursive approach, using BFS with an array as a queue. I keep the key-value pairs where the value isn't an array/hash, and push array/hash contents to the queue (with combined keys). Turning arrays into hashes (["a", "b"] &mapsto; {1=>"a", 2=>"b"}) as that felt neat.
def flattify(hash)
(q = hash.to_a).select { |key, value|
value = (1..value.size).zip(value).to_h if value.is_a? Array
!value.is_a?(Hash) || !value.each { |k, v| q << ["#{key}_#{k}", v] }
}.to_h
end
One thing I like about it is the nice combination of keys as "#{key}_#{k}". In my other solution, I could've also used a string path = '' and extended that with path + "_" + k, but that would've caused a leading underscore that I'd have to avoid or trim with extra code.

Safely assign value to nested hash using Hash#dig or Lonely operator(&.)

h = {
data: {
user: {
value: "John Doe"
}
}
}
To assign value to the nested hash, we can use
h[:data][:user][:value] = "Bob"
However if any part in the middle is missing, it will cause error.
Something like
h.dig(:data, :user, :value) = "Bob"
won't work, since there's no Hash#dig= available yet.
To safely assign value, we can do
h.dig(:data, :user)&.[]=(:value, "Bob") # or equivalently
h.dig(:data, :user)&.store(:value, "Bob")
But is there better way to do that?
It's not without its caveats (and doesn't work if you're receiving the hash from elsewhere), but a common solution is this:
hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }
hash[:data][:user][:value] = "Bob"
p hash
# => { :data => { :user => { :value => "Bob" } } }
And building on #rellampec's answer, ones that does not throw errors:
def dig_set(obj, keys, value)
key = keys.first
if keys.length == 1
obj[key] = value
else
obj[key] = {} unless obj[key]
dig_set(obj[key], keys.slice(1..-1), value)
end
end
obj = {d: 'hey'}
dig_set(obj, [:a, :b, :c], 'val')
obj #=> {d: 'hey', a: {b: {c: 'val'}}}
interesting one:
def dig_set(obj, keys, value)
if keys.length == 1
obj[keys.first] = value
else
dig_set(obj[keys.first], keys.slice(1..-1), value)
end
end
will raise an exception anyways if there's no [] or []= methods.
I found a simple solution to set the value of a nested hash, even if a parent key is missing, even if the hash already exists. Given:
x = { gojira: { guitar: { joe: 'charvel' } } }
Suppose you wanted to include mario's drum to result in:
x = { gojira: { guitar: { joe: 'charvel' }, drum: { mario: 'tama' } } }
I ended up monkey-patching Hash:
class Hash
# ensures nested hash from keys, and sets final key to value
# keys: Array of Symbol|String
# value: any
def nested_set(keys, value)
raise "DEBUG: nested_set keys must be an Array" unless keys.is_a?(Array)
final_key = keys.pop
return unless valid_key?(final_key)
position = self
for key in keys
return unless valid_key?(key)
position[key] = {} unless position[key].is_a?(Hash)
position = position[key]
end
position[final_key] = value
end
private
# returns true if key is valid
def valid_key?(key)
return true if key.is_a?(Symbol) || key.is_a?(String)
raise "DEBUG: nested_set invalid key: #{key} (#{key.class})"
end
end
usage:
x.nested_set([:instrument, :drum, :mario], 'tama')
usage for your example:
h.nested_set([:data, :user, :value], 'Bob')
any caveats i missed? any better way to write the code without sacrificing readability?
Searching for an answer to a similar question I developmentally stumbled upon an interface similar to #niels-kristian's answer, but wanted to also support a namespace definition parameter, like an xpath.
def deep_merge(memo, source)
# From: http://www.ruby-forum.com/topic/142809
# Author: Stefan Rusterholz
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
memo.merge!(source, &merger)
end
# Like Hash#dig, but for setting a value at an xpath
def bury(memo, xpath, value, delimiter=%r{\.})
xpath = xpath.split(delimiter) if xpath.respond_to?(:split)
xpath.map!{|x|x.to_s.to_sym}.push(value)
deep_merge(memo, xpath.reverse.inject { |memo, field| {field.to_sym => memo} })
end
Nested hashes are sort of like xpaths, and the opposite of dig is bury.
irb(main):014:0> memo = {:test=>"value"}
=> {:test=>"value"}
irb(main):015:0> bury(memo, 'test.this.long.path', 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}}}}
irb(main):016:0> bury(memo, [:test, 'this', 2, 4.0], 'value')
=> {:test=>{:this=>{:long=>{:path=>"value"}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):017:0> bury(memo, 'test.this.long.path.even.longer', 'value')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}}, :"2"=>{:"4.0"=>"value"}}}}
irb(main):018:0> bury(memo, 'test.this.long.other.even.longer', 'other')
=> {:test=>{:this=>{:long=>{:path=>{:even=>{:longer=>"value"}}, :other=>{:even=>{:longer=>"other"}}}, :"2"=>{:"4.0"=>"value"}}}}
A more ruby-helper-like version of #niels-kristian answer
You can use it like:
a = {}
a.bury!([:a, :b], "foo")
a # => {:a => { :b => "foo" }}
class Hash
def bury!(keys, value)
key = keys.first
if keys.length == 1
self[key] = value
else
self[key] = {} unless self[key]
self[key].bury!(keys.slice(1..-1), value)
end
self
end
end

Add something to hash value if key exists?

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"}

'Hash#keys' in helper

I have this method in helper:
def get_hash_keys(hash)
hash.delete_if{|k, v| v.class != Hash}
return hash.keys
end
When I call get_hash_keys from another method in the same helper, it returns a blank array :
def datas_size
sum = 0
new_hash = {title: "This is a test", datas: {firstname: "foo", lastname: "bar"}}
new_hash.each do |k, v|
sum += v.size if get_hash_keys(new_hash).includes? k
end
return sum
end
I tested to change return hash.keys with fixed array, and I get it. Only keys function seems not to work. I also double checked my array in params.
Is there some specifications I ignore working inside helpers ?
Edit for #DRSE :
I start with hash that contains others hashes. I need to know size of each children hash. The point is when I call this function (get_hash_keys) from the views, it fails (return blank array), but from console it works (return keys).
More investigation this morning drive me to conclude may be this is a wrong usage of delete_if. My ununderstood solution is to replace :
def get_hash_keys(hash)
hash.delete_if{|k, v| v.class != Hash}
return hash.keys
end
with
def get_hash_keys(hash)
tmp_hash = hash.clone
tmp_hash.delete_if{|k, v| v.class != Hash}
return tmp_hash.keys
end
Nothing is wrong with hash.keys:
def get_new_keys(hash)
hash.delete_if{|k,v| k == :bar}
return hash.keys
end
get_new_keys({qux: :bar, bar: :baz}) # => [:qux]
This, on the other hand, is busted.
def display_keys(hash)
return "foo " + get_new_keys(hash)
end
Are you expecting an array to be returned? If so, that's not the way to go about it at all. You could do something like:
def display_keys(hash)
return [:foo].concat(get_new_keys(hash))
end
In which case, it'd do something like:
def display_keys(hash)
return [:foo].concat(get_new_keys(hash))
end
display_keys({qux: :bar, bar: :baz}) # => [:foo, :qux]
The .keys method returns and Array of keys in the Hash.
Your question is impossible to answer for two reasons:
You have not included the condition in your delete_if block. My
guess is that you are deleting all keys in the hash, so .keys is
returning [].
You have not described the output you are expecting.
On a side note, your method display_keys does not work in ruby 2.3. You cannot concatenate an Array and a String like that. It will throw a TypeError: no implicit conversion of Array into String
But taking my best guess at it the following code works just fine:
def get_new_keys(hash)
hash.delete_if{ |k,v| v % 2 == 0 }
return hash.keys
end
def display_keys(hash)
return ["foo"] + get_new_keys(hash)
end
hash = {
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9,
ten: 10
}
Outputs:
pry(main)> display_keys( hash )
=> ["foo", :one, :three, :five, :seven, :nine]

Resources