How to sum 2-dimensional arrays

608 views Asked by At

I have a two-dimensional array with sub-arrays of equal size, for example:

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

I would like to create a new array by summing the corresponding elements of every 4th sub-array, i.e. the elements that are "on top of each other" in the above example:

new_array = [
  [10 + 36, 12 + 25, 15 + 74, 17 + 98],
  [16 + 32, 32 + 19, 65 + 66, 47 + 88],
  [45, 48, 4‌​1, 23]
]

These are just examples, the actual arrays can be larger.

2

There are 2 answers

3
Eric Duminil On BEST ANSWER

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"]]
0
Stefan On

Here's a variation of Eric Duminil's answer using zip instead of transpose to account for an "odd" number of sub-arrays:

first, *rest = array.each_slice(3).to_a
first.zip(*rest).map { |r| r.compact.transpose.map { |x| x.inject(:+) } }
#=> [[46, 37, 89, 115], [48, 51, 131, 135], [45, 48, 41, 23]]

How it works:

each_slice separates the array into groups of 3:

array.each_slice(3).to_a
#=> [
#     [[10, 12, 15, 17], [16, 32, 65, 47], [45, 48, 41, 23]],
#     [[36, 25, 74, 98], [32, 19, 66, 88]]
#   ]

first.zip(*rest) combines the first slice "column"-wise with the remaining slices, adding nil when a slice is missing:

first.zip(*rest)
#=> [
#     [[10, 12, 15, 17], [36, 25, 74, 98]],
#     [[16, 32, 65, 47], [32, 19, 66, 88]],
#     [[45, 48, 41, 23], nil]
#   ]

The map / compact / transpose part then restructures the sub-array while getting rid of nil values:

first.zip(*rest).map { |r| r.compact.transpose }
#=> [
#     [[10, 36], [12, 25], [15, 74], [17, 98]],
#     [[16, 32], [32, 19], [65, 66], [47, 88]],
#     [[45],     [48],     [41],     [23]]
#   ]

And inject(:+) finally sums the inner elements.