Flatten Hash while Iterating over hash's array elements. Ruby - ruby

The input hash can have nests of any combo of Arrays and Hashes (AoA, AoH, HoH, and HoA). Flatting the hash elements to have the proper key and delimiter of _> is no problem.
However, I'm having trouble when an Array comes into the picture and I need to grab each element and stick it to the proper key while continuing to build the output. The final output should be a 1-D array of hashes with the only difference being the each array elements.
For example:
if the input hash is:
{:x => 333, :y => 13, :z => [1,2,{:zz => [40,50]},[10,20]], :a => {:o => "1", :p => "2"}}
The final result should be:
`[{:x => 333, :y => 13, :z => 1, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 1, :z_>zz => 50, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 2, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 2, :z_>zz => 50, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 10, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 10, :z_>zz => 50, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 20, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 20, :z_>zz => 50, :a_>o => 1, a_>p => 2}]`

This is long and complicated, but at least it works:
my_hash = {:x => 333, :y => 13, :z => [1,2,{:zz => [40,50]},[10,20]], :a => {:o => "1", :p => "2"}}
# Create Recursive function to get values:
def advance_hash_flattener(input, parent=[])
case input
when Hash then input.flat_map{|key, val|
advance_hash_flattener(val, parent+[key])}
when Array then input.flat_map{|x| advance_hash_flattener(x, parent)}
else [parent.join('_>'), input]
end
end
#Some small transformations for the last step:
first_step = advance_hash_flattener(my_hash)
.each_slice(2)
.group_by{|x| x.first}
.map{|x| [x.first, x.last.map(&:last)]}
p first_step #=> [["x", [333]], ["y", [13]], ["z", [1, 2, 10, 20]], ["z_>zz", [40, 50]], ["a_>o", ["1"]], ["a_>p", ["2"]]]
# Create an array of Hashes:
final_array = [Hash.new]
first_step.each do |key,values|
new = []
values.each do |val|
if final_array.first.key?(key)
final_copy = final_array.map{|x|x.clone}
final_copy.each{|x| x[key] = val}
new += final_copy
else
final_array.each{|x| x[key] = val}
end
end
final_array += new
end
# result stored in final_array

Related

Add element to a nested hash in ruby

I have a hash:
a = { 21 => { 3 => {:x => 5, :y => 6}}}
I want to add another value to the key '21' so that the hash looks like this:
a = { 21 => { 3 => {:x => 5, :y => 6}, 4 => {:x => 8, :y => 7}}}
How can I do that?
You want to add an key-value pair to a hash (a[21]). a[21] will give you the inner hash object.
a = { 21 => { 3 => {:x => 5, :y => 6}}}
a[21]
# => {3=>{:x=>5, :y=>6}}
Associating key, values to the inner hash will solve your problem.
a[21][4] = {:x => 8, :y => 7}
a
# => {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}
Another way is:
a[21].update({ 4=>{:x => 8, :y => 7} })
a #=> {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}

Combining two hashes with a common key

