compare two hashes in ruby - ruby

I need to compare 2 hashes in ruby where one of the hash contains the numeric values in quoted string, which makes that a string. Consider the following 2 hashes:
hash1 = {"A"=>"0", "B"=>"1", "SVHTID"=>"VH", "D"=>"0", "E"=>"19930730", "F"=>"TEST - DEPOSIT", "G"=>"2.25000000"}
hash2 = {"a"=>"0", "b"=>1, "c"=>"VH", "d"=>0,"e"=>19930730, "f"=>"TEST - DEPOSIT", "g"=>2.25}
Now the code i have written so far is as follows:
hash2 = Hash[hash2.map {|key, value| [key.upcase, value] }]
hash1.each{|k,v| hash1[k] = hash1[k].to_i if hash1[k].match(/-?\d+(?:\.\d+)?/)}
hash1.keys.select { |key| hash1[key] != hash2[key] }.each { |key|
puts "expected #{key} => #{hash1[key]}, but found #{key} => #{hash2[key]}"
}
what is does is that it also converts the float value to integer and the original value is lost.
What i want is that when the above 2 hashes are compared the output should contain only G as mismatched type and following should be printed:
Expected: G=>2.25000000 but found G=>2.25

# normalization
h1, h2 = [hash1, hash2].map do |h|
h.map do |k, v|
[k.to_s.downcase, v.to_s.to_i.to_s == v.to_s ? v.to_i : v]
end.to_h
end
Now we are ready to compare:
h1.reject { |k, v| h2[k].nil? || h2[k] == v }
#⇒ { "g" => "2.25000000" }
This might be printed, formatted etc as you want.

Related

Ruby: How do I remove nil/empty values from a nested hash and why aren't my attempts doing so?

