diff --git a/Sources/AsyncAlgorithms/Dictionary.swift b/Sources/AsyncAlgorithms/Dictionary.swift new file mode 100644 index 00000000..f2086d92 --- /dev/null +++ b/Sources/AsyncAlgorithms/Dictionary.swift @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension Dictionary { + /// Creates a new dictionary from the key-value pairs in the given asynchronous sequence. + /// + /// You use this initializer to create a dictionary when you have an asynchronous sequence + /// of key-value tuples with unique keys. Passing an asynchronous sequence with duplicate + /// keys to this initializer results in a runtime error. If your + /// asynchronous sequence might have duplicate keys, use the + /// `Dictionary(_:uniquingKeysWith:)` initializer instead. + /// + /// - Parameter keysAndValues: An asynchronous sequence of key-value pairs to use for + /// the new dictionary. Every key in `keysAndValues` must be unique. + /// - Returns: A new dictionary initialized with the elements of + /// `keysAndValues`. + /// - Precondition: The sequence must not have duplicate keys. + @inlinable + public init(uniqueKeysWithValues keysAndValues: S) async rethrows where S.Element == (Key, Value) { + self.init(uniqueKeysWithValues: try await Array(keysAndValues)) + } + + /// Creates a new dictionary from the key-value pairs in the given asynchronous sequence, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// You use this initializer to create a dictionary when you have a sequence + /// of key-value tuples that might have duplicate keys. As the dictionary is + /// built, the initializer calls the `combine` closure with the current and + /// new values for any duplicate keys. Pass a closure as `combine` that + /// returns the value to use in the resulting dictionary: The closure can + /// choose between the two values, combine them to produce a new value, or + /// even throw an error. + /// + /// - Parameters: + /// - keysAndValues: An asynchronous sequence of key-value pairs to use for the new + /// dictionary. + /// - combine: A closure that is called with the values for any duplicate + /// keys that are encountered. The closure returns the desired value for + /// the final dictionary. + @inlinable + public init(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) async throws -> Value) async rethrows where S.Element == (Key, Value) { + self.init() + for try await (key, value) in keysAndValues { + if let existing = self[key] { + self[key] = try await combine(existing, value) + } else { + self[key] = value + } + } + } + + /// Creates a new dictionary whose keys are the groupings returned by the + /// given closure and whose values are arrays of the elements that returned + /// each key. + /// + /// The arrays in the "values" position of the new dictionary each contain at + /// least one element, with the elements in the same order as the source + /// asynchronous sequence. + /// + /// - Parameters: + /// - values: An asynchronous sequence of values to group into a dictionary. + /// - keyForValue: A closure that returns a key for each element in + /// `values`. + @inlinable + public init(grouping values: S, by keyForValue: (S.Element) async throws -> Key) async rethrows where Value == [S.Element] { + self.init() + for try await value in values { + let key = try await keyForValue(value) + self[key, default: []].append(value) + } + } +} diff --git a/Sources/AsyncAlgorithms/RangeReplaceableCollection.swift b/Sources/AsyncAlgorithms/RangeReplaceableCollection.swift new file mode 100644 index 00000000..58f879e1 --- /dev/null +++ b/Sources/AsyncAlgorithms/RangeReplaceableCollection.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension RangeReplaceableCollection { + /// Creates a new instance of a collection containing the elements of an asynchronous sequence. + /// + /// - Parameter surce: The asynchronous sequence of elements for the new collection. + @inlinable + public init(_ source: Source) async rethrows where Source.Element == Element { + self.init() + for try await item in source { + append(item) + } + } +} diff --git a/Sources/AsyncAlgorithms/SetAlgebra.swift b/Sources/AsyncAlgorithms/SetAlgebra.swift new file mode 100644 index 00000000..884fc02b --- /dev/null +++ b/Sources/AsyncAlgorithms/SetAlgebra.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +extension SetAlgebra { + /// Creates a new set from an asynchronous sequence of items. + /// + /// Use this initializer to create a new set from an asynchronous sequence + /// + /// - Parameter source: The elements to use as members of the new set. + @inlinable + public init(_ source: Source) async rethrows where Source.Element == Element { + self.init() + for try await item in source { + insert(item) + } + } +} diff --git a/Tests/AsyncAlgorithmsTests/TestDictionary.swift b/Tests/AsyncAlgorithmsTests/TestDictionary.swift new file mode 100644 index 00000000..bebfc974 --- /dev/null +++ b/Tests/AsyncAlgorithmsTests/TestDictionary.swift @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import AsyncAlgorithms + +final class TestDictionary: XCTestCase { + func test_uniqueKeysAndValues() async { + let source = [(1, "a"), (2, "b"), (3, "c")] + let expected = Dictionary(uniqueKeysWithValues: source) + let actual = await Dictionary(uniqueKeysWithValues: source.async) + XCTAssertEqual(expected, actual) + } + + func test_throwing_uniqueKeysAndValues() async { + let source = Array([1, 2, 3, 4, 5, 6]) + let input = source.async.map { (value: Int) async throws -> (Int, Int) in + if value == 4 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return (value, value) + } + do { + _ = try await Dictionary(uniqueKeysWithValues: input) + XCTFail() + } catch { + XCTAssertEqual((error as NSError).code, -1) + } + } + + func test_uniquingWith() async { + let source = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] + let expected = Dictionary(source) { first, _ in first } + let actual = await Dictionary(source.async) { first, _ in first } + XCTAssertEqual(expected, actual) + } + + func test_throwing_uniquingWith() async { + let source = Array([1, 2, 3, 4, 5, 6]) + let input = source.async.map { (value: Int) async throws -> (Int, Int) in + if value == 4 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return (value, value) + } + do { + _ = try await Dictionary(input) { first, _ in first } + XCTFail() + } catch { + XCTAssertEqual((error as NSError).code, -1) + } + } + + func test_grouping() async { + let source = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"] + let expected = Dictionary(grouping: source, by: { $0.first! }) + let actual = await Dictionary(grouping: source.async, by: { $0.first! }) + XCTAssertEqual(expected, actual) + } + + func test_throwing_grouping() async { + let source = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"] + let input = source.async.map { (value: String) async throws -> String in + if value == "Kweku" { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return value + } + do { + _ = try await Dictionary(grouping: input, by: { $0.first! }) + XCTFail() + } catch { + XCTAssertEqual((error as NSError).code, -1) + } + } +} diff --git a/Tests/AsyncAlgorithmsTests/TestRangeReplacableCollection.swift b/Tests/AsyncAlgorithmsTests/TestRangeReplacableCollection.swift new file mode 100644 index 00000000..ae5616e4 --- /dev/null +++ b/Tests/AsyncAlgorithmsTests/TestRangeReplacableCollection.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import AsyncAlgorithms + +final class TestRangeReplacableCollection: XCTestCase { + func test_String() async { + let source = "abc" + let expected = source + let actual = await String(source.async) + XCTAssertEqual(expected, actual) + } + + func test_Data() async { + let source = Data([1, 2, 3]) + let expected = source + let actual = await Data(source.async) + XCTAssertEqual(expected, actual) + } + + func test_ContiguousArray() async { + let source = ContiguousArray([1, 2, 3]) + let expected = source + let actual = await ContiguousArray(source.async) + XCTAssertEqual(expected, actual) + } + + func test_Array() async { + let source = Array([1, 2, 3]) + let expected = source + let actual = await Array(source.async) + XCTAssertEqual(expected, actual) + } + + func test_throwing() async { + let source = Array([1, 2, 3, 4, 5, 6]) + let input = source.async.map { (value: Int) async throws -> Int in + if value == 4 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return value + } + do { + _ = try await Array(input) + XCTFail() + } catch { + XCTAssertEqual((error as NSError).code, -1) + } + } +} diff --git a/Tests/AsyncAlgorithmsTests/TestSetAlgebra.swift b/Tests/AsyncAlgorithmsTests/TestSetAlgebra.swift new file mode 100644 index 00000000..94acfff3 --- /dev/null +++ b/Tests/AsyncAlgorithmsTests/TestSetAlgebra.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2021 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import AsyncAlgorithms + +final class TestSetAlgebra: XCTestCase { + func test_Set() async { + let source = [1, 2, 3] + let expected = Set(source) + let actual = await Set(source.async) + XCTAssertEqual(expected, actual) + } + + func test_Set_duplicate() async { + let source = [1, 2, 3, 3] + let expected = Set(source) + let actual = await Set(source.async) + XCTAssertEqual(expected, actual) + } + + func test_IndexSet() async { + let source = [1, 2, 3] + let expected = IndexSet(source) + let actual = await IndexSet(source.async) + XCTAssertEqual(expected, actual) + } + + func test_throwing() async { + let source = Array([1, 2, 3, 4, 5, 6]) + let input = source.async.map { (value: Int) async throws -> Int in + if value == 4 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return value + } + do { + _ = try await Set(input) + XCTFail() + } catch { + XCTAssertEqual((error as NSError).code, -1) + } + } +}