I have been trying to combine two hashes in Ruby. For example:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
The output I would like is:
h{"a"=> 10, 11, "b"=>20,21, "c"=> 34, "d"=>3,15}
Each hash has the same key, except the second hash might be missing some. I would like the two values then to be represented by the same key.
This is my unsuccessful code:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h3= h1.update(h2){|key1, key2, val1, val2 |key1,h2_val=h2}
It gives:
{"a"=>{"a"=>11, "b"=>21, "d"=>15}, "b"=>{"a"=>11, "b"=>21, "d"=>15}, "c"=>34, "d"=>{"a"=>11, "b"=>21, "d"=>15}}
I am just new to Ruby so I assume I am missing something very basic here. I would appreciate any help.
What about:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
p h1.merge(h2){|key, old, new| Array(old).push(new) } #=> {"a"=>[10, 11], "b"=>[20, 21], "c"=>34, "d"=>[3, 15]}
And this is how I would write it to combine more than 2 Hashes:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h3 = { "a" => 11, "b" => 21, "c"=> 1, "d"=>15}
merge_to_array = -> x,y { x.merge(y){|key, old, new| Array(old).push(new)} }
p [h1,h2,h3].reduce &merge_to_array #=> {"a"=>[10, 11, 11], "b"=>[20, 21, 21], "c"=>[34, 1], "d"=>[3, 15, 15]}
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
arr= []
arr << h1 << h2
data= arr.map(&:to_a).flatten(1).reduce({}) {|h,(k,v)| (h[k] ||= []) << v; h}
and it gives
{"a"=>[10, 11], "b"=>[20, 21], "c"=>[34], "d"=>[3, 15]}
As example use #reduce:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h2.reduce(h1.dup) {|h,(k,v)| h[k] = (h[k] && [h[k], v] || v); h}
# => {"a"=>[10, 11], "b"=>[20, 21], "c"=>34, "d"=>[3, 15]}
It's not entirely clear what you're looking for, because your example output is invalid, but here's what I'd do to merge the two hashes without stomping on the keys:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
new_hash = Hash.new{ |h, k| h[k] = [] }
[*h1, *h2].each { |k, v| new_hash[k] << v }
Which results in:
new_hash # => {"a"=>[10, 11], "b"=>[20, 21], "c"=>[34], "d"=>[3, 15]}

Collect values from an array of hashes

I have a data structure in the following format:
data_hash = [
{ price: 1, count: 3 },
{ price: 2, count: 3 },
{ price: 3, count: 3 }
]
Is there an efficient way to get the values of :price as an array like [1,2,3]?
First, if you are using ruby < 1.9:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
Then to get what you need:
array.map{|x| x[:price]}
There is a closed question that redirects here asking about handing map a Symbol to derive a key. This can be done using an Enumerable as a middle-man:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
array.each.with_object(:price).map(&:[])
#=> [1, 2, 3]
Beyond being slightly more verbose and more difficult to understand, it also slower.
Benchmark.bm do |b|
b.report { 10000.times { array.map{|x| x[:price] } } }
b.report { 10000.times { array.each.with_object(:price).map(&:[]) } }
end
# user system total real
# 0.004816 0.000005 0.004821 ( 0.004816)
# 0.015723 0.000606 0.016329 ( 0.016334)

Parse abbreviated numbers in Ruby

