How to change format of nested hashes - ruby

I'm looking for a solution how to write the format function which will take a string or nested hash as an argument and return the flatten version of it with the path as a key.
arg = "foo"
format(arg) # => { "hash[keys]" => "foo" }
arg = {:a => "foo", :b => { :c => "bar", :d => "baz" }}
format(arg) # => { "hash[keys][a]" => "foo", "hash[keys][b][c]" => "bar", "hash[keys][b][d]" => "baz" }

def hash_flatten h
h.inject({}) do |a,(k,v)|
if v.is_a?(Hash)
hash_flatten(v).each do |sk, sv|
a[[k]+sk] = sv
end
else
k = k ? [k] : []
a[k] = v
end
a
end
end
def format h
if h.is_a?(Hash)
a = hash_flatten(h).map do |k,v|
key = k.map{|e| "[#{e}]"}.join
"\"event[actor]#{key}\" => \"#{v}\""
end.join(', ')
else
format({nil => h})
end
end
arg = "sth"
puts format(arg)
# => "event[actor]" => "sth"
arg = {:a => "sth", :b => { :c => "sth else", :d => "trololo" }}
puts format(arg)
# => "event[actor][a]" => "sth", "event[actor][b][c]" => "sth else", "event[actor][b][d]" => "trololo"

Related

Convert array into hash and add a counter value to the new hash

I have the following array of hashes:
[
{"BREAD" => {:price => 1.50, :discount => true }},
{"BREAD" => {:price => 1.50, :discount => true }},
{"MARMITE" => {:price => 1.60, :discount => false}}
]
And I would like to translate this array into a hash that includes the counts for each item:
Output:
{
"BREAD" => {:price => 1.50, :discount => true, :count => 2},
"MARMITE" => {:price => 1.60, :discount => false, :count => 1}
}
I have tried two approaches to translate the array into a hash.
new_cart = cart.inject(:merge)
hash = Hash[cart.collect { |item| [item, ""] } ]
Both work but then I am stumped at how to capture and pass the count value.
Expected output
{
"BREAD" => {:price => 1.50, :discount => true, :count => 2},
"MARMITE" => {:price => 1.60, :discount => false, :count => 1}
}
We are given the array:
arr = [
{"BREAD" => {:price => 1.50, :discount => true }},
{"BREAD" => {:price => 1.50, :discount => true }},
{"MARMITE" => {:price => 1.60, :discount => false}}
]
and make the assumption that each hash has a single key and if two hashes have the same (single) key, the value of that key is the same in both hashes.
The first step is create an empty hash to which will add key-value pairs:
h = {}
Now we loop through arr to build the hash h. I've added a puts statement to display intermediate values in the calculation.
arr.each do |g|
k, v = g.first
puts "k=#{k}, v=#{v}"
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count => 1 })
end
end
displays:
k=BREAD, v={:price=>1.5, :discount=>true}
k=BREAD, v={:price=>1.5, :discount=>true}
k=MARMITE, v={:price=>1.6, :discount=>false}
and returns:
#=> [{"BREAD" =>{:price=>1.5, :discount=>true}},
# {"BREAD" =>{:price=>1.5, :discount=>true}},
# {"MARMITE"=>{:price=>1.6, :discount=>false}}]
each always returns its receiver (here arr), which is not what we want.
h #=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}}
is the result we need. See Hash#key? (aka, has_key?), Hash#[], Hash#[]= and Hash#merge.
Now let's wrap this in a method.
def hashify(arr)
h = {}
arr.each do |g|
k, v = g.first
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count=>1 })
end
end
h
end
hashify(arr)
#=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}}
Rubyists would often use the method Enumerable#each_with_object to simplify.
def hashify(arr)
arr.each_with_object({}) do |g,h|
k, v = g.first
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count => 1 })
end
end
end
Compare the two methods to identify their differences. See Enumerable#each_with_object.
When, as here, the keys are symbols, Ruby allows you to use the shorthand { count: 1 } for { :count=>1 }. Moreover, she permits you to write :count = 1 or count: 1 without the braces when the hash is an argument. For example,
{}.merge('cat'=>'meow', dog:'woof', :pig=>'oink')
#=> {"cat"=>"meow", :dog=>"woof", :pig=>"oink"}
It's probably more common to see the form count: 1 when keys are symbols and for the braces to be omitted when a hash is an argument.
Here's a further refinement you might see. First create
h = arr.group_by { |h| h.keys.first }
#=> {"BREAD" =>[{"BREAD"=>{:price=>1.5, :discount=>true}},
# {"BREAD"=>{:price=>1.5, :discount=>true}}],
# "MARMITE"=>[{"MARMITE"=>{:price=>1.6, :discount=>false}}]}
See Enumerable#group_by. Now convert the values (arrays) to their sizes:
counts = h.transform_values { |arr| arr.size }
#=> {"BREAD"=>2, "MARMITE"=>1}
which can be written in abbreviated form:
counts = h.transform_values(&:size)
#=> {"BREAD"=>2, "MARMITE"=>1}
See Hash#transform_values. We can now write:
uniq_arr = arr.uniq
#=> [{"BREAD"=>{:price=>1.5, :discount=>true}},
#= {"MARMITE"=>{:price=>1.6, :discount=>false}}]
uniq_arr.each_with_object({}) do |g,h|
puts "g=#{g}"
k,v = g.first
puts " k=#{k}, v=#{v}"
h[k] = v.merge(counts: counts[k])
puts " h=#{h}"
end
which displays:
g={"BREAD"=>{:price=>1.5, :discount=>true}}
k=BREAD, v={:price=>1.5, :discount=>true}
h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2}}
g={"MARMITE"=>{:price=>1.6, :discount=>false}}
k=MARMITE, v={:price=>1.6, :discount=>false}
h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
"MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}}
and returns:
#=> {"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}}
See Array#uniq.
This did the trick:
arr = [
{ bread: { price: 1.50, discount: true } },
{ bread: { price: 1.50, discount: true } },
{ marmite: { price: 1.60, discount: false } }
]
Get the count for each occurrence of hash, add as key value pair and store:
h = arr.uniq.each { |x| x[x.first.first][:count] = arr.count(x) }
Then convert hashes into arrays, flatten to a single array then construct a hash:
Hash[*h.collect(&:to_a).flatten]
#=> {:bread=>{:price=>1.50, :discount=>true, :count=>2}, :marmite=>{:price=>1.60, :discount=>false, :count=>1}}
Combined a couple of nice ideas from here:
https://raycodingdotnet.wordpress.com/2013/08/05/array-of-hashes-into-single-hash-in-ruby/
and here:
http://carol-nichols.com/2015/08/07/ruby-occurrence-couting/

