How to array_wrap an hash of hashes - arrays

inv = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}} is my object
I want
inv = {"C"=>{"CPS"=>{"CP"=>[{"name"=>"a"}]}}}
I tried
inv["C"]["CPS"].inject({}) do |result, (k, v)|
k = Array.wrap(v)
end
=> [{"name"=>"a"}]
but still inv={"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}}
tries map also

Another option is to use tap
inv["C"]["CPS"].tap do |h|
h["CP"] = [h["CP"]] #or Array.wrap(h["CP"]) in rails
end
inv
#=> {"C"=>{"CPS"=>{"CP"=>[{"name"=>"a"}]}}}
tap will yield the current object so you can modify it in place.
Update
Inspired by #CarySwoveland's broader application you could use something like this as well.
class HashWrapper
attr_reader :original_hash
attr_accessor :target_keys
def initialize(h,*target_keys)
#original_hash = h
#target_keys = target_keys
end
def wrapped_hash
#wrapped_hash ||= {}
end
def wrap_me
original_hash.each do |k,v|
value = v.is_a?(Hash) ? HashWrapper.new(v,*target_keys).wrap_me : v
wrapped_hash[k] = wrap(k,value)
end
wrapped_hash
end
private
def wrap(k,v)
target_keys.include?(k) ? [v] : v
end
end
Then implementation is as follows
wrapper = HashWrapper.new(inv,"CP")
wrapper.wrap_me
#=> {"C"=>
{"CPS"=>
{"CP"=>
[
{"name"=>"a"}
]
}
}
}
new_wrapper = HashWrapper.new(inv,"CP","CPS")
new_wrapper.wrap_me
#=> {"C"=>
{"CPS"=>
[
{"CP"=>
[
{"name"=>"a"}
]
}
]
}
}
This assumes unique keys all the way through the hierarchy otherwise nested keys of the same name will be wrapped in the same fashion from the bottom up.
e.g.
inv = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}},"CP" => "higher level"}}
HashWrapper.new(inv,"CP").wrap_me
#=> {"C"=>
{"CPS"=>
{"CP"=>
[
{"name"=>"a"}
]
},
"CP"=>
[
"higher level"
]
}
}

This should do it:
hash = {"C"=>{"CPS"=>{"CP"=>{"name"=>"a"}}}}
val = hash["C"]["CPS"]["CP"]
val_as_arr = [val] # can optionally call flatten here
hash["C"]["CPS"]["CP"] = val_as_arr
puts hash
# => {"C"=>{"CPS"=>{"CP"=> [{"name" => "a"}] }}}
basically
get the value
convert to array
set the value
There is no iteration required here i.e. map or reduce

I suggest you use recursion, in the form of a compact and easily readable method that has broader application than solutions that only work with your specific hash.
def wrap_it(h)
h.each { |k,v| h[k] = v.is_a?(Hash) ? wrap_it(v) : [v] }
h
end
h = { "C"=>{ "CPS"=>{ "CP"=>{ "name"=>"a" } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"name"=>["a"]}}}}
h = { "C"=>{ "CPS"=>{ "CP"=>{ "CPPS"=> { "name"=>"cat" } } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"CPPS"=>{"name"=>["cat"]}}}}}
h = { "C"=>{ "CPS"=>{ "CP"=>{ "CPPS"=> { "name"=>"cat" } },
"DP"=>{ "CPPPS"=>"dog" } } } }
wrap_it(h)
#=> {"C"=>{"CPS"=>{"CP"=>{"CPPS"=>{"name"=>["cat"]}}, "DP"=>{"CPPPS"=>["dog"]}}}}

Related

Manipulate array of hashes into grouped hashes with arrays

