16

I'd like a function runningSum on an array of numbers a (or any ordered collection of addable things) that returns an array of the same length where each element i is the sum of all elements in A up to an including i.

Examples:

runningSum([1,1,1,1,1,1]) -> [1,2,3,4,5,6]
runningSum([2,2,2,2,2,2]) -> [2,4,6,8,10,12]
runningSum([1,0,1,0,1,0]) -> [1,1,2,2,3,3]
runningSum([0,1,0,1,0,1]) -> [0,1,1,2,2,3]

I can do this with a for loop, or whatever. Is there a more functional option? It's a little like a reduce, except that it builds a result array that has all the intermediate values.

Even more general would be to have a function that takes any sequence and provides a sequence that's the running total of the input sequence.

Benjohn
  • 13,228
  • 9
  • 65
  • 127

8 Answers8

13

The general combinator you're looking for is often called scan, and can be defined (like all higher-order functions on lists) in terms of reduce:

extension Array {
    func scan<T>(initial: T, _ f: (T, Element) -> T) -> [T] {
        return self.reduce([initial], combine: { (listSoFar: [T], next: Element) -> [T] in
            // because we seeded it with a non-empty
            // list, it's easy to prove inductively
            // that this unwrapping can't fail
            let lastElement = listSoFar.last!
            return listSoFar + [f(lastElement, next)]
        })
    }
}

