Reputation: 45
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, 41, 23]
]
These are just examples, the actual arrays can be larger.
Upvotes: 0
Views: 611
Reputation: 114138
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.
Upvotes: 0
Reputation: 54223
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]]
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"]]
Upvotes: 4