Ruby convert flattened hash to nested hash - ruby

I have the following:
{ :a_b_c => 42, :a_b_d => 67, :a_d => 89, :e => 90 }
How do I convert this as below
{ a: { b: { c: 42, d: 67 }, d: 89 }, e: 90 }

Rails with ActiveSupport have Hash#deep_merge and Hash#deep_merge!
If you haven't them, you can define
class Hash
def deep_merge(other_hash, &block)
dup.deep_merge!(other_hash, &block)
end
def deep_merge!(other_hash, &block)
merge!(other_hash) do |key, this_val, other_val|
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
this_val.deep_merge(other_val, &block)
elsif block_given?
block.call(key, this_val, other_val)
else
other_val
end
end
end
end
Or just require these methods
require 'active_support/core_ext/hash/deep_merge'
And finally
hash =
{ :a_b_c => 42, :a_b_d => 67, :a_d => 89, :e => 90 }
hash.each_with_object({}) do |(k, v), h|
h.deep_merge!(k.to_s.split('_').map(&:to_sym).reverse.reduce(v) { |assigned_value, key| { key => assigned_value } })
end
# => {:a=>{:b=>{:c=>42, :d=>67}, :d=>89}, :e=>90}

Related

How to find the largest value of a hash in an array of hashes

In my array, I'm trying to retrieve the key with the largest value of "value_2", so in this case, "B":
myArray = [
"A" => {
"value_1" => 30,
"value_2" => 240
},
"B" => {
"value_1" => 40,
"value_2" => 250
},
"C" => {
"value_1" => 18,
"value_2" => 60
}
]
myArray.each do |array_hash|
array_hash.each do |key, value|
if value["value_2"] == array_hash.values.max
puts key
end
end
end
I get the error:
"comparison of Hash with Hash failed (ArgumentError)".
What am I missing?
Though equivalent, the array given in the question is generally written:
arr = [{ "A" => { "value_1" => 30, "value_2" => 240 } },
{ "B" => { "value_1" => 40, "value_2" => 250 } },
{ "C" => { "value_1" => 18, "value_2" => 60 } }]
We can find the desired key as follows:
arr.max_by { |h| h.values.first["value_2"] }.keys.first
#=> "B"
See Enumerable#max_by. The steps are:
g = arr.max_by { |h| h.values.first["value_2"] }
#=> {"B"=>{"value_1"=>40, "value_2"=>250}}
a = g.keys
#=> ["B"]
a.first
#=> "B"
In calculating g, for
h = arr[0]
#=> {"A"=>{"value_1"=>30, "value_2"=>240}}
the block calculation is
a = h.values
#=> [{"value_1"=>30, "value_2"=>240}]
b = a.first
#=> {"value_1"=>30, "value_2"=>240}
b["value_2"]
#=> 240
Suppose now arr is as follows:
arr << { "D" => { "value_1" => 23, "value_2" => 250 } }
#=> [{"A"=>{"value_1"=>30, "value_2"=>240}},
# {"B"=>{"value_1"=>40, "value_2"=>250}},
# {"C"=>{"value_1"=>18, "value_2"=>60}},
# {"D"=>{"value_1"=>23, "value_2"=>250}}]
and we wish to return an array of all keys for which the value of "value_2" is maximum (["B", "D"]). We can obtain that as follows.
max_val = arr.map { |h| h.values.first["value_2"] }.max
#=> 250
arr.select { |h| h.values.first["value_2"] == max_val }.flat_map(&:keys)
#=> ["B", "D"]
flat_map(&:keys) is shorthand for:
flat_map { |h| h.keys }
which returns the same array as:
map { |h| h.keys.first }
See Enumerable#flat_map.
Code
p myArray.pop.max_by{|k,v|v["value_2"]}.first
Output
"B"
I'd use:
my_array = [
"A" => {
"value_1" => 30,
"value_2" => 240
},
"B" => {
"value_1" => 40,
"value_2" => 250
},
"C" => {
"value_1" => 18,
"value_2" => 60
}
]
h = Hash[*my_array]
# => {"A"=>{"value_1"=>30, "value_2"=>240},
# "B"=>{"value_1"=>40, "value_2"=>250},
# "C"=>{"value_1"=>18, "value_2"=>60}}
k = h.max_by { |k, v| v['value_2'] }.first # => "B"
Hash[*my_array] takes the array of hashes and turns it into a single hash. Then max_by will iterate each key/value pair, returning an array containing the key value "B" and the sub-hash, making it easy to grab the key using first:
k = h.max_by { |k, v| v['value_2'] } # => ["B", {"value_1"=>40, "value_2"=>250}]
I guess the idea of your solution is looping through each hash element and compare the found minimum value with hash["value_2"].
But you are getting an error at
if value["value_2"] == array_hash.values.max
Because the array_hash.values is still a hash
{"A"=>{"value_1"=>30, "value_2"=>240}}.values.max
#=> {"value_1"=>30, "value_2"=>240}
It should be like this:
max = nil
max_key = ""
myArray.each do |array_hash|
array_hash.each do |key, value|
if max.nil? || value.values.max > max
max = value.values.max
max_key = key
end
end
end
# max_key #=> "B"
Another solution:
myArray.map{ |h| h.transform_values{ |v| v["value_2"] } }.max_by{ |k| k.values }.keys.first
You asked "What am I missing?".
I think you are missing a proper understanding of the data structures that you are using. I suggest that you try printing the data structures and take a careful look at the results.
The simplest way is p myArray which gives:
[{"A"=>{"value_1"=>30, "value_2"=>240}, "B"=>{"value_1"=>40, "value_2"=>250}, "C"=>{"value_1"=>18, "value_2"=>60}}]
You can get prettier results using pp:
require 'pp'
pp myArray
yields:
[{"A"=>{"value_1"=>30, "value_2"=>240},
"B"=>{"value_1"=>40, "value_2"=>250},
"C"=>{"value_1"=>18, "value_2"=>60}}]
This helps you to see that myArray has only one element, a Hash.
You could also look at the expression array_hash.values.max inside the loop:
myArray.each do |array_hash|
p array_hash.values
end
gives:
[{"value_1"=>30, "value_2"=>240}, {"value_1"=>40, "value_2"=>250}, {"value_1"=>18, "value_2"=>60}]
Not what you expected? :-)
Given this, what would you expect to be returned by array_hash.values.max in the above loop?
Use p and/or pp liberally in your ruby code to help understand what's going on.