(But I would suggest that that's not a very good implementation.)

This is a very useful general function, and it's a shame that it's not included in the standard library.

You can then generate your cumulative sum by specializing the starting value and operation:

let cumSum = els.scan(0, +)

And you can omit the zero-length case rather simply:

let cumSumTail = els.scan(0, +).dropFirst()
Ian Henry
  • 22,255
  • 4
  • 50
  • 61
  • I like this. Is it possible (and advantageous?) to define this as a generic function on sequences? – Benjohn Feb 02 '16 at 20:28
  • 2
    Change `extension Array` to `extension SequenceType` and change both instances of `Element` to `Generator.Element` to make it work on any `SequenceType`. – rob mayoff Feb 02 '16 at 21:03
  • For fun, and maybe useful, I've added an [answer](https://stackoverflow.com/a/52440162/2547229) with an implementation of `scan` on `Sequence` which returns a `Sequence`. – Benjohn Sep 21 '18 at 09:00
11

Swift 4

The general sequence case

Citing the OP:

Even more general would be to have a function that takes any sequence and provides a sequence that's the running total of the input sequence.

Consider some arbitrary sequence (conforming to Sequence), say

var seq = 1... // 1, 2, 3, ... (CountablePartialRangeFrom)

To create another sequence which is the (lazy) running sum over seq, you can make use of the global sequence(state:next:) function:

var runningSumSequence =
    sequence(state: (sum: 0, it: seq.makeIterator())) { state -> Int? in
    if let val = state.it.next() {
        defer { state.sum += val }
        return val + state.sum
    }
    else { return nil }
}

// Consume and print accumulated values less than 100
while let accumulatedSum = runningSumSequence.next(),
    accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91

// Consume and print next
print(runningSumSequence.next() ?? -1) // 120

// ...

If we'd like (for the joy of it), we could condense the closure to sequence(state:next:) above somewhat:

var runningSumSequence =
    sequence(state: (sum: 0, it: seq.makeIterator())) {
        (state: inout (sum: Int, it: AnyIterator<Int>)) -> Int? in
        state.it.next().map { (state.sum + $0, state.sum += $0).0 }
}

However, type inference tends to break (still some open bugs, perhaps?) for these single-line returns of sequence(state:next:), forcing us to explicitly specify the type of state, hence the gritty ... in in the closure.

Alternatively: custom sequence accumulator

protocol Accumulatable {
    static func +(lhs: Self, rhs: Self) -> Self
}
extension Int : Accumulatable {}

struct AccumulateSequence<T: Sequence>: Sequence, IteratorProtocol
where T.Element: Accumulatable {
    var iterator: T.Iterator
    var accumulatedValue: T.Element?

    init(_ sequence: T) {
        self.iterator = sequence.makeIterator()
    }

    mutating func next() -> T.Element? {
        if let val = iterator.next() {
            if accumulatedValue == nil {
                accumulatedValue = val
            }
            else { defer { accumulatedValue = accumulatedValue! + val } }
            return accumulatedValue

        }
        return nil
    }
}

var accumulator = AccumulateSequence(1...)

// Consume and print accumulated values less than 100
while let accumulatedSum = accumulator.next(),
    accumulatedSum < 100 { print(accumulatedSum) }
// 1 3 6 10 15 21 28 36 45 55 66 78 91

The specific array case: using reduce(into:_:)

As of Swift 4, we can use reduce(into:_:) to accumulate the running sum into an array.

let runningSum = arr
    .reduce(into: []) { $0.append(($0.last ?? 0) + $1) }
    // [2, 4, 6, 8, 10, 12]

By using reduce(into:_:), the [Int] accumulator will not be copied in subsequent reduce iterations; citing the Language reference:

This method is preferred over reduce(_:_:) for efficiency when the result is a copy-on-write type, for example an Array or a Dictionary.

See also the implementation of reduce(into:_:), noting that the accumulator is provided as an inout parameter to the supplied closure.

However, each iteration will still result in an append(_:) call on the accumulator array; amortized O(1) averaged over many invocations, but still an arguably unnecessary overhead here as we know the final size of the accumulator.

Because arrays increase their allocated capacity using an exponential strategy, appending a single element to an array is an O(1) operation when averaged over many calls to the append(_:) method. When an array has additional capacity and is not sharing its storage with another instance, appending an element is O(1). When an array needs to reallocate storage before appending or its storage is shared with another copy, appending is O(n), where n is the length of the array.

Thus, knowing the final size of the accumulator, we could explicitly reserve such a capacity for it using reserveCapacity(_:) (as is done e.g. for the native implementation of map(_:))

let runningSum = arr
    .reduce(into: [Int]()) { (sums, element) in
        if let sum = sums.last {
            sums.append(sum + element)
        }
        else {
            sums.reserveCapacity(arr.count)
            sums.append(element)
        }
} // [2, 4, 6, 8, 10, 12]

For the joy of it, condensed:

let runningSum = arr
    .reduce(into: []) {
        $0.append(($0.last ?? ($0.reserveCapacity(arr.count), 0).1) + $1)
} // [2, 4, 6, 8, 10, 12]

Swift 3: Using enumerated() for subsequent calls to reduce

Another Swift 3 alternative (with an overhead ...) is using enumerated().map in combination with reduce within each element mapping:

func runningSum(_ arr: [Int]) -> [Int] {
    return arr.enumerated().map { arr.prefix($0).reduce($1, +) }
} /* thanks @Hamish for improvement! */

let arr = [2, 2, 2, 2, 2, 2]
print(runningSum(arr)) // [2, 4, 6, 8, 10, 12]

The upside is you wont have to use an array as the collector in a single reduce (instead repeatedly calling reduce).

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Thanks for the reply! I'll take a look at this on my commute… – Benjohn Apr 15 '16 at 16:06
  • 1
    @Benjohn I re-did my tests in an actual project rather than a playground, and for that case, the Swift compiler shows its strength and fixes any possible issue we could've seen with any of the methods when analyzing them in a playground. Hence, removed my poor benchmarking and left only the `enumeration()` solution (another alternative to the various ones in this Q&A). – dfrib Apr 15 '16 at 17:07
  • 1
    I understand the code now. Unless the compiler manages some magic, it's O(n^2), though, right? Did you look at the generated assembly and find it derived the O(n) solution – if so, that seems pretty impressive! – Benjohn Apr 29 '16 at 15:38
  • 1
    @Benjohn yes this one runs in `O(n^2)` (unless there's some compiler magic reducing it to less), which shouldn't be an issue, however, until the profiler tells you to. If performance turns out to be an issue, then JAL:s `O(n)` solution using an "external" variable for the cumulative sum should be the fastest one. – dfrib Apr 29 '16 at 18:36
  • @dfri Cool – Personally, and this could be old school madness, algorithmic complexity is an area I keep in mind before I take to the profiler. I suppose I think of it is part of a method's type. O(n^2) will leave a _billion_ core cluster needing 6 months of compute on a problem that a linear solution would complete on 1 core in 1 hour. That's obviously a totally extreme example :-) – Benjohn May 03 '16 at 07:41
  • 1
    @Benjohn Ah I missed this comment :) Yes I also come from a background of scientific computing, but have forcibly re-taught myself that for full-blown apps I'm mostly best of simply writing neat and readable code, worrying about optimisations at a later stage (complexity is always a good thing to keep in mind, however!); hence not worrying to much about smaller `O(n^2)` algorithms, but naturally avoiding anything that reeks of exponential time! – dfrib May 03 '16 at 10:01
  • 1
    @dfri Slight improvement, you could use the element as the starting value for the reduce: `arr.enumerated().map { arr.prefix($0).reduce($1, +) }` :) – Hamish Nov 15 '16 at 15:55