I have an array of hashes:
[
{
"June" => { "A" => { 3 => 48.4 } }
},
{
"January" => { "C" => { 2 => 88.0} }
},
{
"January"=> { "B" => { 2 => 44.0} }
},
{
"January"=> { "C" => { 4 => 48.8} }
}
]
I need to group each similar hash key into an array of the subsequent values like the following:
{
"June" => [{ "A" => [{ 3 => 48.4 }]] },
"January" => [
{ "B" => [{ 2 => 44.0}],
{ "C" => [{ 2 => 88.0}, { 4 => 48.8}],
] }
}
I am looking for an efficient method of grouping these elements. Can anyone help me master this hash of hashes?
I am trying to avoid looping through the base array and grouping manually. I was hoping that map (or some other enumerable method) might give what I want. When I used reduce(Hash.new, :merge), it came close but it used the last hash for each month key instead of adding it to an array.
Note: I added the following after gaining a clearer understanding of the question. My original answer is below.
Here is the OP's array of hashes, modified slightly.
arr = [{ "June" =>{ "A"=>{ 3=>48.4 } } },
{ "January"=>{ "C"=>{ 2=>88.0 } } },
{ "January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } } },
{ "January"=>{ "C"=>{ 2=>10.0 } } },
{ "January"=>{ "C"=>{ 4=>48.8 } } }]
The hash to be constructed appears to be the following.
{ "June" =>[{ "A"=>[{ 3=>48.4 }] }],
"January"=>[{ "B"=>[{ "D"=>[{ 2=>44.0 }] }] }],
"C"=>[{ 2=>98.0, 4=>48.8 }] }] }
Note that 88.0 + 10.0 #=> 98.0 in 2=>98.0.
Observe that all the arrays within arr contain a single element, a hash. That being the case, those arrays serve no useful purpose. I therefore suggest the following hash be constructed instead:
{ "June" =>{ "A"=>{ 3=>48.4 } },
"January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } },
"C"=>{ 2=>98.0, 4=>48.8 } } }
This can be produced with the following recursive method.
def recurse(arr)
arr.map(&:flatten).
group_by(&:first).
each_with_object({}) do |(k,v),h|
o = v.map(&:last)
h.update(k=>o.first.is_a?(Hash) ? recurse(o) : o.sum )
end
end
recurse(arr)
#=> {"June"=>{"A"=>{3=>48.4}},
# "January"=>{"C"=>{2=>98.0, 4=>48.8}, "B"=>{"D"=>{2=>44.0}}}}
(Original answer follows)
Here are two ways to obtain the desired hash. I assume that arr is your array of hashes.
#1 Use the form of Hash::new that takes a block
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
k, v = g.to_a.first
h[k] << v
end
# => {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}
#2 Use Enumerable#group_by
arr.map(&:first).
group_by(&:first).
tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
# => {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}
The steps are as follows.
a = arr.map(&:first)
#=> [["June", {"A"=>{3=>48.4}}], ["January", {"C"=>{2=>88.0}}],
# ["January", {"B"=>{2=>44.0}}], ["January", {"C"=>{4=>48.8}}]]
b = a.group_by(&:first)
#=> {"June"=>[["June", {"A"=>{3=>48.4}}]],
# "January"=>[["January", {"C"=>{2=>88.0}}], ["January", {"B"=>{2=>44.0}}],
# ["January", {"C"=>{4=>48.8}}]]}
c = b.tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
#=> {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{=>48.8}}]}
Let me elaborate the last step. Inside tap's block, we compute the following.
h = b
d = h.keys
#=> ["June", "January"]
The first element of d is passed to each's block and the block variable is assigned to that element.
k = d.first
#=> "June"
The block calculation is as follows.
e = h[k]
#=> [["June", {"A"=>{3=>48.4}}]]
f = e.map(&:last)
#=> [{"A"=>{3=>48.4}}]
h[k] = f
#=> [{"A"=>{3=>48.4}}]
b #=> {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[["January", {"C"=>{2=>88.0}}],
# ["January", {"B"=>{2=>44.0}}],
# ["January", {"C"=>{4=>48.8}}]]}
Next, d[1] ("January") is passed to each's block and similar calculations are performed.
Rather than using Object#tap I could have written
h = arr.map(&:first).
group_by(&:first)
h.keys.each { |k| h[k] = h[k].map(&:last) }
h
tap merely avoids the creation of local variable h and the need to have a final line equal to h.

Recursively setting hash keys from an array of keys