I'm trying to write a method that removes all keys in a nested hash that point to nil recursively.
For example:
{:a=>nil, :b=>"b", :c=>nil, :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
becomes:
{:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}
I'm not having much luck though.
My most recent attempt looks like:
def deep_compact(hash)
hash.reject do |key, value|
deep_compact(value) if value.class == Hash
next true if value.nil? || value.empty?
end
end
Here I want to iterate over each key value pair in the hash. If the value is a hash, I want to do the same for that hash. I want to reject the pair if the value is nil or empty. Otherwise, I want to keep it.
The result isn't what I want:
#=> {:b=>"b", :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
I have also tried:
def deep_compact(hash)
hash.compact.transform_values do |value|
deep_compact(value) if value.class == Hash
value
end
end
Again, I get the same result:
#=> {:b=>"b", :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
This leaves me to believe that either I've missed something or my understanding of recursion is wrong.
Are any of my attempts close? What do I need to do to ensure I get the result I want: {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}?
The trick would be to recursively compact nested hashes and then to eliminate empty values.
compact = ->(hash) {
hash.is_a?(Hash) ?
hash.map { |k, v| [k, compact.(v)] }.
to_h.
delete_if { |_, v| v.nil? || v.respond_to?(:empty?) && v.empty? } :
hash
}
compact.(input)
#⇒ {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}
I discovered that by placing my recursive function call at the end of the block got me most of the way there. (Is this 'tail-end' recursion?)
I also call reject on the hash returned by transform_values to removed any empty pairs.
This achieves what I wanted:
def deep_compact(hash)
hash.compact.transform_values do |value|
next value unless value.class == Hash
deep_compact(value)
end.reject { |_k, v| v.empty? }
end
> h
=> {:a=>nil, :b=>"b", :c=>nil, :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
> deep_compact h
=> {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}
Other option using Hash#reject!, it changes the original Hash:
def deep_compact(h)
h.each { |_, v| deep_compact(v) if v.is_a? Hash }.reject! { |_, v| v.nil? || v.empty? }
end
deep_compact(h)
#=> {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}

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"] ↦ {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.

Change Key Positions in Ruby Hash

I have the following hash:
my_hash = {
"redis_1"=>{"group"=>"Output", "name"=>"Redis", "parameters"=>{"redis_db"=>2, "redis_password"=>"<password>"}},
"file_1"=>{"name"=>"File", "group"=>"Output", "parameters"=>{"file"=>"/opt/var/lib/bots/file-output/ctt.txt", "hierarchical_output"=>false}}
}
And I would like to move the key parameters in each inner hash to the first position, something like this:
my_hash = {
"redis_1"=>{"parameters"=>{"redis_db"=>2, "redis_password"=>"<password>"}, "group"=>"Output", "name"=>"Redis"},
"file_1"=>{"parameters"=>{"file"=>"/opt/var/lib/bots/file-output/ctt.txt", "hierarchical_output"=>false}, "name"=>"File", "group"=>"Output"}
}
I have the following code:
my_hash.each_pair do |key, value|
value.sort_by {|k, v| k == "parameters" ? 0 : 1}
end
I'm not getting any errors, but this code doesn't do anything and I'm quite lost on how to reach the output that I want.
my_hash
.each {|k, v| my_hash[k] = {"parameters" => v.delete("parameters")}.merge(v)}
or
my_hash
.each_value{|v| v.replace({"parameters" => v.delete("parameters")}.merge(v))}
Return value:
{
"redis_1"=>{"parameters"=>{"redis_db"=>2, "redis_password"=>"<password>"}, "group"=>"Output", "name"=>"Redis"},
"file_1"=>{"parameters"=>{"file"=>"/opt/var/lib/bots/file-output/ctt.txt", "hierarchical_output"=>false}, "name"=>"File", "group"=>"Output"}
}
Let's debug your code :
my_hash.each_pair do |key, value|
p value.sort_by {|k, v| k == "parameters" ? 0 : 1}
end
It outputs :
[["parameters", {"redis_db"=>2, "redis_password"=>"<password>"}], ["group", "Output"], ["name", "Redis"]]
[["parameters", {"file"=>"/opt/var/lib/bots/file-output/ctt.txt", "hierarchical_output"=>false}], ["name", "File"], ["group", "Output"]]
It sorts the pairs correctly, but :
it returns arrays instead of hashes
it doesn't write the result back to the original hash
For the first problem, you could use to_h.
For the second, you could use transform_values!, available in Ruby 2.4.
Here's the working code, which is still very similar to your proposed method :
my_hash.transform_values! do |subhash|
subhash.sort_by { |k, _| k == 'parameters' ? 0 : 1 }.to_h
end

How to compare and print matching hash values in Ruby?

I have two hashes like below,
h1 = {"a" => 1, "b" => 2, "c" => 3}
h2 = {"a" => 2, "b" => 2, "d" => 3}
I want to iterate over hash1 and hash2 and find matching keys and their values and print it on console.
Example here it should return output "b" => 2 .its not working with below code,
h1.each do |key1, value1|
h2.each do |key2, value2|
if ((h2.include? key1) && (h2.include? value1))
puts "matching h2 key #{h2[key2]}and h1 key #{h1[key1]}"
else
puts " don not match h2 key #{h2[key2]}and h1 key #{h1[key1]}"
end
end
end
I am from basically C++ and Java background and its very easy to do using for loops and iterators, but using Ruby, it is very difficult.
h1.merge(h2) { |k,o,n| puts "#{k}=>#{o}" if o == n }
"b" => 2
This uses the form of Hash#merge that employs a bock to determine the values of keys that are present in both hashes being merged. See the doc for details.
The second loop is not required.
h1.each do |key1, value1|
if (h2.include? key1) and (h2[key1] == value1)
puts "Match #{key1} with value #{value1}"
else
puts "#{key1} does not match"
end
end
You might write something like
h1.each do |k,v|
if h2[k] == v
puts "matched key = #{k} and value = #{v}"
else
puts "NOT matched key = #{k} and value = #{v}"
end
end
Output
NOT matched key = a and value = 1
matched key = b and value = 2
NOT matched key = c and value = 3
If the result is the main objective, select can work too:
h1.select{|k,v| h2[k] == v }

Find highest value from hash that contains "nil"

I have a hash which looks like this
#hash = {
0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil], 2=>[nil], 3=>[nil], 4=>[nil], 5=>[nil], 6=>[nil], 7=>[nil], 8=>[nil],
9=>[nil], 10=>[nil], 11=>[nil], 12=>[nil], 13=>[nil], 14=>[nil], 15=>[nil],
16=>[nil], 17=>[nil], 18=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]
}
I want the highest value from this hash in a variable. The output should be
#h = 7.9 \\only float value which should be highest among all
so I am doing like this
#hash.each do |k, v|
if !v.nil?
#h= [v.flatten.sort{ |v1, v2| v2['value'] <=> v1['value'] }.first['value']]
end
end
but sometimes it works, and most of the times it doesn't.
#hash.values.flatten.compact.map { |h| h["value"] }.max
=> 7.9
Which equates to:
Get the values of the hash as an array
Flatten all the elements in the values array
Compact to remove all nil entries
Map the remaining entries to the ["value"] element in the hash
Return the maximum of all those value
It makes a lot of assumptions about the format of your #hash though.
I prefer #Shadwell's solution, but here's another way:
hash.select { |_,v| v.first }
.max_by { |_,v| v.first["value"] }
.last
.first["value"]
#=> 7.9
The steps (with all but one n=>[nil] element removed for readabiity):
hash = { 0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]}
h = hash.select { |_,v| v.first }
#=> { 0=>[{"name"=>"guest", "value"=>7.9}],
# 19=>[{"name"=>"test", "value"=>2.5}],
# 20=>[{"name"=>"roam", "value"=>2.5}],
# 21=>[{"name"=>"test2", "value"=>1.58}],
# 22=>[{"name"=>"dff", "value"=>1.9}],
# 23=>[{"name"=>"dddd", "value"=>3.16}]}
a = h.max_by { |_,v| v.first["value"] }
#=> [0, [{"name"=>"guest", "value"=>7.9}]]
b = a.last
#=> [{"name"=>"guest", "value"=>7.9}]
b.first["value"]
#=> 7.9

Resources