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
How to Include Variables in My Vagrantfile
The Encoding That Notepad++ Just Calls "Ansi", Does Anyone Know What to Call It for Ruby
Rails 4 + Devise: Invalid Route Name, Already in Use
Do Fixtures Trigger Model Callbacks
Why Doesn't Rails' "Errors.Full_Messages" Replace Attribute and Message Variables
How to Add a Primary Key to a Table in Rails
How Does Rails Csrf Protection Work
Rails: Plus Sign in Get-Request Replaced by Space
How to Write a Shell Script That Starts Tmux Session, and Then Runs a Ruby Script
How to Check If a Class Is Defined
How to Handle Errors with Httparty
Scope of Constants in Ruby Modules
How to Sort a String's Characters Alphabetically
How to Use Dot Syntax for Ruby Hash