Skip to content

[swift-4.0-branch] Implement SE-0171: Reduce with inout #11135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,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
Expand Down
112 changes: 112 additions & 0 deletions benchmark/single-source/ReduceInto.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//===--- 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

// Sum

@inline(never)
public func run_SumUsingReduce(_ N: Int) {
let numbers = [Int](0..<1000)

var c = 0
for _ in 1...N*1000 {
c = c &+ numbers.reduce(0) { (acc: Int, num: Int) -> Int in
acc &+ num
}
}
CheckResults(c != 0)
}

@inline(never)
public func run_SumUsingReduceInto(_ N: Int) {
let numbers = [Int](0..<1000)

var c = 0
for _ in 1...N*1000 {
c = c &+ numbers.reduce(into: 0) { (acc: inout Int, num: Int) in
acc = acc &+ num
}
}
CheckResults(c != 0)
}

// Filter

@inline(never)
public func run_FilterEvenUsingReduce(_ N: Int) {
let numbers = [Int](0..<100)

var c = 0
for _ in 1...N*100 {
let a = numbers.reduce([]) { (acc: [Int], num: Int) -> [Int] in
var a = acc
if num % 2 == 0 {
a.append(num)
}
return a
}
c = c &+ a.count
}
CheckResults(c != 0)
}

@inline(never)
public func run_FilterEvenUsingReduceInto(_ 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
if num % 2 == 0 {
acc.append(num)
}
}
c = c &+ a.count
}
CheckResults(c != 0)
}

// Frequencies

@inline(never)
public func run_FrequenciesUsingReduce(_ N: Int) {
let s = "thequickbrownfoxjumpsoverthelazydogusingasmanycharacteraspossible123456789"

var c = 0
for _ in 1...N*100 {
let a = s.reduce([:]) {
(acc: [Character: Int], c: Character) -> [Character: Int] in
var d = acc
d[c, default: 0] += 1
return d
}
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)
}
7 changes: 7 additions & 0 deletions benchmark/utils/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import RC4
import RGBHistogram
import RangeAssignment
import RecursiveOwnedParameter
import ReduceInto
import ReversedCollections
import SetTests
import SevenBoom
Expand Down Expand Up @@ -237,6 +238,10 @@ addTo(&precommitTests, "EqualSubstringString", run_EqualSubstringString)
addTo(&precommitTests, "EqualSubstringSubstring", run_EqualSubstringSubstring)
addTo(&precommitTests, "EqualSubstringSubstringGenericEquatable", run_EqualSubstringSubstringGenericEquatable)
addTo(&precommitTests, "ErrorHandling", run_ErrorHandling)
addTo(&precommitTests, "FilterEvenUsingReduce", run_FilterEvenUsingReduce)
addTo(&precommitTests, "FilterEvenUsingReduceInto", run_FilterEvenUsingReduceInto)
addTo(&precommitTests, "FrequenciesUsingReduce", run_FrequenciesUsingReduce)
addTo(&precommitTests, "FrequenciesUsingReduceInto", run_FrequenciesUsingReduceInto)
addTo(&precommitTests, "GlobalClass", run_GlobalClass)
addTo(&precommitTests, "Hanoi", run_Hanoi)
addTo(&precommitTests, "HashTest", run_HashTest)
Expand Down Expand Up @@ -408,6 +413,8 @@ addTo(&precommitTests, "SuffixCountableRange", run_SuffixCountableRange)
addTo(&precommitTests, "SuffixCountableRangeLazy", run_SuffixCountableRangeLazy)
addTo(&precommitTests, "SuffixSequence", run_SuffixSequence)
addTo(&precommitTests, "SuffixSequenceLazy", run_SuffixSequenceLazy)
addTo(&precommitTests, "SumUsingReduce", run_SumUsingReduce)
addTo(&precommitTests, "SumUsingReduceInto", run_SumUsingReduceInto)
addTo(&precommitTests, "SuperChars", run_SuperChars)
addTo(&precommitTests, "TwoSum", run_TwoSum)
addTo(&precommitTests, "TypeFlood", run_TypeFlood)
Expand Down
55 changes: 55 additions & 0 deletions stdlib/public/core/SequenceAlgorithms.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

//===----------------------------------------------------------------------===//
Expand Down
2 changes: 1 addition & 1 deletion test/Constraints/closures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 '((_, _) -> _)'}}



Expand Down
90 changes: 90 additions & 0 deletions test/stdlib/Reduce.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//===--- Reduce.swift - tests for the two reduce variants -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// RUN: %target-run-simple-swift | %FileCheck %s
// REQUIRES: executable_test

// CHECK: testing...
print("testing...")

// Test the examples from the documentation of reduce(_:_:) and reduce(into:_:)

let numbers = [1, 2, 3, 4]
let numberSum = numbers.reduce(0, { x, y in
x + y
})
// CHECK-NEXT: 10
print(numberSum)

let letters = "abracadabra"
let letterCount = letters.reduce(into: [:]) { counts, letter in
counts[letter, default: 0] += 1
}
// CHECK-NEXT: ["a", "b", "c", "d", "r"]
print(letterCount.keys.sorted())
print(letterCount["a"]!) // CHECK: 5
print(letterCount["b"]!) // CHECK: 2
print(letterCount["c"]!) // CHECK: 1
print(letterCount["d"]!) // CHECK: 1
print(letterCount["r"]!) // CHECK: 2


// Test the two reduce methods with different levels of inference
let numbers2 = Array(2..<7)

// Test reduce(_:_:)
// CHECK-NEXT: 20
let sum1 = numbers2.reduce(0) { (x: Int, y: Int) -> Int in x + y }
print(sum1)

// CHECK-NEXT: 20
let sum2 = numbers2.reduce(0) { (x, y) in x + y }
print(sum2)

// CHECK-NEXT: 20
let sum3 = numbers2.reduce(0) { $0 + $1 }
print(sum3)

// CHECK-NEXT: 20
let sum4 = numbers2.reduce(0, +)
print(sum4)

// Test reduce(into:_:)
// CHECK-NEXT: 20
let sum5 = numbers2.reduce(into: 0) { (x: inout Int, y: Int) in x += y }
print(sum5)

// CHECK-NEXT: 20
let sum6 = numbers2.reduce(into: 0) { x, y in x += y }
print(sum6)

// CHECK-NEXT: 20
let sum7 = numbers2.reduce(into: 0) { $0 += $1 }
print(sum7)

// CHECK-NEXT: 20
let sum8 = numbers2.reduce(into: 0, +=)
print(sum8)


// Test that the initial value remains unmodified
var original = [0, 1]
let result = numbers2.reduce(into: original) { acc, x in
acc.append(x)
}
// CHECK-NEXT: [0, 1]
print(original)
// CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6]
print(result)


// CHECK: all done.
print("all done.")