[stdlib] Implement reduce with inout (SE-0171)

Implement and document `reduce(into:_:)`, with a few notes:

 - The `initial` parameter was renamed `initialResult` to match the first parameter in `reduce(_:_:)`.
 - The unnamed `combining` parameter was renamed `updateAccumulatingResult` to try and resemble the naming of the closure parameter in `reduce(_:_:)`.
 - The closure throws and `reduce(into:_)` re-throws.
 - This documentation mentions that `reduce(into:_)` is preferred over `reduce(_:_:)` when the result is a copy-on-write type and an example where the result is a dictionary.

Add benchmarks for reduce with accumulation into a scalar, an array, and a dictionary.

Update expected error message in closures test (since there are now two `reduce` methods, the diagnostic is different).
This commit is contained in:
David Rönnqvist
2017-07-14 23:24:42 +02:00
parent b37b3ba367
commit e15ea5fcf3
5 changed files with 147 additions and 1 deletions

View File

@@ -91,6 +91,7 @@ set(SWIFT_BENCH_MODULES
single-source/RGBHistogram
single-source/RangeAssignment
single-source/RecursiveOwnedParameter
single-source/ReduceInto
single-source/ReversedCollections
single-source/SetTests
single-source/SevenBoom

View File

@@ -0,0 +1,84 @@
//===--- ReduceInto.swift -------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import TestsUtils
import Foundation
@inline(never)
public func run_ReduceIntoInt(_ N: Int) {
let numbers = [Int](0..<1000)
var c = 0
for _ in 1...N*100 {
c = c &+ numbers.reduce(into: 0) { (acc: inout Int, num: Int) in
acc = acc &+ num
}
}
CheckResults(c != 0)
}
@inline(never)
public func run_ReduceIntoArray(_ N: Int) {
let numbers = [Int](0..<100)
var c = 0
for _ in 1...N*100 {
let a = numbers.reduce(into: []) { (acc: inout [Int], num: Int) in
acc.append(num)
}
c = c &+ a.count
}
CheckResults(c != 0)
}
@inline(never)
public func run_ReduceIntoDictionary(_ N: Int) {
let numbers = [Int](0..<100)
var c = 0
for _ in 1...N*100 {
let d = numbers.reduce(into: [:]) { (acc: inout [Int: Int], num: Int) in
acc[num] = num
}
c = c &+ d.count
}
CheckResults(c != 0)
}
@inline(never)
public func run_MapUsingReduceInto(_ N: Int) {
let numbers = [Int](0..<100)
var c = 0
let f: (Int) -> Int = { $0 &+ 5 }
for _ in 1...N*100 {
let a = numbers.reduce(into: []) { (acc: inout [Int], x: Int) in
acc.append(f(x))
}
c = c &+ a.count
}
CheckResults(c != 0)
}
@inline(never)
public func run_FrequenciesUsingReduceInto(_ N: Int) {
let s = "thequickbrownfoxjumpsoverthelazydogusingasmanycharacteraspossible123456789"
var c = 0
for _ in 1...N*100 {
let a = s.reduce(into: [:]) { (acc: inout [Character: Int], c: Character) in
acc[c, default: 0] += 1
}
c = c &+ a.count
}
CheckResults(c != 0)
}

View File

@@ -96,6 +96,7 @@ import RC4
import RGBHistogram
import RangeAssignment
import RecursiveOwnedParameter
import ReduceInto
import ReversedCollections
import SetTests
import SevenBoom
@@ -274,6 +275,7 @@ addTo(&precommitTests, "EqualSubstringString", run_EqualSubstringString)
addTo(&precommitTests, "EqualSubstringSubstring", run_EqualSubstringSubstring)
addTo(&precommitTests, "EqualSubstringSubstringGenericEquatable", run_EqualSubstringSubstringGenericEquatable)
addTo(&precommitTests, "ErrorHandling", run_ErrorHandling)
addTo(&precommitTests, "FrequenciesUsingReduceInto", run_FrequenciesUsingReduceInto)
addTo(&precommitTests, "Hanoi", run_Hanoi)
addTo(&precommitTests, "HashTest", run_HashTest)
addTo(&precommitTests, "Histogram", run_Histogram)
@@ -297,6 +299,7 @@ addTo(&precommitTests, "MapReduceSequence", run_MapReduceSequence)
addTo(&precommitTests, "MapReduceShort", run_MapReduceShort)
addTo(&precommitTests, "MapReduceShortString", run_MapReduceShortString)
addTo(&precommitTests, "MapReduceString", run_MapReduceString)
addTo(&precommitTests, "MapUsingReduceInto", run_MapUsingReduceInto)
addTo(&precommitTests, "Memset", run_Memset)
addTo(&precommitTests, "MonteCarloE", run_MonteCarloE)
addTo(&precommitTests, "MonteCarloPi", run_MonteCarloPi)
@@ -387,6 +390,9 @@ addTo(&precommitTests, "RGBHistogram", run_RGBHistogram)
addTo(&precommitTests, "RGBHistogramOfObjects", run_RGBHistogramOfObjects)
addTo(&precommitTests, "RangeAssignment", run_RangeAssignment)
addTo(&precommitTests, "RecursiveOwnedParameter", run_RecursiveOwnedParameter)
addTo(&precommitTests, "ReduceIntoArray", run_ReduceIntoArray)
addTo(&precommitTests, "ReduceIntoDictionary", run_ReduceIntoDictionary)
addTo(&precommitTests, "ReduceIntoInt", run_ReduceIntoInt)
addTo(&precommitTests, "ReversedArray", run_ReversedArray)
addTo(&precommitTests, "ReversedBidirectional", run_ReversedBidirectional)
addTo(&precommitTests, "ReversedDictionary", run_ReversedDictionary)

View File

@@ -596,6 +596,61 @@ extension Sequence {
}
return accumulator
}
/// Returns the result of combining the elements of the sequence using the
/// given closure.
///
/// Use the `reduce(into:_:)` method to produce a single value from the
/// elements of an entire sequence. For example, you can use this method on an
/// array of integers to filter adjacent equal entries or count frequencies.
///
/// This method is preferred over `reduce(_:_:)` for efficiency when the
/// result is a copy-on-write type, for example an Array or a Dictionary.
///
/// The `updateAccumulatingResult` closure is called sequentially with a
/// mutable accumulating value initialized to `initialResult` and each element
/// of the sequence. This example shows how to build a dictionary of letter
/// frequencies of a string.
///
/// let letters = "abracadabra"
/// let letterCount = letters.reduce(into: [:]) { counts, letter in
/// counts[letter, default: 0] += 1
/// }
/// // letterCount == ["a": 5, "b": 2, "r": 2, "c": 1, "d": 1]
///
/// When `letters.reduce(into:_:)` is called, the following steps occur:
///
/// 1. The `updateAccumulatingResult` closure is called with the initial
/// accumulating value---`[:]` in this case---and the first character of
/// `letters`, modifying the accumulating value by setting `1` for the key
/// `"a"`.
/// 2. The closure is called again repeatedly with the updated accumulating
/// value and each element of the sequence.
/// 3. When the sequence is exhausted, the accumulating value is returned to
/// the caller.
///
/// If the sequence has no elements, `updateAccumulatingResult` is never
/// executed and `initialResult` is the result of the call to
/// `reduce(into:_:)`.
///
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value.
/// - updateAccumulatingResult: A closure that updates the accumulating
/// value with an element of the sequence.
/// - Returns: The final accumulated value. If the sequence has no elements,
/// the result is `initialResult`.
@_inlineable
public func reduce<Result>(
into initialResult: Result,
_ updateAccumulatingResult:
(_ partialResult: inout Result, Element) throws -> ()
) rethrows -> Result {
var accumulator = initialResult
for element in self {
try updateAccumulatingResult(&accumulator, element)
}
return accumulator
}
}
//===----------------------------------------------------------------------===//

View File

@@ -164,7 +164,7 @@ func testMap() {
}
// <rdar://problem/22414757> "UnresolvedDot" "in wrong phase" assertion from verifier
[].reduce { $0 + $1 } // expected-error {{missing argument for parameter #1 in call}}
[].reduce { $0 + $1 } // expected-error {{cannot invoke 'reduce' with an argument list of type '((_, _) -> _)'}}