简体   繁体   中英

Summing up every other array of an array of array of integers using inject

From my understanding of how folding works, given an array of integers and some existing value (say, 2)

val = 2
arr = [1,2,3]

I can say

arr.inject(val) do |r, val|
  r += val
end

To add all of the elements to the specified value in some specific order (left to right?).

So now if I had instead an array of arrays of integers like this

val = 2
arr = [ [1], [2], [3] ]

My logic would be similar, except I now I have an extra layer of arrays:

val = arr.inject(val) do |r, arrElmt|
  r = arrElmt.inject(r, :+)
end

But now I only want every other element starting with the first element, so I want to ignore the [2] and just add up the 1 and 3.

I'm not sure how to do this with a fold so I end up going back to using something like each_with_index and then skipping odd indices.

val = 2
arr = [ [1], [2], [3] ]

arr.each_with_index do |arrElmt, i|
  next if i.odd?
  val = arrElmt.inject(val, :+)
end

Is there a better way to write this?

If the inner arrays are just one element, you can flatten first. There are probably a dozen ways to do this whole thing in Ruby, many of them a little obtuse-looking, but here's another:

val += arr.flatten.values_at(* arr.each_index.select(&:even?)).reduce(:+)

This adds up the odd values and adds the result to val . It might look incorrect at first, with the even? method, but it's because Ruby arrays are 0 index based.

You could do something like this:

val = arr.each_slice(2).inject(val) do |total, (even, _odd)|
   even.inject(total, :+)
end

each_slice(n) yields items n at a time. In this case it will first yield first & second items, then third and fourth and so on. Then I use only the first element of each of these pairs in the sum. If you wanted to sum the odd indices then you'd just change pair[0] to pair[1] || [] pair[1] || []

That said, you could probably argue that your original attempt was clearer.

Here's some fun with destructuring bind of parameters:

arr.each_slice(2).inject(val) {|acc, ((el, *_), _)| acc + el }

A more serious way would be:

arr.each_slice(2).inject(val) {|acc, (el, _)| acc.+(*el) }

or

arr.each_slice(2).inject(val) {|acc, (el, _)| acc + el.first }

Something closer to your original attempt:

arr.each_with_index.inject(val) {|acc, (el, i)|
  if i.odd? then acc else acc + el.first end }

Another way:

def sum_even_elements(arr)
  arr.each_slice(2).map(&:first).flatten.reduce(:+)
end

sum_even_elements([1,2,3,4,5])                     #=> 9 
sum_even_elements([[1],[2],[3],[4],[5]])           #=> 9 
sum_even_elements([[1,2],[3,4],[5,6],[7,8],[9,0]]) #=> 23

If needed:

def sum_odd_elements(arr)
  arr.flatten.reduce(:+) - sum_even_elements(arr)
end

Inefficient? Yes, but just a small cost to make it read well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM