2

Lets say I have the following array:

my_array = [1, 5, 8, 11, -6]

I need to iterate over this array and add the values prior to the current value together. An example will probably be easier to understand. I need to return an array that should look something like this:

final_array = [1, 6, 14, 25, 19]

I have tried doing something like this:

my_array.collect {|value| value + previous_values }

But obviously that doesn't work because I can't figure out how to get the previous values in the array.

I am a programming noob so this might be easier than I am making it. I am pretty sure I need to use either collect or inject, but I can't seem to figure out how to do this.

Any help would be appreciated.

James
  • 220
  • 2
  • 11

6 Answers6

9

My very first instinct was: "That's obviously a scan (aka prefix-sum), so that should be easy":

[1, 5, 8, 11, -6].scan(:+)

Obviously, I've been reading way too much Haskell and Scala lately, because there is no Enumerable#scan in Ruby … yet:

module Enumerable
  def scan(initial=first, &block)
    [initial].tap {|res| 
      reduce {|acc, el| 
        block.(acc, el).tap {|el|
          res << el
        }
      }
    }
  end
end

If you want Enumerable#scan to behave like Enumerable#reduce, i.e. take an optional initial argument and an optional symbol, we need to enhance our version slightly with some argument massaging code stolen from Rubinius's Enumerable#reduce:

module Enumerable
  def scan(initial=nil, sym=nil, &block)
    args = if initial then [initial] else [] end
    unless block_given?
      args, sym, initial = [], initial, first unless sym
      block = ->(acc, el) { acc.send(sym, el) }
    end
    [initial || first].tap {|res| 
      reduce(*args) {|acc, el| 
        block.(acc, el).tap {|e|
          res << e
        }
      }
    }
  end
end

With this enhanced version, the example above now works:

p [1, 5, 8, 11, -6].scan(:+)
# => [1, 6, 14, 25, 19]

If you have this kind of problem again, in another language, remember the terms scan and prefix-sum, such functions are usually pretty common. I don't quite understand why Ruby doesn't have them already.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • I share your instinct.... It's fairly ridiculous this hasn't been a builtin since the beginning... – Jonah Aug 14 '17 at 03:50
6

Your own attempt at it with collect was already very close; just keep summing the previous values as you go.

my_array = [1, 5, 8, 11, -6]
previous_values = 0
my_array.collect { |value| previous_values += value }
# => [1, 6, 14, 25, 19]
Arkku
  • 41,011
  • 10
  • 62
  • 84
3
x = 0
[1, 5, 8, 11, -6].map {|a| x = x +a }
Anton
  • 2,653
  • 1
  • 16
  • 7
1
my_array.each_index{|i| my_array[i] += my_array[i-1] if i>0 }

or

my_array.inject([]){|memo, item| memo << item + memo.last.to_i }
Draco Ater
  • 20,820
  • 8
  • 62
  • 86
  • inject is unlikely to work as memo.last will return nil initially. – Anton May 17 '10 at 08:26
  • Yes, you are right. I thought it will convert automatically to int, but no, so added explicitly `to_i`. – Draco Ater May 17 '10 at 08:28
  • Seems unnecessarily complex having to make a special case of the first element in both solutions. – Arkku May 17 '10 at 08:53
  • @Arkku Actually it is necessary to add `if i>0` in first solution, because otherwise `my_array[i-1]` will be `my_array[0-1] = my_array[-1]`, which is last element of array. – Draco Ater May 17 '10 at 09:03
  • After: Yes, I know, that's why it seems unnecessarily complex; the first element becomes a special case. – Arkku May 17 '10 at 12:27
1

You can use this:

my_array = [1, 5, 8, 11, -6]
final_array = []

my_array.inject(0) {|res, it| final_array << res + it; res + it}
Yossi
  • 11,778
  • 2
  • 53
  • 66
1

I made a gem for this that pre-allocates the result array. The operation is very fast, even for Enumerables with a large number of elements. Unlike solutions using Enumerable#map, the syntax is exactly like that of Enumerable#reduce, and can optionally use Enumerable#reduce under the hood in case you have monkey-patched #reduce. The name was taken from Clojure's function of the same name.

https://rubygems.org/gems/reductions

To install:

$ gem install reductions

To use:

require 'reductions'

[1, 5, 8, 11, -6].reductions(:+)            #=> [1, 6, 14, 25, 19]
[1, 5, 8, 11, -6].reductions{|a| a + b}     #=> [1, 6, 14, 25, 19]

# With an inital value:
[1, 5, 8, 11, -6].reductions(1,:+)          #=> [1, 2, 7, 15, 26, 20]
[1, 5, 8, 11, -6].reductions(1){|a| a + b}  #=> [1, 2, 7, 15, 26, 20]
Penn Taylor
  • 362
  • 2
  • 9