Ignoring nil parameters in array select

Example
The user has made a search and has specified an age but not a number of children, i.e. wants to find people with any number of children, including zero.
array = []
obj1 = {:name => "Steve", :age => 32, :children => 2}
obj2 = {:name => "Dave", :age => 37, :children => 4}
obj3 = {:name => "Barry", :age => 40, :children => 0}
array << obj1
array << obj2
array << obj3
puts array
## replicating incoming search parameters
params = {}
params[:age] = 35
params[:children] = nil
matching_params = array.select{|person| person[:age] > params[:age] && person[:children] > params[:children]}
If I run this code, I will get an error that it can't compare a number with nil.
Workaround
If I change the children part to && person[:children] > (params[:children] || -1) then this will show all people over 35. For this situation, it does what's required.
Problem
Imagine, however, a world where it's possible to have a negative number of children...I would have to change -1 to minus infinity. Is there some way to just exclude any search criteria which have nil values?
Explicitly filter in nils.
array = [
{:name => "Steve", :age => 32, :children => 2},
{:name => "Dave", :age => 37, :children => 4},
{:name => "Barry", :age => 40, :children => 0}
]
params = {age: 35, children: nil}
matching_params =
array.select do |person|
(params[:age].nil? || person[:age] > params[:age]) &&
(params[:children].nil? || person[:children] > params[:children])
end
Code
def select_by_age_and_children(arr, params)
arr.select do |p|
p[:age] > (params[:age] || -1) &&
p[:children] > (params[:children] || -1)
end
end
Examples
array = [
{:name => "Steve", :age => 32, :children => 2},
{:name => "Dave", :age => 37, :children => 4},
{:name => "Barry", :age => 40, :children => 0}
]
params = { age: 35, children: 1 }
select_by_age_and_children(array, params)
#=> [{:name=>"Dave", :age=>37, :children=>4}]
params = { age: 35, children: nil }
select_by_age_and_children(array, params)
#=> [{:name=>"Dave", :age=>37, :children=>4},
# {:name=>"Barry", :age=>40, :children=>0}]
params = { age: 0, children: 1 }
select_by_age_and_children(array, params)
#=> [{:name=>"Steve", :age=>32, :children=>2},
# {:name=>"Dave", :age=>37, :children=>4}]
params = { age: nil, children: nil }
select_by_age_and_children(array, params)
#=> [{:name=>"Steve", :age=>32, :children=>2},
# {:name=>"Dave", :age=>37, :children=>4},
# {:name=>"Barry", :age=>40, :children=>0}]
Alternative design
Consider writing the method as follows:
def select_by_age_and_children(arr, params)
arr.select { p[:age] >= params[:age] && p[:children] >= params[:children] }
end
and then instead of calling it with, say:
params = { age: 35, children: nil }
call it with:
params = { age: 36, children: 0 }
As well as simplifying the code this avoids the need for the reader to understand what is meant by a nil value in params.

