Ruby: Sum Corresponding Members of Two or More Arrays

ruby: sum corresponding members of two or more arrays

Here's the transpose version Anurag suggested:

[[1,2,3], [4,5,6]].transpose.map {|x| x.reduce(:+)}

This will work with any number of component arrays. reduce and inject are synonyms, but reduce seems to me to more clearly communicate the code's intent here...

How can I sum corresponding elements of several arrays?

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

How to group and sum values in array of arrays with the same structure

Case 1: The values of :data are arrays of the same size whose elements (two-element arrays) are ordered the same by their first elements

arr = [{ :name=>"cardio",   :data=>[["06", 999], ["07", 50], ["08", 0]] }, 
{ :name=>"swimming", :data=>[["06", 0], ["07", 60], ["08", 0]] }]

a, b = arr.map { |h| h[:data].transpose }.transpose
#=> [[["06", "07", "08"], ["06", "07", "08"]], [[999, 50, 0], [0, 60, 0]]]
{ :data=>a.first.zip(b.transpose.map { |col| col.reduce(:+) }) }
#=> {:data=>[["06", 999], ["07", 110], ["08", 0]]}

Case 2: The values of :data are arrays which may differ in size and whose elements (two-element arrays) may not be ordered the same by their first element

arr = [{ :name=>"cardio",   :data=>[["05", 999], ["07", 50], ["08",  0]] }, 
{ :name=>"swimming", :data=>[["08", 300], ["04", 33], ["07", 60]] }]

{ :data=>arr.flat_map { |g| g[:data] }.
each_with_object(Hash.new(0)) { |(f,v),h| h[f] += v }.
sort.
to_a }
#=> {:data=>[["04", 33], ["05", 999], ["07", 110], ["08", 300]]}

Note:

  • depending on requirements, sort may not be required
  • the second method could be used regardless of whether the values of :data are defined in parallel
  • the second method uses the form of Hash::new which takes an argument (the default value) which here is zero. This is sometimes called a counting hash. See the doc for details.

How to find array elements that are the sum of a given number

Like @Stefan and @Jorg said in comments there is no easy way to do it. If this was a question to myself, I would probably write down something like this.

arr = [1, 1, 3, 4, 5, 7]
number = 8
result = []

for i in 0..(arr.length) do
arr.combination(i).each do |combination|
result.push(combination) if combination.sum == number
end
end

print result.uniq

How to sum 2-dimensional arrays


Complete Matrix

You can use each_slice, transpose, map and transpose again to navigate your matrix.
The code first uses join('+') to show what is being calculated :

array= [[10,12,15,17],[16,32,65,47],[45,48,41,23],[36,25,74,98],[32,19,66,88],[1,2,3,4]]

array.each_slice(3).to_a.transpose.map{|r| r.transpose.map{|x| x.join('+')}}
# => [["10+36", "12+25", "15+74", "17+98"], ["16+32", "32+19", "65+66", "47+88"], ["45+1", "48+2", "41+3", "23+4"]]

array.each_slice(3).to_a.transpose.map{|r| r.transpose.map{|x| x.inject(:+)}}
# => [[46, 37, 89, 115], [48, 51, 131, 135], [46, 50, 44, 27]]

Warning!

You need to carefully select the each_slice parameter to suit your original array. transpose might raise an exception otherwise :

array = [[10,12,15,17],[19,32,65,47],[45,48,41,23],[36,25,74,98],[10,12,15,17],[16,98,65,47],[69,48,65,23],[66,25,74,98]]
array.each_slice(3).to_a.transpose.map{|r| r.transpose.map{|x| x.inject(:+)}}
#=> IndexError: element size differs (2 should be 3)
array.each_slice(4).to_a.transpose.map{|r| r.transpose.map{|x| x.inject(:+)}}
#=> [[20, 24, 30, 34], [35, 130, 130, 94], [114, 96, 106, 46], [102, 50, 148, 196]]

Incomplete Matrix

If the matrix size isn't a multiple of width :

array = [
[10, 12, 15 ,17], [16, 32, 65, 47], [45, 48, 41, 23],
[36, 25, 74, 98], [32, 19, 66, 88]
]

you could add subarrays full of 0s to get :

matrix = [
[10, 12, 15 ,17], [16, 32, 65, 47], [45, 48, 41, 23],
[36, 25, 74, 98], [32, 19, 66, 88], [ 0, 0, 0, 0]
]

Array#fill does the job :

def maxtrix_column_sums(array, width)
size = array.size
size2 = array.first.size
missing = (-size) % width
matrix = array.dup.fill(Array.new(size2, 0), size...size + missing)
matrix.each_slice(width).to_a.transpose.map { |r| r.transpose.map { |x| x.join('+') } }
end

p maxtrix_column_sums(array, 3)
#=> [["10+36", "12+25", "15+74", "17+98"], ["16+32", "32+19", "65+66", "47+88"], ["45+0", "48+0", "41+0", "23+0"]]

Add corresponding elements in two arrays of different length


b.zip(a * (b.size / a.size + 1)).map { |o| o.reduce(:+) }
#⇒ [69, 24, 62, 44, 86, 25, 72, 62, 68, 31]

Or, much better and more concise from @SimpleLime:

b.zip(a.cycle).map(&:sum)

How to sum multiple elements of nested arrays on unique keys

This is essentially the same as the solution to your previous question.

data = [ [ 123.0, 23, "id1", 34, "abc" ], 
[ 234.1, 43, "id2", 24, "jsk" ],
[ 423.5, 53, "id1", 1, "xyz" ],
[ 1.4, 5, "id2", 0, "klm" ] ]

sums = Hash.new {|h,k| h[k] = [0, 0, 0] }

data.each_with_object(sums) do |(val0, val1, id, val2, _), sums|
sums[id][0] += val0
sums[id][1] += val1
sums[id][2] += val2
end
# => { "id1" => [ 546.5, 76, 35 ],
# "id2" => [ 235.5, 48, 24 ] }

The main difference is that instead of giving the Hash a default value of 0, we're giving it a default proc that initializes missing keys with [0, 0, 0]. (We can't just do Hash.new([0, 0, 0]) because then every value would be a reference to a single Array instance, rather than each value having its own Array.) Then, inside the block, we add each value (val0 et al) to the corresponding elements of sums[id].

If you wanted an Array of Arrays instead of a Hash with the id at index 2, then at the end, you would have to add something like this:

.map {|id, vals| vals.insert(2, id) }

However, a Hash with the ids as keys makes more sense as a data structure.

Sum of the previous elements in an array

You can iterate with each_with_object

numbers = [3.0, 3.0, 2.0, 5.0, 6.0, 10.0]

sum_of_repvious = numbers.each_with_object([]) do |number, accu|
previous = accu.last || 0 # on the first iteration, there is no previous, initialize to 0
accu << previous += number
end

p sum_of_repvious #> [3.0, 6.0, 8.0, 13.0, 19.0, 29.0]

Let me explain a bit more in detail what is going on here:

With each_with_object you iterate the array and have an additional object that is passed to each iteration. I chose it to be an empty array (the []).

I call this accumlator or accu for short.

In this array i store the sum of the previous numbers. On the first step, there is no previous sum, hence the || 0 to make sure that we have 0 instead of nil.

Now you have an array that has the sum of the numbers in your original array up to the respective index.



Related Topics



Leave a reply



Submit