ruby: sum corresponding members of two or more arrays
Asked Answered
F

10

44

I've got two (or more) arrays with 12 integers in each (corresponding to values for each month). All I want is to add them together so that I've got a single array with summed values for each month. Here's an example with three values: [1,2,3] and [4,5,6] => [5,7,9]

The best I could come up with was:

[[1,2,3],[4,5,6]].transpose.map{|arr| arr.inject{|sum, element| sum+element}} #=> [5,7,9]

Is there a better way of doing this? It just seems such a basic thing to want to do.

Falsework answered 21/4, 2010 at 11:25 Comment(0)
C
56

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

Cocainize answered 21/4, 2010 at 12:51 Comment(6)
... or in rails: a.transpose.map {|x| x.sum}Falsework
@jjnevis: well, if we're golfing it, how about a.transpose.map(&:sum)Kendall
@rampion: That's certainly quite terse - could you explain the (&:sum) bit? ThanksFalsework
@jjnevis: So, Symbol#to_proc returns a Proc that passes that symbol to its argument (so :sum.to_proc returns lambda {|x| x.sum}). An & prefix-operator for the last argument to a method invokes #to_proc on the argument, and passes the resulting Proc as a block to the method. This allows you to write methods that capture their block arguments (def foo(a,b,&block)...end) and pass them into whatever methods they may call (bar = baz(1, &block)). It also allows this trickery with symbols, since a.transpose.map(&:sum) is the equivalent of your rails solution.Kendall
But note that in 1.9 reduce takes a symbol directly, thus the :+ there instead of needing &:+...Cocainize
@Falsework It works for me outside rails in ruby 2.4.0p0Ramsden
T
18

Now we can use sum in 2.4

nums = [[1, 2, 3], [4, 5, 6]]
nums.transpose.map(&:sum) #=> [5, 7, 9]
Tuyere answered 15/7, 2017 at 4:29 Comment(0)
B
11

For clearer syntax (not the fastest), you can make use of Vector:

require 'matrix'
Vector[1,2,3] + Vector[4,5,6]
=> Vector[5, 7, 9]

For multiple vectors, you can do:

arr = [ Vector[1,2,3], Vector[4,5,6], Vector[7,8,9] ]
arr.inject(&:+)
=> Vector[12, 15, 18]

If you wish to load your arrays into Vectors and sum:

arrays = [ [1,2,3], [4,5,6], [7,8,9] ]
arrays.map { |a| Vector[*a] }.inject(:+)
=> Vector[12, 15, 18]
Bangle answered 9/2, 2014 at 0:53 Comment(2)
This is definitely the cleanest from an understandability perspective.Makepeace
To convert an array to a vector, you can also do Vector.elements([1, 2, 3]), in addition to Vector[*a].Barthol
P
8

here's my attempt at code-golfing this thing:

// ruby 1.9 syntax, too bad they didn't add a sum() function afaik
[1,2,3].zip([4,5,6]).map {|a| a.inject(:+)} # [5,7,9]

zip returns [1,4], [2,5], [3,6], and map sums each sub-array.

Prestidigitation answered 21/4, 2010 at 11:41 Comment(4)
Nice. How would this work with three or more arrays - I'm iterating over an unknown number of arrays, so the transpose works well because I can just push them into an empty array and transpose that. I love the ':+' syntax for inject (although not as good as a sum() method as you say, rails does add it, though)- not see that before, thanks.Falsework
zip is a method of Array, and it accepts any number of arrays as parameters. But I don't know how would you pass those if they were created dynamically. transpose seems like a better choice for that. Ruby generally covers many of the commonly used functions, but where is lacks, Rails comes to rescue!Prestidigitation
say aoa is our array of arrays, and you want to calculate [ aoa[0][0] + aoa[1][0] + ... + aoa[-1][0], aoa[0][1] + aoa[1][1] + ... + aoa[-1][1], ..., aoa[0][-1] + aoa[1][-1] + ... + aoa[-1][-1] ] then you could do first, *rest = *aoa; first.zip(*rest).map { |a| a.inject(&:+) }Kendall
I think that arr.zip(arr).map { |a,b| a + b } is more readable than using inject/reduce over a 2 element array.Carincarina
G
8

I humbly feel that the other answers I see are so complex that they would be confusing to code reviewers. You would need to add an explanatory comment, which just increases the amount of text needed.

How about this instead:

a_arr = [1,2,3]
b_arr = [4,5,6]
(0..2).map{ |i| a_arr[i] + b_arr[i] }

