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.

jjnevis
  • 2,672
  • 3
  • 22
  • 22

9 Answers9

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

m02ph3u5
  • 3,022
  • 7
  • 38
  • 51
glenn mcdonald
  • 15,290
  • 3
  • 35
  • 40
  • 1
    ... or in rails: a.transpose.map {|x| x.sum} – jjnevis Apr 21 '10 at 12:59
  • 6
    @jjnevis: well, if we're golfing it, how about `a.transpose.map(&:sum)` – rampion Apr 21 '10 at 18:25
  • @rampion: That's certainly quite terse - could you explain the (&:sum) bit? Thanks – jjnevis Apr 22 '10 at 07:28
  • 4
    @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. – rampion Apr 23 '10 at 14:30
  • But note that in 1.9 `reduce` takes a symbol directly, thus the `:+` there instead of needing `&:+`... – glenn mcdonald Apr 24 '10 at 02:35
  • 1
    @jjnevis It works for me outside rails in ruby 2.4.0p0 – Pedro Adame Vergara May 16 '17 at 08:04
18

Now we can use sum in 2.4

nums = [[1, 2, 3], [4, 5, 6]]
nums.transpose.map(&:sum) #=> [5, 7, 9]
cavin kwon
  • 501
  • 3
  • 5
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]
Abdo
  • 13,549
  • 10
  • 79
  • 98
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.

Anurag
  • 140,337
  • 36
  • 221
  • 257
  • 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. – jjnevis Apr 21 '10 at 11:52
  • 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! – Anurag Apr 21 '10 at 11:58
  • 2
    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(&:+) }` – rampion Apr 21 '10 at 18:22
  • 2
    I think that `arr.zip(arr).map { |a,b| a + b }` is more readable than using inject/reduce over a 2 element array. – MrPopinjay Sep 17 '14 at 10:11
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?

aristotll
  • 8,694
  • 6
  • 33
  • 53
6
[[1,2,3],[4,5,6]].transpose.map{|a| a.sum} #=> [5,7,9]
Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
Milap Neupane
  • 69
  • 1
  • 3
  • 2
    Note that this doesn't work in plain Ruby. Maybe it does in Rails, [as suggested by jjnevis](http://stackoverflow.com/questions/2682411/ruby-sum-corresponding-members-of-two-arrays#comment2702699_2682983). – FriendFX Dec 18 '13 at 04:35
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]
cevaris
  • 5,671
  • 2
  • 49
  • 34
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]
Surya
  • 15,703
  • 3
  • 51
  • 74
  • 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. – jjnevis Oct 05 '16 at 15:05
  • A more succinct solution is ` a.zip(b).map(&:sum)` – Pere Joan Martorell Apr 06 '22 at 22:20
  • @PereJoanMartorell : Sorry that you had to find out this way, but the `sum` method did not exist in Ruby when this was posted. – Surya Apr 07 '22 at 10:10
  • @Surya 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 better – Pere Joan Martorell May 16 '22 at 16:09
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

Johnson
  • 1,679
  • 1
  • 14
  • 21
  • 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|" – jjnevis Sep 29 '14 at 08:39
  • this is not very idiomatic Ruby. Using `while` loops is *very* rare – Bryan Ash Oct 20 '16 at 00:35