What's the best way in Ruby to turn human-readable abbreviated numbers into actual integers?
Examples:
"1.2M" => 1200000
"477k" => 477000
module SIValue
# http://en.wikipedia.org/wiki/SI_prefix
PREFIX_MAGNITUDES = {
'Y' => 24, 'Z' => 21, 'E' => 18, 'P' => 15, 'T' => 12,
'G' => 9, 'M' => 6, 'k' => 3, 'h' => 2, 'da' => 1,
'd' => -1, 'c' => -2, 'm' => -3, 'μ' => -6, 'n' => -9,
'p' => -12, 'f' => -15, 'a' => -18, 'z' => -21, 'y' => -24
}
def self.from( str )
_, num, prefix = str.match(/^([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)(#{PREFIX_MAGNITUDES.keys.join('|')})?/o).to_a
if num
prefix ? num.to_f * 10**PREFIX_MAGNITUDES[prefix] : num.to_f
else
0.0
end
end
end
%w[ 1k +3.3m +3.3M 123.123da 0.31h 0.31μ cats ].each do |s|
p [s,SIValue.from(s) ]
end
#=> ["1k", 1000.0]
#=> ["+3.3m", 0.0033]
#=> ["+3.3M", 3300000.0]
#=> ["123.123da", 1231.23]
#=> ["0.31h", 31.0]
#=> ["0.31μ", 3.1e-07]
#=> ["cats", 0.0]
def scale s
a = s.downcase.split /(?=[a-z])/
Integer(a.first.to_f * Hash.new(1).merge('k' => 1024, 'm' => 1024 * 1024)[a[1]] + 0.5)
end
# you may want to extend the hash with more suffix types

Idiomatic Ruby: data structure transformation

What's the "Rubyist" way to do the following data structure transformation:
I have
incoming = [ {:date => 20090501, :width => 2},
{:date => 20090501, :height => 7},
{:date => 20090501, :depth => 3},
{:date => 20090502, :width => 4},
{:date => 20090502, :height => 6},
{:date => 20090502, :depth => 2},
]
and I want to collapse these by :date, to end up with
outgoing = [ {:date => 20090501, :width => 2, :height => 7, :depth => 3},
{:date => 20090502, :width => 4, :height => 6, :depth => 2},
]
An array of arrays would also be fine at the last step, provided that the columns are in the same order in each row. Also, importantly, I do not know all the hash keys in advance (that is, I do not know :width, :height, or :depth -- they could be :cats, :dogs, and :hamsters).
If using Ruby 1.8.7 or Ruby 1.9+ the following code reads well:
incoming.group_by{|hash| hash[:date]}.map do |_, hashes|
hashes.reduce(:merge)
end
The underscore in the block attributes (_, hashes) indicates that we don't need/care about that particular attribute.
#reduce is an alias for #inject, which is used to reduce a collection into a single item. In the new Ruby versions it also accepts a symbol, which is the name of the method used to do the reduction.
It starts out by calling the method on the first item in the collection with the second item as the argument. It then calls the method again on the result with the third item as the argument and so on until there are no more items.
[1, 3, 2, 2].reduce(:+) => [4, 2, 2] => [6, 2] => 8
Here is a one liner :)
incoming.inject({}){ |o,i| o[i[:date]]||=[];o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}}
But actually the previous post is more clear, and might be faster too.
EDIT: with a bit of optimization:
p incoming.inject(Hash.new{|h,k| h[k]=[]}){ |o,i| o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}}
A concise solution:
incoming = [ {:date => 20090501, :width => 2},
{:date => 20090501, :height => 7},
{:date => 20090501, :depth => 3},
{:date => 20090502, :width => 4},
{:date => 20090502, :height => 6},
{:date => 20090502, :depth => 2},
]
temp = Hash.new {|hash,key| hash[key] = {}}
incoming.each {|row| temp[row[:date]].update(row)}
outgoing = temp.values.sort {|*rows| rows[0][:date] <=> rows[1][:date]}
The only thing that's at all tricky here is the Hash constructor, which allows you to supply a block that's called when you access a nonexistent key. So I have the Hash create an empty Hash for us to update with the values we're finding. Then I just use the date as the hash keys, sort the hash values by date and we're transformed.
Try this:
incoming = [ {:date => 20090501, :width => 2},
{:date => 20090501, :height => 7},
{:date => 20090501, :depth => 3},
{:date => 20090502, :width => 4},
{:date => 20090502, :height => 6},
{:date => 20090502, :depth => 2},
]
# Grouping by `:date`
temp = {}
incoming.each do |row|
if temp[row[:date]].nil?
temp[row[:date]] = []
end
temp[row[:date]] << row
end
# Merging it together
outcoming = []
temp.each_pair do |date, hashlist|
res = {}
hashlist.each do |hash|
res.merge!(hash)
end
outcoming << res
end
For information concerning the hash-members, see this page
When ordering is important, you must use jagged arrays:
incoming = [ {:date => 20090501, :width => 2},
{:date => 20090501, :height => 7},
{:date => 20090501, :depth => 3},
{:date => 20090502, :width => 4},
{:date => 20090502, :height => 6},
{:date => 20090502, :depth => 2},
]
# Grouping by `:date`
temp = {}
incoming.each do |row|
if temp[row[:date]].nil?
temp[row[:date]] = []
end
key = row[:date]
row.delete :date
temp[key] << row
end
# Merging it together
outcoming = []
temp.each_pair do |date, hashlist|
res = [:date, date]
hashlist.each do |hash|
hash.each_pair {|key, value| res << [key, value] }
end
outcoming << res
end

Resources