Slightly different solution: (so that you're not hard coding the "2")

a_arr = [1,2,3]
b_arr = [4,5,6]
c_arr = []
a_arr.each_index { |i| c_arr[i] = a_arr[i] + b_arr[i] }

Finally, mathematically speaking, this is the same question as this:

How do I perform vector addition in Ruby?

Gavin answered 27/2, 2015 at 18:59 Comment(0)
K
6
[[1,2,3],[4,5,6]].transpose.map{|a| a.sum} #=> [5,7,9]
Kuster answered 12/12, 2013 at 11:43 Comment(1)
Note that this doesn't work in plain Ruby. Maybe it does in Rails, as suggested by jjnevis.Functionalism
E
2

For anyone wondering what the fastest solution is, these are the times for the answers being run 1,000,000 times each with Benchmark.bmbm

# plain-ole
a[0] += b[0]
a[1] += b[1]
a[2] += b[2]
a
# each_index
a.each_index { |i| a[i] += b[i] }
#zip-sum
a.zip(b).map(&:sum)
# transpose-sum
[a, b].transpose.map(&:sum)
#map
a.map.with_index { |v, i| v + b[i] }
# zip-reduce
a.zip(b).map { _1.reduce(&:+) }
# transpose-reduce
[a, b].transpose.map { _1.reduce(&:+) }
# vector-declared
Vector[1, 2, 3] + Vector[4, 5, 6]
# vector
Vector.elements(a) + Vector.elements(b)
Rehearsal ------------------------------------------------------
plain-ole            0.217787   0.000722   0.218509 (  0.218544)
each_index           0.431834   0.001533   0.433367 (  0.433421)
zip-sum              0.576384   0.002875   0.579259 (  0.579365)
transpose-sum        0.588562   0.002788   0.591350 (  0.591382)
map                  0.774294   0.002759   0.777053 (  0.777108)
zip-reduce           1.217026   0.005008   1.222034 (  1.222105)
transpose-reduce     1.244968   0.005115   1.250083 (  1.250596)
vector-initialized   1.691190   0.004128   1.695318 (  1.695422)
vector               1.972912   0.005582   1.978494 (  1.978689)
--------------------------------------------- total: 8.745467sec

                         user     system      total        real
plain-ole            0.215911   0.000831   0.216742 (  0.216781)
each_index           0.430009   0.001125   0.431134 (  0.431200)
zip-sum              0.567621   0.002756   0.570377 (  0.570465)
transpose-sum        0.589709   0.002606   0.592315 (  0.592342)
map                  0.769459   0.002611   0.772070 (  0.772167)
zip-reduce           1.209870   0.004881   1.214751 (  1.214985)
transpose-reduce     1.230031   0.004926   1.234957 (  1.235065)
vector-initialized   1.687652   0.003992   1.691644 (  1.691750)
vector               1.974240   0.005038   1.979278 (  1.979393)
Eloiseloisa answered 15/12, 2023 at 0:59 Comment(2)
Are there any novel solutions to the question here? It doesn't appear so.Antonantone
No, but when I looked up this question and saw all the proposed solutions I was wondering how they compared speed wise, so I ran the tests and I figured it would be helpful for others who might run the test as I did.Eloiseloisa
S
1

@FriendFX, you are correct about @user2061694 answer. It only worked in Rails environment for me. You can make it run in plain Ruby if you make the following changes...

In the IRB

[[0, 0, 0], [2, 2, 1], [1,3,4]].transpose.map {|a| a.inject(:+)}
 => [3, 5, 5]


[[1,2,3],[4,5,6]].transpose.map {|a| a.inject(:+)}
 => [5, 7, 9]
Sporogenesis answered 7/9, 2014 at 23:59 Comment(0)
C
1

For:

a = [1,2,3]
b = [4,5,6]

You could zip and then use reduce:

p a.zip(b).map{|v| v.reduce(:+) }
#=> [5, 7, 9]

Or, if you're sure that array a and b will always be of equal length:

p a.map.with_index { |v, i| v + b[i] }
#=> [5, 7, 9]
Cawnpore answered 5/10, 2016 at 5:19 Comment(4)
Two very elegant solutions, although I've updated the title to more accurately reflect the question, namely that it's "two or more" arrays, so an array or arrays really.Falsework
A more succinct solution is ` a.zip(b).map(&:sum)`Impercipient
@PereJoanMartorell : Sorry that you had to find out this way, but the sum method did not exist in Ruby when this was posted.Cawnpore
@Cawnpore thanks for the explanation, maybe it would be helpful to offer a solution for different ruby versions so everyone can pick the one that fits betterImpercipient
A
0

This might not be the best answer but it works.

array_one = [1,2,3]
array_two = [4,5,6]
x = 0
array_three = []
while x < array_one.length
  array_three[x] = array_one[x] + array_two[x]
  x += 1
end

=>[5,7,9]

This might be more lines of code than other answers, but it is an answer nonetheless

Algiers answered 29/9, 2014 at 1:58 Comment(2)
I like this one simply because it's very clear what's going on, but you can clean it up a bit by using "array_one.each_index do |x|"Falsework
this is not very idiomatic Ruby. Using while loops is very rareCuisine

© 2022 - 2025 — McMap. All rights reserved.