5

Just for fun: The running sum as a one-liner:

let arr = [1, 2, 3, 4]

let rs = arr.map({ () -> (Int) -> Int in var s = 0; return { (s += $0, s).1 } }())

print(rs) // [1, 3, 6, 10]

It does the same as the (updated) code in JAL's answer, in particular, no intermediate arrays are generated. The sum variable is captured in an immediately-evaluated closure returning the transformation.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
4

Assuming an array of Ints, sounds like you can use map to manipulate the input:

let arr = [0,1,0,1,0,1]

var sum = 0
let val = arr.map { (sum += $0, sum).1 }

print(val) // "[0, 1, 1, 2, 2, 3]\n"

I'll keep working on a solution that doesn't use an external variable.

JAL
  • 41,701
  • 23
  • 172
  • 300
  • :-) That without the external sum would be it. – Benjohn Feb 02 '16 at 18:23
  • You can't use `map` to do it without an external variable, but you could probably use `reduce` with an array as the collector – Kevin Feb 02 '16 at 18:25
  • @Kevin I wondered about that, but thought it might be horribly inefficient? – Benjohn Feb 02 '16 at 18:29
  • @Sulthan you can map with an index using `arr.enumerate().map({})`. – JAL Feb 02 '16 at 18:33
  • @JAL That second shorter one seems to be returning an array of closures, for me. Not sure what's going on there, or if I'm doing something wrong. – Think you have edited it out now though? Wonder why it didn't work? It looked reasonable :-) – Benjohn Feb 02 '16 at 18:43
  • @Benjohn I was trying to find a short way to do the map in one line, but `(sum+=$0)` evaluates as a closure in itself, so I don't think I can do this with `map`. I still think my solution is the most readable. – JAL Feb 02 '16 at 19:07
  • 2
    @JAL: You could shorten the map to `let var = arr.map { (sum += $0, sum).1 }` :) – Martin R Nov 15 '16 at 15:10
  • @MartinR that's a really neat (fully valid empty-tuple return) hack, one I for one haven't seen before! Could you think of any downsides to "inlining" `()`-return operations as `()`-type members of a tuple like this, weighted against the the obvious neatness upfactor? This approach in general might even be appropriate to add as an answer to [the following Q&A regarding the use the empty tuple type and instances of it](http://stackoverflow.com/questions/34561452). – dfrib Nov 15 '16 at 15:30
  • @MartinR I haven't been able to come up with any downside to it myself, even the silly analysis of the possible "additional" memory footprint of the empty tuple member is minimal. Simply good stuff, I guess! – dfrib Nov 16 '16 at 18:44
4

If you just want it to work for Int, you can use this:

func runningSum(array: [Int]) -> [Int] {
    return array.reduce([], combine: { (sums, element) in
        return sums + [element + (sums.last ?? 0)]
    })
}

If you want it to be generic over the element type, you have to do a lot of extra work declaring the various number types to conform to a custom protocol that provides a zero element, and (if you want it generic over both floating point and integer types) an addition operation, because Swift doesn't do that already. (A future version of Swift may fix this problem.)

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Nice, thanks – I definitely prefer this to the `map` based approach with an external variable. Can you able to comment on the efficiency of building the array up in this way? I'm going to also look at the `scan` approach. – Benjohn Feb 02 '16 at 20:05
  • I'm not sure of the efficiency. It is either O(N) or O(N^2). Don't worry about it until the profiler tells you it's a bottleneck. – rob mayoff Feb 02 '16 at 20:58
3

I thought I'd be cool to extend Sequence with a generic scan function as is suggested in the great first answer.

Given this extension, you can get the running sum of an array like this: [1,2,3].scan(0, +)

But you can also get other interesting things…

  • Running product: array.scan(1, *)
  • Running max: array.scan(Int.min, max)
  • Running min: array.scan(Int.max, min)

Because the implementation is a function on Sequence and returns a Sequence, you can chain it together with other sequence functions. It is efficient, having linear running time.

Here's the extension…

extension Sequence {

    func scan<Result>(_ initialResult: Result, _ nextPartialResult: @escaping (Result, Self.Element) -> Result) -> ScanSequence<Self, Result> {
        return ScanSequence(initialResult: initialResult, underlying: self, combine: nextPartialResult)
    }
}

struct ScanSequence<Underlying: Sequence, Result>: Sequence {

    let initialResult: Result
    let underlying: Underlying
    let combine: (Result, Underlying.Element) -> Result

    typealias Iterator = ScanIterator<Underlying.Iterator, Result>

    func makeIterator() -> Iterator {
        return ScanIterator(previousResult: initialResult, underlying: underlying.makeIterator(), combine: combine)
    }

    var underestimatedCount: Int {
        return underlying.underestimatedCount
    }
}

struct ScanIterator<Underlying: IteratorProtocol, Result>: IteratorProtocol {

    var previousResult: Result
    var underlying: Underlying
    let combine: (Result, Underlying.Element) -> Result

    mutating func next() -> Result? {
        guard let nextUnderlying = underlying.next() else {
            return nil
        }

        previousResult = combine(previousResult, nextUnderlying)
        return previousResult
    }
}
Benjohn
  • 13,228
  • 9
  • 65
  • 127
  • 2
    I feel that this would be a great feature that can be added into Swift. Could someone eligible propose a **scan** function to Swift Evolution? – jackxujh Nov 09 '18 at 18:52
0

One solution using reduce:

func runningSum(array: [Int]) -> [Int] {
    return array.reduce([], combine: { (result: [Int], item: Int) -> [Int] in
        if result.isEmpty {
            return [item] //first item, just take the value
        }

        // otherwise take the previous value and append the new item
        return result + [result.last! + item]
    })
}
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • The inside could be a bit cleaner as `if let last = result.last { return result + [last + item] } else { return [item] }` – Kevin Feb 02 '16 at 18:39
  • @Kevin I don't think that is much of a difference. The [solution](http://stackoverflow.com/a/35161164/669586) by rob-mayoff is probably the cleanest. – Sulthan Feb 02 '16 at 18:46
0

I'm very late to this party. The other answers have good explanations. But none of them have provided the initial result, in a generic way. This implementation is useful to me.

public extension Sequence {
  /// A sequence of the partial results that `reduce` would employ.
  func scan<Result>(
    _ initialResult: Result,
    _ nextPartialResult: @escaping (Result, Element) -> Result
  ) -> AnySequence<Result> {
    var iterator = makeIterator()
    return .init(
      sequence(first: initialResult) { partialResult in
        iterator.next().map {
          nextPartialResult(partialResult, $0)
        }
      }
    )
  }
}
extension Sequence where Element: AdditiveArithmetic & ExpressibleByIntegerLiteral {
  var runningSum: AnySequence<Element> { scan(0, +).dropFirst() }
}