How can I sum corresponding elements of several arrays? - ruby

I have several arrays of various lengths, each with a 2-item array within it. For example:
[["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]]
or
[["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44], ["03:00", 46.11], ["04:00", 8.9], ["05:00", 18.187]]
I want to loop through, and add the corresponding elements of each array, into a new array. The output array will be the length of the longest array we're adding together.
So the two arrays above summed together would output the following:
[["12:00", 51.3], ["01:00", 5.55], ["02:00", 14.34], ["03:00", 102.61], ["04:00", 8.9], ["05:00", 18.187]]
I don't think I can use reduce() or inject() since I don't want to collapse the array, nor is the array a simple array of elements.
I really have no idea how I might approach this problem.

You can do it in one line with Hash.merge. Use a block to sum the values during the merge.
def sum_arrays(a, b)
Hash[a].merge(Hash[b]){|k, i, j| i + j}.to_a
end
Output:
a = [["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]]
b = [["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44], ["03:00", 46.11], ["04:00", 8.9], ["05:00", 18.187]]
sum_arrays(a,b)
=> [["12:00", 51.3], ["01:00", 5.55], ["02:00", 14.34], ["03:00", 102.61], ["04:00", 8.9], ["05:00", 18.187]]
To sum more than two arrays, add one more line:
def sum_many_arrays(*a)
a.reduce{|s, i| sum_arrays(s, i)}
end
Output:
sum_many_arrays([[:a,1],[:b,2]],[[:a,2],[:b,2],[:c,1]],[[:a,5],[:b,2]])
=> [[:a, 8], [:b, 6], [:c, 1]]

I recommend #Oleg's approach, but there's always another way:
arr = [[["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]],
[["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44], ["03:00", 46.11],
["04:00", 8.9]],
[["01:00", 22.25], ["02:00", 1.84], ["12:00", 13.3]]]
keys = arr.reduce([]) { |keys,a| keys | a.map(&:first) }
arr.map { |a| a.to_h.values_at(*keys) }
.transpose
.map { |e| [keys.shift, e.reduce(0) { |tot,x| tot + x.to_f }] }
#=> [["12:00", 64.6], ["01:00", 27.8], ["02:00", 16.18],
# ["03:00", 102.61], ["04:00", 8.9]]
The steps:
keys = arr.reduce([]) { |keys,a| keys | a.map(&:first) }
#=> ["12:00", "01:00", "02:00", "03:00", "04:00"]
b = arr.map { |a| a.to_h.values_at(*keys) }
#=> [[ 7.0, 3.3, 11.9, 56.5, nil],
# [44.3, 2.25, 2.44, 46.11, 8.9],
# [13.3, 22.25, 1.84, nil, nil]]
c = b.transpose
#=> [[7.0, 44.3, 13.3],
# [3.3, 2.25, 22.25],
# [11.9, 2.44, 1.84],
# [56.5, 46.11, nil],
# [nil, 8.9, nil]]
c.map { |e| [keys.shift, e.reduce(0) { |tot,x| tot + x.to_f }] }
#=> [["12:00", 64.6], ["01:00", 27.8], ["02:00", 16.18],
# ["03:00", 102.61], ["04:00", 8.9]]
Notice that NilClass#to_f converts nil to 0.0.
To elaborate the calculation of b above:
d = arr.map
#=> #<Enumerator: [[["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9],
# ["03:00", 56.5]],
# [["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44],
# ["03:00", 46.11], ["04:00", 8.9]],
# [["01:00", 22.25], ["02:00", 1.84], ["12:00", 13.3]]]:map>
a = d.next
#=> [["12:00", 7.0], ["01:00", 3.3], ["02:00", 11.9], ["03:00", 56.5]]
e = a.to_h
#=> {"12:00"=>7.0, "01:00"=>3.3, "02:00"=>11.9, "03:00"=>56.5}
f = e.values_at(*keys)
#=> e.values_at(*["12:00", "01:00", "02:00", "03:00", "04:00"] )
#=> [7.0, 3.3, 11.9, 56.5, nil]
a = d.next
#=> [["12:00", 44.3], ["01:00", 2.25], ["02:00", 2.44],
# ["03:00", 46.11], ["04:00", 8.9]]
e = a.to_h
#=> {"12:00"=> 44.3, "01:00"=>2.25, "02:00"=>2.44,
# "03:00"=>46.11, "04:00"=> 8.9}
f = e.values_at(*keys)
#=> [44.3, 2.25, 2.44, 46.11, 8.9]
a = d.next
#=> [["01:00", 22.25], ["02:00", 1.84], ["12:00", 13.3]]
e = a.to_h
#=> {"01:00"=>22.25, "02:00"=>1.84, "12:00"=>13.3}
f = e.values_at(*keys)
#=> [13.3, 22.25, 1.84, nil, nil]
c.map is computed as follows:
keys = ["12:00", "01:00", "02:00", "03:00", "04:00"]
d = c.map
#=> #<Enumerator: [[ 7.0, 44.3, 13.3], [3.3, 2.25, 22.25],
# [11.9, 2.44, 1.84], [56.5, 46.11, nil],
# [nil, 8.9, nil]]:map>
e = d.next
#=> [7.0, 44.3, 13.3]
f = keys.shift
#=> "12:00"
keys
#=> ["01:00", "02:00", "03:00", "04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 64.6
e = d.next
#=> [3.3, 2.25, 22.25]
f = keys.shift
#=> "01:00"
keys
#=> ["02:00", "03:00", "04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 27.8
e = d.next
#=> [11.9, 2.44, 1.84]
f = keys.shift
#=> "02:00"
keys
#=> ["03:00", "04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 16.18
e = d.next
#=> [56.5, 46.11, nil]
f = keys.shift
#=> "03:00"
keys
#=> ["04:00"]
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 102.61
e = d.next
#=> [nil, 8.9, nil]
f = keys.shift
#=> "04:00"
keys
#=> []
e.reduce(0) { |tot,x| tot + x.to_f }
#=> 8.9

Related

Ruby - converting a string into hash with each character as key and index as value?

I am trying to transform a given string into a hash with each its character = key and index = value.
For example, if I have str = "hello", I would like it to transform into {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}.
I created a method as such:
def map_indices(arr)
arr.map.with_index {|el, index| [el, index]}.to_h
end
#=> map_indices('hello'.split(''))
#=> {"h"=>0, "e"=>1, "l"=>3, "o"=>4}
The problem is it skips the first l. If I reverse the order of el and index: arr.map.with_index {|el, index| [index, el]}.to_h, I get all the letters spelled out: {0=>"h", 1=>"e", 2=>"l", 3=>"l", 4=>"o"}
But when I invert it, I get the same hash that skips one of the l's.
map_indices('hello'.split('')).invert
#=> {"h"=>0, "e"=>1, "l"=>3, "o"=>4}
Why is this behaving like such? How can I get it to print {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}?
It can be done, but will confuse other Ruby programmers.A normal hash treats a key "a" as identical to another "a". Unless a little known feature .compare_by_identity is used:
h = {}.compare_by_identity
"hello".chars.each_with_index{|c,i| h[c] = i}
p h # => {"h"=>0, "e"=>1, "l"=>2, "l"=>3, "o"=>4}
Any of the following could be used. For
str = "hello"
all return
{"h"=>[0], "e"=>[1], "l"=>[2, 3], "o"=>[4]}
str.each_char
.with_index
.with_object({}) { |(c,i),h| (h[c] ||= []) << i }
See String#each_char, Enumerator#with_index and Enumerator#with_object. The block variables have been written to exploit array decomposition.
str.each_char
.with_index
.with_object(Hash.new { |h,k| h[k] = [] }) { |(c,i),h| h[c] << i }
See the form of Hash::new that takes a block and no argument. If a hash has been defined
h = Hash.new { |h,k| h[k] = [] }
and later
h[c] << i
is executed, h[c] is first set equal to an empty array if h does not have a key c.
str.size
.times
.with_object(Hash.new { |h,k| h[k] = [] }) { |i,h| h[str[i]] << i }
str.each_char
.with_index
.group_by(&:first)
.transform_values { |a| a.flat_map(&:last) }
See Enumerable#group_by, Hash#transform_values (introduced in Ruby v2.5) and Enumerable#flat_map.
Note that
str.each_char
.with_index
.group_by(&:first)
#=> {"h"=>[["h", 0]], "e"=>[["e", 1]], "l"=>[["l", 2], ["l", 3]],
# "o"=>[["o", 4]]}
Another option you can use is zipping two enumerations together.
s = "hello"
s.chars.zip(0..s.size)
This yields: [["h", 0], ["e", 1], ["l", 2], ["l", 3], ["o", 4]]
I am new to Ruby and I am sure this can be refactored, but another alternative might be:
arr1 = "Hello".split(%r{\s*})
arr2 = []
for i in 0..arr1.size - 1
arr2 << i
end
o = arr1.zip(arr2)
a_h = []
o.each do |i|
a_h << Hash[*i]
end
p a_h.each_with_object({}) { |k, v| k.each { |kk,vv| (v[kk] ||= []) << vv } }
=> {"H"=>[0], "e"=>[1], "l"=>[2, 3], "o"=>[4]}

Conversion of Ruby Hashes to Sort array of Hash

I have following input:
array = [{:year=>2015, :platform_id=>2},
{:year=>nil, :platform_id=>2},
{:year=>nil, :platform_id=>4},
{:year=>2015, :platform_id=>4}]
I need expected result to be:
[{platform_id=>2, year=>[2015, nil]},
{platform_id=>4, year=>[nil, 2015]}]
What I code is:
array.inject(:merge)
But that gets me this result, which is not what I want:
{:year=>2015, :platform_id=>4}
Updated below after answer received:
I do performance test after see the answers and here is the result:
arr = [
{:year => 2015, :platform_id => 2},
{:year => nil, :platform_id => 2},
{:year => nil, :platform_id => 4},
{:year => 2015, :platform_id => 4}
]
#approach 1
x1 = Time.now.to_f
exp = arr.each_with_object({}) do |h, exp|
exp[h[:platform_id]] ||= {:platform_id => h[:platform_id], :year => []}
exp[h[:platform_id]][:year] << h[:year]
end.values
x2 = Time.now.to_f
p x2-x1
#approach 2
x3 = Time.now.to_f
new_data = arr.group_by { |d| d[:platform_id] }
new_arr = []
new_data.each do |k,v|
t2 = v.map{|x| x[:year]}
temp = {"platform_id": k, "years": t2}
new_arr.push(temp)
end
x4 = Time.now.to_f
p x4-x3
#approach 3
x5 = Time.now.to_f
f = arr.each_with_object({}) { |g,h|
h.update(g[:platform_id]=>[g[:year]]) { |_,o,n| o+n } }
#=> {2=>[2015, nil], 4=>[nil, 2015]}
f.map { |k,v| { :platform_id=>k, :year=>v } }
x6 = Time.now.to_f
p x6-x5
#output is:
9.059906005859375e-06
6.4373016357421875e-06
9.775161743164062e-06
arr = [
{ :year=>2015, :platform_id=>2 },
{ :year=>nil, :platform_id=>2 },
{ :year=>nil, :platform_id=>4 },
{ :year=>2015, :platform_id=>4 }
]
arr.each_with_object({}) { |g,h|
h.update(g[:platform_id]=>[g[:year]]) { |_,o,n| o+n } }.
map { |k,v| { :platform_id=>k, :year=>v } }
#=> [{:platform_id=>2, :year=>[2015, nil]},
# {:platform_id=>4, :year=>[nil, 2015]}]
The two steps are as follows.
f = arr.each_with_object({}) { |g,h|
h.update(g[:platform_id]=>[g[:year]]) { |_,o,n| o+n } }
#=> {2=>[2015, nil], 4=>[nil, 2015]}
f.map { |k,v| { :platform_id=>k, :year=>v } }
#=> [{:platform_id=>2, :year=>[2015, nil]},
# {:platform_id=>4, :year=>[nil, 2015]}]
The first step uses the form of Hash#update (aka merge!) that employs a block (here { |_,o,n| o+n }) that computes the value of keys that are present in both hashes being merged.
You can use each_with_object with hash and print values like this
arr = [
{:year => 2015, :platform_id => 2},
{:year => nil, :platform_id => 2},
{:year => nil, :platform_id => 4},
{:year => 2015, :platform_id => 4}
]
exp = arr.each_with_object({}) do |h, exp|
exp[h[:platform_id]] ||= {:platform_id => h[:platform_id], :year => []}
exp[h[:platform_id]][:year] << h[:year]
end.values
p exp
# => [{:platform_id=>2, :year=>[2015, nil]}, {:platform_id=>4, :year=>[nil, 2015]}]
Here is the answer:
data = [
{:year=>2015, :platform_id=>2},
{:year=>nil, :platform_id=>2},
{:year=>nil, :platform_id=>4},
{:year=>2015, :platform_id=>4}
]
new_data = data.group_by { |d| d[:platform_id] }
#p new_data
new_arr = []
new_data.each do |k,v|
t2 = v.map{|x| x[:year]}
temp = {"pf_id": k, "years": t2}
new_arr.push(temp)
end
p new_arr

How can I build a hash based in a string in ruby?

Example -
String1: "{t: 1},{t2: 2}"
String2: "{s: 1},{s2: 2}"
Build an array of hashes which returns
[
{t: 1},
{t2: 2},
{s: 1},
{s2: 2}
]
If you are given an hash:
h = { String1: "{t: 1},{t2: 2}", String2: "{s: 1},{s2: 2}" }
and wish to return an array:
[ {t: 1}, {t2: 2}, {s: 1}, {s2: 2} ]
you can do it thus:
h.values.flat_map do |s|
s.split(',').map do |t|
k,v = t.scan(/\w+/)
{ k.to_sym=>v }
end
end
#=> [{:t=>"1"}, {:t2=>"2"}, {:s=>"1"}, {:s2=>"2"}]
The steps:
a = h.values
#=> ["{t: 1},{t2: 2}", "{s: 1},{s2: 2}"]
enum = a.flat_map
#=> #<Enumerator: ["{t: 1},{t2: 2}", "{s: 1},{s2: 2}"]:flat_map>
The first element of the enumerator is passed to the block:
s = enum.next
#=> "{t: 1},{t2: 2}"
c = s.split(',')
#=> ["{t: 1}", "{t2: 2}"]
map maps each of c's two elements to a hash:
k,v = "{t: 1}".scan(/\w+/)
#=> ["t", "1"]
{ k.to_sym=>v }
#=> {:t=>"1"}
and
k,v = "{t2: 2}".scan(/\w+/)
#=> ["t2", "2"]
{ k.to_sym=>v }
#=> {:t2=>"2"}
The second and final element of enum is now passed to the block:
s = enum.next
#=> "{s: 1},{s2: 2}"
The above steps are repeated to obtain the two hashes {:s=>1} and {:s2=>2}. Since we are using Enumerable#flat_map, we obtain:
[{:t=>"1"}, {:t2=>"2"}, {:s=>"1"}, {:s2=>"2"}]
rather than:
[[{:t=>"1"}, {:t2=>"2"}], [{:s=>"1"}, {:s2=>"2"}]]
which is what we'd get if we used Enumerable#map.
What you are expecting is invalid, as #mudasobwa says.
But what you can do, is use eval to convert it to array of hashes:
a = "{t: 1},{t2: 2}"
#=> "{t: 1},{t2: 2}"
b = "{s: 1},{s2: 2}"
#=> "{s: 1},{s2: 2}"
[a.split(','), b.split(',')].flatten.map{|a| eval a}
#=> [{:t=>1}, {:t2=>2}, {:s=>1}, {:s2=>2}]

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]}]

Finding indexes of each element in a multidimensional array in ruby

Eg
:a=[["hello", "world"], ["good", "lord"], ["hello", "lord"]]
I need to find and record the indexes of each word with respect to the super-array.
i.e
hello => 0,2
world => 0
lord => 1,2.
here's my shot ,but its very amateurish and lengthy.
all_tokens=tokens.flatten
all_tokens.each do|keyword|
tokens.each do|token_array|
if token_array.include?keyword
x << i
end
i=i+1
end
y[k] = x.clone
y=y.clear
end
Slight improvement (imho) on vava's solution:
tokens = [["hello", "world"], ["good", "lord"], ["hello", "lord"]]
tokens_hash = Hash.new{|h, k| h[k] = []}
tokens.each_with_index do |subarr, i|
subarr.each do |word|
tokens_hash[word] << i
end
end
ret = []
a.each_with_index {|x, i| if x.include?(keyword) then ret << i end }
a.each_with_index.inject({}){|acc,(elem,i)|
elem.each{|e|
acc[e] ||= []
acc[e] << i
}
acc
}
#=> {"hello"=>[0, 2], "world"=>[0], "good"=>[1], "lord"=>[1, 2]}
tokens = [["hello", "world"], ["good", "lord"], ["hello", "lord"]]
tokens_hash = Hash.new([])
tokens.each_with_index do |subarr, i|
subarr.each do |word|
tokens_hash[word] = tokens_hash[word] + [i]
end
end
p tokens_hash #=>{"good"=>[1], "world"=>[0], "lord"=>[1, 2], "hello"=>[0, 2]}
My solution will scan the whole structure just once.
Just for grins, a functional solution:
#!/usr/bin/ruby1.8
a = [["hello", "world"], ["good", "lord"], ["hello", "lord"]]
b = a.flatten.uniq.inject({}) do |hash, word|
hash.merge(word => a.each_with_index.collect do |list, i|
list.index(word) && i
end.compact)
end
p b # => {"world"=>[0], "good"=>[1], "lord"=>[1, 2], "hello"=>[0, 2]}
a=[["hello", "world"], ["good", "lord"], ["hello", "lord"]]
result = Hash.new{|k,v| k[v] = []}
a.each_with_index{|b,i| b.each{|c| result[c] << i} }
result
#=> {"good"=>[1], "world"=>[0], "lord"=>[1, 2], "hello"=>[0, 2]}

Resources