I want a function that can take an array like [:a, :b, :c] and recursively set hash keys, creating what it needs as it goes.
hash = {}
hash_setter(hash, [:a, :b, :c], 'value')
hash #=> {:a => {:b => {:c => 'value' } } }
hash_setter(hash, [:a, :b, :h], 'value2')
hash #=> {:a => {:b => {:c => 'value', :h => 'value2' } } }
I'm aware that Ruby 2.3's dig can be used for getting in this way, though that doesnt quite get you to an answer. If there was a setter equivalent of dig that'd be what I'm looking for.
Code
def nested_hash(keys, v, h={})
return subhash(keys, v) if h.empty?
return h.merge(subhash(keys, v)) if keys.size == 1
keys[0..-2].reduce(h) { |g,k| g[k] }.update(keys[-1]=>v)
h
end
def subhash(keys, v)
*first_keys, last_key = keys
h = { last_key=>v }
return h if first_keys.empty?
first_keys.reverse_each.reduce(h) { |g,k| g = { k=>g } }
end
Examples
h = nested_hash([:a, :b, :c], 14) #=> {:a=>{:b=>{:c=>14}}}
i = nested_hash([:a, :b, :d], 25, h) #=> {:a=>{:b=>{:c=>14, :d=>25}}}
j = nested_hash([:a, :b, :d], 99, i) #=> {:a=>{:b=>{:c=>14, :d=>99}}}
k = nested_hash([:a, :e], 104, j) #=> {:a=>{:b=>{:c=>14, :d=>99}, :e=>104}}
nested_hash([:f], 222, k) #=> {:a=>{:b=>{:c=>14, :d=>99}, :e=>104}, :f=>222}
Observe that the value of :d is overridden in the calculation of j. Also note that:
subhash([:a, :b, :c], 12)
#=> {:a=>{:b=>{:c=>12}}}
This mutates the hash h. If that is not desired one could insert the line
f = Marshal.load(Marshal.dump(h))
after the line return subhash(keys, v) if h.empty? and change subsequent references to h to f. Methods from the Marshal module can be used to create a deep copy of a hash so the original hash is not be mutated.
Solved it with recursion:
def hash_setter(hash, key_arr, val)
key = key_arr.shift
hash[key] = {} unless hash[key].is_a?(Hash)
key_arr.length > 0 ? hash_setter(hash[key], key_arr, val) : hash[key] = val
end
def set_value_for_keypath(initial, keypath, value)
temp = initial
for key in keypath.first(keypath.count - 1)
temp = (temp[key] ||= {})
end
temp[keypath.last] = value
return initial
end
initial = {:a => {:b => {:c => 'value' } } }
set_value_for_keypath(initial, [:a, :b, :h], 'value2')
initial
Or if you prefer something more unreadable:
def set_value_for_keypath(initial, keypath, value)
keypath.first(keypath.count - 1).reduce(initial) { |hash, key| hash[key] ||= {} }[keypath.last] = value
end

How to merge two arrays of hashes

I have two arrays of hashes:
a = [
{
key: 1,
value: "foo"
},
{
key: 2,
value: "baz"
}
]
b = [
{
key: 1,
value: "bar"
},
{
key: 1000,
value: "something"
}
]
I want to merge them into one array of hashes, so essentially a + b except I want any duplicated key in b to overwrite those in a. In this case, both a and b contain a key 1 and I want the final result to have b's key value pair.
Here's the expected result:
expected = [
{
key: 1,
value: "bar"
},
{
key: 2,
value: "baz"
},
{
key: 1000,
value: "something"
}
]
I got it to work but I was wondering if there's a less wordy way of doing this:
hash_result = {}
a.each do |item|
hash_result[item[:key]] = item[:value]
end
b.each do |item|
hash_result[item[:key]] = item[:value]
end
result = []
hash_result.each do |k,v|
result << {:key => k, :value => v}
end
puts result
puts expected == result # prints true
uniq would work if you concatenate the arrays in reverse order:
(b + a).uniq { |h| h[:key] }
#=> [
# {:key=>1, :value=>"bar"},
# {:key=>1000, :value=>"something"},
# {:key=>2, :value=>"baz"}
# ]
It doesn't however preserve the order.
[a, b].map { |arr| arr.group_by { |e| e[:key] } }
.reduce(&:merge)
.flat_map(&:last)
Here we use hash[:key] as a key to build the new hash, then we merge them overriding everything with the last value and return values.
I would rebuild your data a bit, since there are redundant keys in hashes:
thin_b = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
thin_a = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
Then you can use just Hash#merge:
thin_a.merge(thin_b)
#=> {1=>"bar", 2=>"baz", 1000=>"something"}
But, if you want, you can get exactly result as mentioned in question:
result.map { |k, v| { key: k, value: v } }
#=> [{:key=>1, :value=>"bar"},
# {:key=>2, :value=>"baz"},
# {:key=>1000, :value=>"something"}]
using Enumerable#group_by and Enumerable#map
(b+a).group_by { |e| e[:key] }.values.map {|arr| arr.first}
If you need to merge two arrays of hashes that should be merged also and there is more than two keys, then next snippet should help:
[a, b].flatten
.compact
.group_by { |v| v[:key] }
.values
.map { |e| e.reduce(&:merge) }

