|
| 1 | +# Reductions |
| 2 | + |
| 3 | +[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Reductions.swift) | |
| 4 | + [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/ReductionsTests.swift)] |
| 5 | + |
| 6 | +Produces a sequence of values. |
| 7 | + |
| 8 | +This has the behaviour of reduce, but also returns all intermediate results. |
| 9 | + |
| 10 | +```swift |
| 11 | +let exclusiveRunningTotal = (1...5).reductions(0, +) |
| 12 | +print(exclusiveRunningTotal) |
| 13 | +// prints [0, 1, 3, 6, 10, 15] |
| 14 | + |
| 15 | +var value = 0 |
| 16 | +let intoRunningTotal = (1...5).reductions(into: &value, +=) |
| 17 | +print(intoRunningTotal) |
| 18 | +// prints [0, 1, 3, 6, 10, 15] |
| 19 | +print(value) |
| 20 | +// prints 15 |
| 21 | + |
| 22 | +let inclusiveRunningTotal = (1...5).reductions(+) |
| 23 | +print(inclusiveRunningTotal) |
| 24 | +// prints [1, 3, 6, 10, 15] |
| 25 | +``` |
| 26 | + |
| 27 | +## Detailed Design |
| 28 | + |
| 29 | +One trio of methods are added to `LazySequenceProtocol` for a lazily evaluated |
| 30 | +sequence and another trio are added to `Sequence` which are eagerly evaluated. |
| 31 | + |
| 32 | +```swift |
| 33 | +extension LazySequenceProtocol { |
| 34 | + |
| 35 | + public func reductions<Result>( |
| 36 | + _ initial: Result, |
| 37 | + _ transform: @escaping (Result, Element) -> Result |
| 38 | + ) -> ExclusiveReductions<Result, Self> |
| 39 | + |
| 40 | + public func reductions<Result>( |
| 41 | + into initial: inout Result, |
| 42 | + _ transform: @escaping (inout Result, Element) -> Void |
| 43 | + ) -> ExclusiveReductions<Result, Self> |
| 44 | + |
| 45 | + public func reductions( |
| 46 | + _ transform: @escaping (Element, Element) -> Element |
| 47 | + ) -> InclusiveReductions<Self> |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +```swift |
| 52 | +extension Sequence { |
| 53 | + |
| 54 | + public func reductions<Result>( |
| 55 | + _ initial: Result, |
| 56 | + _ transform: (Result, Element) throws -> Result |
| 57 | + ) rethrows -> [Result] |
| 58 | + |
| 59 | + public func reductions<Result>( |
| 60 | + into initial: inout Result, |
| 61 | + _ transform: (inout Result, Element) throws -> Void |
| 62 | + ) rethrows -> [Result] |
| 63 | + |
| 64 | + public func reductions( |
| 65 | + _ transform: (Element, Element) throws -> Element |
| 66 | + ) rethrows -> [Element] |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +### Complexity |
| 71 | + |
| 72 | +Calling the lazy methods, those defined on `LazySequenceProtocol`, is O(_1_). |
| 73 | +Calling the eager methods, those returning an array, is O(_n_). |
| 74 | + |
| 75 | +### Naming |
| 76 | + |
| 77 | +While the name `scan` is the term of art for this function, it has been |
| 78 | +discussed that `reductions` aligns better with the existing `reduce` function |
| 79 | +and will aid newcomers that might not know the existing `scan` term. |
| 80 | + |
| 81 | +Deprecated `scan` methods have been added for people who are familiar with the |
| 82 | +term, so they can easily discover the `reductions` methods via compiler |
| 83 | +deprecation warnings. |
| 84 | + |
| 85 | +Below are two quotes from the Swift forum [discussion about SE-0045][SE-0045] |
| 86 | +which proposed adding `scan` to the standard library and one from |
| 87 | +[issue #25][Issue 25] on the swift-algorithms GitHub project. These provide |
| 88 | +the reasoning to use the name `reductions`. |
| 89 | + |
| 90 | +[Brent Royal-Gordon][Brent_Royal-Gordon]: |
| 91 | +> I really like the `reduce`/`reductions` pairing instead of `reduce`/`scan`; |
| 92 | +it does a really good job of explaining the relationship between the two |
| 93 | +functions. |
| 94 | + |
| 95 | +[David Rönnqvist][David Rönnqvist]: |
| 96 | +> As other have already pointed out, I also feel that `scan` is the least |
| 97 | +intuitive name among these and that the `reduce`/`reductions` pairing would do |
| 98 | +a good job at explaining the relation between the two. |
| 99 | + |
| 100 | +[Kyle Macomber][Kyle Macomber]: |
| 101 | +> As someone unfamiliar with the prior art, `reductions` strikes me as very |
| 102 | +approachable—I feel like I can extrapolate the expected behavior purely from my |
| 103 | +familiarity with `reduce`. |
| 104 | + |
| 105 | +As part of early discussions, it was decided to have two variants, one which |
| 106 | +takes an initial value to use for the first element in the returned sequence, |
| 107 | +and another which uses the first value of the base sequence as the initial |
| 108 | +value. C++ calls these variants exclusive and inclusive respectively and so |
| 109 | +these terms carry through as the name for the lazy sequences; |
| 110 | +`ExclusiveReductions` and `InclusiveReductions`. |
| 111 | + |
| 112 | +[SE-0045]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382 |
| 113 | +[Issue 25]: https://github.com/apple/swift-algorithms/issues/25 |
| 114 | +[Brent_Royal-Gordon]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382/6 |
| 115 | +[David Rönnqvist]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382/8 |
| 116 | +[Kyle Macomber]: https://github.com/apple/swift-algorithms/issues/25#issuecomment-709317894 |
| 117 | + |
| 118 | +### Comparison with other langauges |
| 119 | + |
| 120 | +**C++:** As of C++17, the `<algorithm>` library includes both |
| 121 | +[`exclusive_scan`][C++ Exclusive] and [`inclusive_scan`][C++ Inclusive] |
| 122 | +functions. |
| 123 | + |
| 124 | +**[Clojure][Clojure]:** Clojure 1.2 added a `reductions` function. |
| 125 | + |
| 126 | +**[Haskell][Haskell]:** Haskell includes a `scan` function for its |
| 127 | +`Traversable` type, which is akin to Swift's `Sequence`. |
| 128 | + |
| 129 | +**Python:** Python’s `itertools` includes an `accumulate` method. In version |
| 130 | +3.3, a function paramenter was added. Version 3.8 added the optional initial |
| 131 | +parameter. |
| 132 | + |
| 133 | +**[Rust][Rust]:** Rust provides a `scan` function. |
| 134 | + |
| 135 | +[C++ Exclusive]: https://en.cppreference.com/w/cpp/algorithm/exclusive_scan |
| 136 | +[C++ Inclusive]: https://en.cppreference.com/w/cpp/algorithm/inclusive_scan |
| 137 | +[Clojure]: http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/reductions |
| 138 | +[Haskell]: http://hackage.haskell.org/package/base-4.8.2.0/docs/Prelude.html#v:scanl |
| 139 | +[Rust]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.scan |
0 commit comments