Determine if a hash is a nested hash in Ruby - ruby

Is there a way to determine if a hash is a nested hash in ruby ?
For example
a = { a: 1, b: 2, c: 2 }
should return false
a = { a: {a1: 1, a2: 2}, b: {b1: 1}, c: 2 }
should return true

a.any? { |_, v| v.is_a?(Hash) }

You can check it by iterating over your hash values with Hash#values method:
a.values.any? { |v| v.is_a?(Hash) }

Related

Hash tree depth Ruby

I'm supposed to write a method that takes a nested hash as input and returns that hash with added "depth" keys. So, for example, the following input:
tree = {
a: 1,
b: 2,
c: { d: { e: 3 } }
}
would yield the following return value:
{
a: 1,
b: 2,
c: {
d: {
e: 3,
depth: 2
},
depth: 1
},
depth: 0
}
If the input is not a hash, then the function should return nil.
This is what I came up with:
def depth(hash)
num = 0
hash.each do |key, value|
if value.class == Hash
num += 1
v[:depth] = num
value.each do |k, v|
if v.class == Hash
num += 1
v[:depth] = num
v.each do |ky, val|
if val.class == Hash
num += 1
v[:depth] = num
val.each do |ke, vl|
if vl.class == Hash
num += 1
v[:depth] = num
end
end
end
end
end
end
end
num = 0
end
end
but it's limited to hash depth of 4, and I can't just keep making the method bigger.
Try this.
def depth(h, i=0)
h.each_with_object(depth: i) { |(k,v),g| g[k] = v.is_a?(Hash) ? depth(v, i+1) : v }
end
depth { a: 1, b: 2, c: { d: { e: 3 } }
#=> {:depth=>0, :a=>1, :b=>2, :c=>{:depth=>1, :d=>{:depth=>2, :e=>3}}}

easier and shorter way to sum values in a hash of hashes in ruby

Having a hash of hashes like this:
d = {a: {c: 1, d:3 }, b: {c: 2, d: 6}, ...}
What is the easier way to sum all values in c:?
d.values.map { |val| val[:c] }.reduce(&:+)
To explain:
d.values
=> [{:c=>1, :d=>3}, {:c=>2, :d=>6}]
d.values.map { |val| val[:c] }
=> [1, 2]
From this point you can use reduce(&:+) to get the sum, or if you're using rails (or have required active support), you can use Array#sum
reduce(&:+), by the way, is a shorthand for reduce { |memo, val| memo + val }
Just go over the hashes and sum their :c values.
d.values.sum { |h| h[:c] }
=> 3
Even shorter (from Sagar Pandya's comment):
d.sum { |_, v| v[:c] }
=> 3
If you wish to permit nested hashes of arbitrary depth you can use the following recursive method.
def sum_cees(h)
h.sum { |k,v| v.is_a?(Hash) ? sum_cees(v) : k == :c ? v : 0 }
end
sum_cees({ a: { c: 1, d:3 }, b: { d: { m: { c: 2, e: 6 } }, f: { c: 3} },
g: { c: 4 }, n: { r: 3 } })
#=> 10

Can I do the union operator for two hashes and keep the max value

I wonder is there efficient way to get my goal in Ruby.
Keep all the keys in the two hashed, and keep the larger value.
input
h1 = {
a: 0,
b: 1,
c: 2
}
h2 = {
a: 7,
c: 9
}
output
{
a: 7,
b: 1,
c: 9
}
Hash#merge can take a block:
h1.merge(h2) {|key, old, new| old > new ? old : new}
# => {:a=>7, :b=>1, :c=>9}
Hash#merge can take block. You can use it to get max value:
h1.merge(h2) { |key, v1, v2| [v1, v2].max }
# => {:a=>7, :b=>1, :c=>9}

How to merge array of hash based on the same keys in ruby?

How to merge array of hash based on the same keys in ruby?
example :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
How to get result like this?
a = [{:a=>[1, 10]},{:b=>8},{:c=>[7, 2]}]
Try
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.map(&:last)]}
Another alternative:
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
p a.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[8], :c=>[7, 2]}
It also works when the Hashes have multiple key/value combinations, e.g:
b = [{:a=>1, :b=>5, :x=>10},{:a=>10, :y=>2},{:b=>8},{:c=>7},{:c=>2}]
p b.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[5, 8], :x=>[10], :y=>[2], :c=>[7, 2]}
Minor addition to answer by Arie Shaw to match required answer:
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.size.eql?(1) ? v.last.last : v.map(&:last) ]}
#=> [{:a=>[1, 10]}, {:b=>8}, {:c=>[7, 2]}]
I'd do :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
hsh[k] = hsh.has_key?(k) ? [ *Array( v ), hsh[k] ] : v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]
update
A better taste :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
(hsh[k] ||= []) << v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]

Ruby Hash transpose

I have the following ruby hash:
h = { i1: { q1: 1, q2:2 }, i2: { q1: 3, q2: 4} }
and I want to transpose it as follows:
{ q1: { i1: 1, i2: 3 }, q2: { i1: 2, i2: 4 } }
Now, I came up with a function that does what I want,
but I wonder if there is a more succinct/elegant way for the same thing?
My solution:
ht = Hash.new{ |h,k| h[k] = {} }
h.each_pair do |k,ih|
ih.each_pair{ |ik, iv| ht[ik][k] = iv }
end
If you prefer inject, you can write it as
h.inject({}) do |a, (k, v)|
v.inject(a) do |a1, (k1, v1)|
a1[k1] ||= {}
a1[k1][k] = v1
a1
end
a
end

Resources