Correct way to search an Array instead of where

How to query search an Array with where? What's the correct way?
U have an association like this:
# Foo has many bars
Foo.first.bars
Controller:
def index
#bars = []
#datas = Foo.where(email: current_user.email)
#datas.map { |d| #bars.push(d.bar).where("name like ?", "%#{params[:email]}%") }
respond_to do |format|
format.html
format.json { render json: #bars }
end
end
Instead of where for Array, what is the correct query term?
You can use the select method to filter out values in an array given a certain condition.
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
Or for your particular example:
#bars = #datas.map { |d| d.bars }.select { |b| b.name.include? params[:email] }
However, since you actually don't have an array of bars and would have to create it, this is simply an unnecessary step and a more straightforward solution would be:
#datas = Foo.where(email: current_user.email)
# #bars is an array
#bars = #datas.map { |d| d.bars.where("name like ?", "%#{params[:email]}%") }
or
# #bars is an ActiveRecord object
#bars = Bar.where(id: #datas.pluck(:id)).where("name like ?", "%#{params[:email]}%")

Merge nested hash without overwritting in Ruby

After checking this Ruby convert array to nested hash and other sites I am not able to achieve the following convertion:
I have this:
{"a"=>"text"}
{"b"=>{"x"=>"hola"}}
{"b"=>{"y"=>"pto"}
}
and I want to obtain:
{"a"=>text,
b=>{"x" => "hola",
"y" => "pto"
}
}
Until now the code seems like this:
tm =[[["a"],"text"],[["b","x"],"hola"],[["b","y"],"pto"]]
q = {}
tm.each do |l|
q = l[0].reverse.inject(l[1]) { |p, n| { n => p } }
i += 1
end
I tried with merge, but it overwrites the keys!. I tried also this How can I merge two hashes without overwritten duplicate keys in Ruby? but it keeps overwritting.
Update:
How can I do it for an undefined nested hash (level) ? hash[key1][key2][key3]... = "value"
{"a"=>"text"},
{"b"=>{"x"=>"hola"},
{"b"=>{"y"=>"pto"},
{"c"=>{"g"=>{"k" => "test1"}},
...
}
You could use merge with a block to tell Ruby how to handle duplicate keys:
a = {"a"=>"text"}
b = {"b"=>{"x"=>"hola"}}
c = {"b"=>{"y"=>"pto"}}
a.merge(b).merge(c) { |key, left, right| left.merge(right) }
#=> {"a"=>"text", "b"=>{"x"=>"hola", "y"=>"pto"}}
For Rails there is the deep_merge function for ActiveSupport that does exactly what you ask for.
You can implement the same for yourself as follows:
class ::Hash
def deep_merge(second)
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
self.merge(second, &merger)
end
end
Now,
h1 = {"a"=>"text"}
h2 = {"b"=>{"x"=>"hola"}}
h3 = {"b"=>{"y"=>"pto"}}
h1.deep_merge(h2).deep_merge(h3)
# => {"a"=>"text", "b"=>{"x"=>"hola", "y"=>"pto"}}

Resources