How to insert a hash inside a hash ruby

I was wondering how I could insert a hash into another hash. For example, in:
{"abcd"=>{}, "hgfe"=>34567}
I want to put "hgfe" => 34567 into the "abcd" key.
output:
{"abcd"=>{"hgfe" => 34567}}
im wanting to convert this hash
"##### RUBY HASH ####
(1)
INPUT
{
'abcd.hgfe' => 34567,
'abcd.efgh.hijk' => 12345,
'abcd.efgh.ijkl' => 56789,
'wxyz.abcd' => 9876,
'wxyz.uvwx.abcd' => 23456,
}
(1)
OUTPUT
{
'abcd' => {
'efgh' => {
'hijk' => 12345,
'ijkl' => 56789
},
'hgfe' => 34567,
},
'wxyz' => {
'abcd' => 9876,
'uvwx' => {'abcd' => 23456}
}
}
"
my currrent code:
def method1(hash)
result = {}
array2 = []
hash.each_pair do|k, v|
array1 = k.split('.')
count = array1.length
hash2 = {}
array1.each_with_index do |str, index|
if (index + 1) == count
hash2[str] = v
else
hash2[str] = {}
end
end
puts hash2.inspect
puts "--------------"
end
result
end
hash_result = method1(h2c)
Do as below
hash = {"abcd"=>{}, "hgfe"=>34567}
hash['abcd']['hgfe'] = hash.delete('hgfe')
hash # => {"abcd"=>{"hgfe"=>34567}}
You can write something like below :
def delete_key_and_add_to_another_key(hash, update_key, del_key)
hash[update_key][del_key] = hash.delete(del_key)
hash
end
hash = {"abcd"=>{}, "hgfe"=>34567}
delete_key_and_add_to_another_key(hash, 'abcd', 'hgfe')
h = {"abcd"=>{}, "hgfe"=>34567}
f, l = h.partition { |_,v| v =={} }.flatten(1)
{ f.first=> { l.first => l.last } }
#=> {"abcd"=>{"hgfe"=>34567}}

Merge values in a Hash