I thought that I could increment the value of a hash key in ruby. Why is this failing?

hash = { "bill" => '39', 'kim' => '35', 'larry' => '47' }
for word in hash hash[word] += 1 end
puts "Bill is now #{hash['bill]']}"
This is the error message
undefined method `+' for nil:NilClass (NoMethodError)
This isn't working because word is going to represent an array for each key/value pair in the hash. So, on the first pass through the loop, word is going to be ["bill", "39"]. That's why hash[word] is returning nil.
Illustrated:
ruby-1.9.2-p180 :001 > for word in hash
ruby-1.9.2-p180 :002?> puts word.inspect
ruby-1.9.2-p180 :003?> end
["bill", 40]
["kim", 36]
["larry", 48]
What you probably want is:
hash.keys.each do |k|
hash[k] += 1
end
The second problem is that you're storing the values as Strings instead of Ints. So, the += 1 operation will fail. Either change your hash to this:
hash = { "bill" => 39, 'kim' => 35, 'larry' => 47 }
Or convert the value to an integer before doing the + 1.
You need to specify 2 variables for a hash in the for in loop:
hash = { "bill" => 39, 'kim' => 35, 'larry' => 47 }
for word, key in hash
hash[word] += 1
end
puts "Bill is now #{hash['bill]'}"
You should be using the native each enumerator instead:
friend_ages = { 'Bill' => 39, 'Kim' => 35, 'Larry' => 47 }
friend_ages.each { |name, age| friend_ages[name] += 1 }
puts "Bill is now #{friend_ages['Bill']}."

How do I create a diff of hashes with a correction factor?

I want to compare hashes inside an array:
h_array = [
{:name => "John", :age => 23, :eye_color => "blue"},
{:name => "John", :age => 22, :eye_color => "green"},
{:name => "John", :age => 22, :eye_color => "black"}
]
get_diff(h_array, correct_factor = 2)
# should return [{:eye_color => "blue"}, {:eye_color => "green"}, {:eye_color => "black"}]
get_diff(h_array, correct_factor = 3)
# should return
# [[{:age => 23}, {:age => 22}, {:age => 22}],
# [{:eye_color => "blue"}, {:eye_color => "green"}, {:eye_color => "black"}]]
I want to diff the hashes contained in the h_array. It looks like a recursive call/method because the h_array can have multiple hashes but with the same number of keys and values. How can I implement the get_diff method?
def get_diff h_array, correct_factor
h_array.first.keys.reject{|k|
h_array.map{|h| h[k]}.sort.chunk{|e| e}.map{|_,e| e.size}.max >= correct_factor
}.map{|k|
h_array.map{|hash| hash.select{|key,_| k == key}}
}
end
class Array
def find_ndups # also returns the number of items
uniq.map { |v| diff = (self.size - (self-[v]).size); (diff > 1) ? [v, diff] : nil}.compact
end
end
h_array = [
{:name => "John", :age => 22, :eye_color => "blue", :hair => "black"},
{:name => "John", :age => 33, :eye_color => "orange", :hair => "green"},
{:name => "John", :age => 22, :eye_color => "black", :hair => "yello"}
]
def get_diff(h_array, correct_factor)
temp = h_array.inject([]){|result, element| result << element.to_a}
master_array = []
unmatched_arr = []
matched_arr = []
temp = temp.transpose
temp.each_with_index do |arr, index|
ee = arr.find_ndups
if ee.length == 0
unmatched_arr << temp[index].inject([]){|result, arr| result << {arr.first => arr.last} }
elsif ee.length > 0 && ee[0][1] != correct_factor && ee[0][1] < correct_factor
return_arr << temp[index].inject([]){|result, arr| result << {arr.first => arr.last} }
elsif ee[0][1] = correct_factor
matched_arr << temp[index].inject([]){|result, arr| result << {arr.first => arr.last} }
end
end
return [matched_arr, unmatched_arr]
end
puts get_diff(h_array, 2).inspect
hope it helps
found this ActiveSupport::CoreExtensions::Hash::Diff module.
ActiveSupport 2.3.2 and 2.3.4 has a built in Hash::Diff module which returns a hash that represents the difference between two hashes.

How do I replace all the values in a hash with a new value?

Let's say I have an arbitrarily deep nested Hash h:
h = {
:foo => { :bar => 1 },
:baz => 10,
:quux => { :swozz => {:muux => 1000}, :grimel => 200 }
# ...
}
And let's say I have a class C defined as:
class C
attr_accessor :dict
end
How do I replace all nested values in h so that they are now C instances with the dict attribute set to that value? For instance, in the above example, I'd expect to have something like:
h = {
:foo => <C #dict={:bar => 1}>,
:baz => 10,
:quux => <C #dict={:swozz => <C #dict={:muux => 1000}>, :grimel => 200}>
# ...
}
where <C #dict = ...> represents a C instance with #dict = .... (Note that as soon as you reach a value which isn't nested, you stop wrapping it in C instances.)
def convert_hash(h)
h.keys.each do |k|
if h[k].is_a? Hash
c = C.new
c.dict = convert_hash(h[k])
h[k] = c
end
end
h
end
If we override inspect in C to give a more friendly output like so:
def inspect
"<C #dict=#{dict.inspect}>"
end
and then run with your example h this gives:
puts convert_hash(h).inspect
{:baz=>10, :quux=><C #dict={:grimel=>200,
:swozz=><C #dict={:muux=>1000}>}>, :foo=><C #dict={:bar=>1}>}
Also, if you add an initialize method to C for setting dict:
def initialize(d=nil)
self.dict = d
end
then you can reduce the 3 lines in the middle of convert_hash to just h[k] = C.new(convert_hash_h[k])
class C
attr_accessor :dict
def initialize(dict)
self.dict = dict
end
end
class Object
def convert_to_dict
C.new(self)
end
end
class Hash
def convert_to_dict
Hash[map {|k, v| [k, v.convert_to_dict] }]
end
end
p h.convert_to_dict
# => {
# => :foo => {
# => :bar => #<C:0x13adc18 #dict=1>
# => },
# => :baz => #<C:0x13adba0 #dict=10>,
# => :quux => {
# => :swozz => {
# => :muux => #<C:0x13adac8 #dict=1000>
# => },
# => :grimel => #<C:0x13ada50 #dict=200>
# => }
# => }

Resources