Guides/Reductions.md
Produces all the intermediate results of reducing a sequence's elements using a closure.
let exclusiveRunningTotal = (1...5).reductions(0, +)
print(exclusiveRunningTotal)
// prints [0, 1, 3, 6, 10, 15]
let intoRunningTotal = (1...5).reductions(into: 0, +=)
print(intoRunningTotal)
// prints [0, 1, 3, 6, 10, 15]
let inclusiveRunningTotal = (1...5).reductions(+)
print(inclusiveRunningTotal)
// prints [1, 3, 6, 10, 15]
One trio of methods are added to LazySequenceProtocol for a lazily evaluated
sequence and another trio are added to Sequence which are eagerly evaluated.
extension LazySequenceProtocol {
public func reductions<Result>(
_ initial: Result,
_ transform: @escaping (Result, Element) -> Result
) -> ExclusiveReductionsSequence<Result, Self>
public func reductions<Result>(
into initial: Result,
_ transform: @escaping (inout Result, Element) -> Void
) -> ExclusiveReductionsSequence<Result, Self>
public func reductions(
_ transform: @escaping (Element, Element) -> Element
) -> InclusiveReductionsSequence<Self>
}
extension Sequence {
public func reductions<Result>(
_ initial: Result,
_ transform: (Result, Element) throws -> Result
) rethrows -> [Result]
public func reductions<Result>(
into initial: Result,
_ transform: (inout Result, Element) throws -> Void
) rethrows -> [Result]
public func reductions(
_ transform: (Element, Element) throws -> Element
) rethrows -> [Element]
}
Calling the lazy methods, those defined on LazySequenceProtocol, is O(1).
Calling the eager methods, those returning an array, is O(n).
While the name scan is the term of art for this function, it has been
discussed that reductions aligns better with the existing reduce function
and will aid newcomers that might not know the existing scan term.
Deprecated scan methods have been added for people who are familiar with the
term, so they can easily discover the reductions methods via compiler
deprecation warnings.
Below are two quotes from the Swift forum discussion about SE-0045
which proposed adding scan to the standard library and one from
issue #25 on the swift-algorithms GitHub project. These provide
the reasoning to use the name reductions.
I really like the
reduce/reductionspairing instead ofreduce/scan; it does a really good job of explaining the relationship between the two functions.
As other have already pointed out, I also feel that
scanis the least intuitive name among these and that thereduce/reductionspairing would do a good job at explaining the relation between the two.
As someone unfamiliar with the prior art,
reductionsstrikes me as very approachable—I feel like I can extrapolate the expected behavior purely from my familiarity withreduce.
As part of early discussions, it was decided to have two variants, one which
takes an initial value to use for the first element in the returned sequence,
and another which uses the first value of the base sequence as the initial
value. C++ calls these variants exclusive and inclusive respectively and so
these terms carry through as the name for the lazy sequences;
ExclusiveReductionsSequence and InclusiveReductionsSequence.
C++: As of C++17, the <algorithm> library includes both
exclusive_scan and inclusive_scan
functions.
Clojure: Clojure 1.2 added a reductions function.
Haskell: Haskell includes a scan function for its
Traversable type, which is akin to Swift's Sequence.
Python: Python’s itertools includes an accumulate method. In version
3.3, a function parameter was added. Version 3.8 added the optional initial
parameter.
Rust: Rust provides a scan function.