Swift 4
General case of sequence
Referring to the OP:
Even more common would be to have a function that accepts any sequence and provides a sequence in which the total amount of input to the sequence is executed.
Consider some arbitrary sequence (corresponding to Sequence ), say
var seq = 1...
To create another sequence that represents the (lazy) amount of execution on seq , you can use 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 wanted (for the sake of this joy), we could smooth out the short circuit to sequence(state:next:) 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 (at least some open errors, maybe?) For these single-line returns of sequence(state:next:) , forcing us to explicitly specify the state type, therefore, gritty ... in to close.
Alternative: custom sequence drive
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...)
Array specific case: using reduce(into:_:)
With Swift 4, we can use reduce(into:_:) to accumulate the current amount into an array.
let runningSum = arr .reduce(into: []) { $0.append(($0.last ?? 0) + $1) } // [2, 4, 6, 8, 10, 12]
Using reduce(into:_:) , the battery [Int] will not be copied in subsequent iteration reductions; Referring to Language Link :
This method is preferred over reduce(_:_:) for efficiency when the result is a copy-on-write type, such as Array or Dictionary .
See also the reduce(into:_:) implementation , noting that the battery is provided as an inout in the supplied closure.
However, each iteration will still lead to a call to append(_:) drive array; amortized O(1) , averaged over many calls, but still, possibly, extra overhead, since we know the final size of the battery.
As arrays increase their allocated capacity using an exponential strategy, adding one element to an array is an O(1) operation when averaging over many calls to the append(_:) method. When an array has extra capacity and does not share its storage with another instance, adding an O(1) element. When an array must redistribute the storage before adding or store it together with another copy, the addition is O(n) , where n is the length of the array.
Thus, knowing the final size of the battery, we could explicitly reserve such a capacity for it using reserveCapacity(_:) (as is done, for example, for the built-in map(_:) implementation )
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 joy concise:
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 reduce calls
Another Swift 3 alternative (with overhead ...) uses enumerated().map in combination with reduce in 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 surface you donβt have to use the array as a collector in a single reduce (instead, it calls reduce again).