nodes = {
:node1 => {
:inherits => nil,
:variables => { :foo => 1, :bar => 2 }
},
:node2 => {
:inherits => :node1,
:variables => { :foo => 9, :baz => 4 }
}
}
I've been trying to understand how to return a new nodes hash where each node's :variables hash is merged with :variables from the node specified in :inheritance. In other words, node1 would be left as is while node2 to end up with :variabes => { :foo => 9, :bar => 2, :baz => 4 }
I've been swimming through docs involving Enumerable#inject, Hash#merge with a block, and more and figure it's time to ask for help.
UPDATE:
Figured I'd provide an update. This code is certainly not the solution but it might be heading in the right direction...
nodes = {
:node1 => { :inherits => nil, :variables => { :foo => 1, :bar => 2 } },
:node2 => { :inherits => :node1, :variables => { :foo => 9, :baz => 4 } }
}
new = nodes.inject({}) do |result, (k, v)|
result.merge k => v.merge({ :variables => { :a => 6, :b => 7 } })
end
returns
{:node2=>{:inherits=>:node1, :variables=>{:a=>6, :b=>7}}, :node1=>{:inherits=>nil, :variables=>{:a=>6, :b=>7}}}
So that v.merge is not working as intended...
You want Hash#merge:
merged_variables = nodes[:node1][:variables].merge(nodes[:node2][:variables])
nodes[:node2][:variables].replace(
nodes[:node1][:variables]
.merge(nodes[:node2][:variables])
)
merged_nodes = {}
nodes.each do |name, node|
merged_nodes[name] = node.dup
merged_nodes[name][:variables] = if node[:inherits]
nodes[node[:inherits]][:variables].merge node[:variables]
else
node[:variables].dup
end
end
will give you
{
:node1=>{:inherits=>nil, :variables=>{:foo=>1, :bar=>2}},
:node2=>{:inherits=>:node1, :variables=>{:foo=>9, :bar=>2, :baz=>4}}
}
but it won't handle deeper nesting i.e. if :node3 then inherits :node2... if you need anything as complicated as that, this hash-based approach is going to get pretty clunky.
A little bit shorter by merging in place:
nodes[:node2][:variables].merge!(nodes[:node1][:variables])
nodes #=> {:node1=>{:inherits=>nil, :variables=>{:foo=>1, :bar=>2}},
# :node2=>{:inherits=>:node1, :variables=>{:foo=>1, :baz=>4, :bar=>2}}}
new = {}
nodes.each do |e, v|
v.each do |attribute, value|
if attribute == :inherits
new[e] = value.nil? ? v : nodes[value][:variables].merge( v[:variables] )
end
end
end
p new #=> {:node1=>{:inherits=>nil, :variables=>{:foo=>1, :bar=>2}}, :node2=>{:foo=>9, :bar=>2, :baz=>4}}

Construct a hash with other hashes

I want to join 2 or more hashes like this.
h1 = { :es => { :hello => "You" } }
h2 = { :es => { :bye => "Man" } }
How can I get this?
h1 + h2 = { :es => { :hello => "you", :bye => "Man" } }
Thanks.
irb(main):001:0> h1 = {:es => {:hello => "You"}}
=> {:es=>{:hello=>"You"}}
irb(main):002:0> h2 = {:es => {:bye => "Man"}}
=> {:es=>{:bye=>"Man"}}
irb(main):003:0> h1.each_key {|x| h1[x].merge! h2[x]}
=> {:es=>{:bye=>"Man", :hello=>"You"}}
What you want is the deep_merge method. Does exactly what you want.
ruby-1.9.2-p136 :001 > {:es => {:hello => "You" } }.deep_merge({:es => {:bye => "Man"}})
=> {:es=>{:hello=>"You", :bye=>"Man"}}
http://apidock.com/rails/ActiveSupport/CoreExtensions/Hash/DeepMerge/deep_merge
Similar to activesupport's deep_merge, but with a functional approach. Works recursively:
class Hash
def inner_merge(other_hash)
other_hash.inject(self) do |acc, (key, value)|
if (acc_value = acc[key]) && acc_value.is_a?(Hash) && value.is_a?(Hash)
acc.merge(key => acc_value.inner_merge(value))
else
acc.merge(key => value)
end
end
end
end
h1.inner_merge(h2) #=> {:es=>{:hello=>"You", :bye=>"Man"}}
If you don't use ActiveSupport, this Proc will perform a deep merge. 1.8.7 & 1.9.2 compatible.
dm = lambda {|l,r| l.merge(r) {|k,ov,nv| l[k] = ov.is_a?(Hash) ? dm[ov, nv || {}] : nv} }
dm[h1,h2]
# => {:es=>{:hello=>"You", :bye=>"Man"}}

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