From 27f397a882059f746aa228855320bf2851dc8bdc Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 00:49:52 +0800 Subject: [PATCH 01/25] add recursiveMap methods --- .../AsyncRecursiveMapSequence.swift | 97 +++++++++++++++++++ .../AsyncThrowingRecursiveMapSequence.swift | 97 +++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift create mode 100644 Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift new file mode 100644 index 00000000..2f8837a9 --- /dev/null +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2022 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 AsyncSequence { + + @inlinable + public func recursiveMap(_ transform: @Sendable @escaping (Element) async -> C) -> AsyncRecursiveMapSequence { + return AsyncRecursiveMapSequence(self, transform) + } +} + +public struct AsyncRecursiveMapSequence: AsyncSequence where Base.Element == Transformed.Element { + + public typealias Element = Base.Element + + @usableFromInline + let base: Base + + @usableFromInline + let transform: @Sendable (Base.Element) async -> Transformed + + @inlinable + init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async -> Transformed) { + self.base = base + self.transform = transform + } + + @inlinable + public func makeAsyncIterator() -> AsyncIterator { + return AsyncIterator(base, transform) + } +} + +extension AsyncRecursiveMapSequence { + + public struct AsyncIterator: AsyncIteratorProtocol { + + @usableFromInline + var base: Base.AsyncIterator? + + @usableFromInline + var mapped: ArraySlice = [] + + @usableFromInline + var mapped_iterator: Transformed.AsyncIterator? + + @usableFromInline + var transform: @Sendable (Base.Element) async -> Transformed + + @inlinable + init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async -> Transformed) { + self.base = base.makeAsyncIterator() + self.transform = transform + } + + @inlinable + public mutating func next() async rethrows -> Base.Element? { + + if self.base != nil { + + if let element = try await self.base?.next() { + await mapped.append(transform(element)) + return element + } + + self.base = nil + self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() + } + + while self.mapped_iterator != nil { + + if let element = try await self.mapped_iterator?.next() { + await mapped.append(transform(element)) + return element + } + + self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() + } + + return nil + } + } +} + +extension AsyncRecursiveMapSequence: Sendable +where Base: Sendable, Base.Element: Sendable, Transformed: Sendable { } + +extension AsyncRecursiveMapSequence.AsyncIterator: Sendable +where Base.AsyncIterator: Sendable, Base.Element: Sendable, Transformed: Sendable, Transformed.AsyncIterator: Sendable { } diff --git a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift new file mode 100644 index 00000000..6d920126 --- /dev/null +++ b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2022 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 AsyncSequence { + + @inlinable + public func recursiveMap(_ transform: @Sendable @escaping (Element) async throws -> C) -> AsyncThrowingRecursiveMapSequence { + return AsyncThrowingRecursiveMapSequence(self, transform) + } +} + +public struct AsyncThrowingRecursiveMapSequence: AsyncSequence where Base.Element == Transformed.Element { + + public typealias Element = Base.Element + + @usableFromInline + let base: Base + + @usableFromInline + let transform: @Sendable (Base.Element) async throws -> Transformed + + @inlinable + init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async throws -> Transformed) { + self.base = base + self.transform = transform + } + + @inlinable + public func makeAsyncIterator() -> AsyncIterator { + return AsyncIterator(base, transform) + } +} + +extension AsyncThrowingRecursiveMapSequence { + + public struct AsyncIterator: AsyncIteratorProtocol { + + @usableFromInline + var base: Base.AsyncIterator? + + @usableFromInline + var mapped: ArraySlice = [] + + @usableFromInline + var mapped_iterator: Transformed.AsyncIterator? + + @usableFromInline + var transform: @Sendable (Base.Element) async throws -> Transformed + + @inlinable + init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async throws -> Transformed) { + self.base = base.makeAsyncIterator() + self.transform = transform + } + + @inlinable + public mutating func next() async throws -> Base.Element? { + + if self.base != nil { + + if let element = try await self.base?.next() { + try await mapped.append(transform(element)) + return element + } + + self.base = nil + self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() + } + + while self.mapped_iterator != nil { + + if let element = try await self.mapped_iterator?.next() { + try await mapped.append(transform(element)) + return element + } + + self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() + } + + return nil + } + } +} + +extension AsyncThrowingRecursiveMapSequence: Sendable +where Base: Sendable, Base.Element: Sendable, Transformed: Sendable { } + +extension AsyncThrowingRecursiveMapSequence.AsyncIterator: Sendable +where Base.AsyncIterator: Sendable, Base.Element: Sendable, Transformed: Sendable, Transformed.AsyncIterator: Sendable { } From 79b0aae629186afc80d98e5a5adfa18f35f39b50 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 01:09:47 +0800 Subject: [PATCH 02/25] Create TestRecursiveMap.swift --- .../TestRecursiveMap.swift | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift diff --git a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift new file mode 100644 index 00000000..523696be --- /dev/null +++ b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift @@ -0,0 +1,103 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Async Algorithms open source project +// +// Copyright (c) 2022 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 +// +//===----------------------------------------------------------------------===// + +@preconcurrency import XCTest +import AsyncAlgorithms + +final class TestRecursiveMap: XCTestCase { + + struct Dir: Hashable { + + var id: UUID = UUID() + + var parent: UUID? + + var name: String + + } + + struct Path: Hashable { + + var id: UUID + + var path: String + + } + + func testAsyncRecursiveMap() async { + + var list: [Dir] = [] + list.append(Dir(name: "root")) + list.append(Dir(parent: list[0].id, name: "images")) + list.append(Dir(parent: list[0].id, name: "Users")) + list.append(Dir(parent: list[2].id, name: "Susan")) + list.append(Dir(parent: list[3].id, name: "Desktop")) + list.append(Dir(parent: list[1].id, name: "test.jpg")) + + let answer = [ + Path(id: list[0].id, path: "/root"), + Path(id: list[1].id, path: "/root/images"), + Path(id: list[2].id, path: "/root/Users"), + Path(id: list[5].id, path: "/root/images/test.jpg"), + Path(id: list[3].id, path: "/root/Users/Susan"), + Path(id: list[4].id, path: "/root/Users/Susan/Desktop"), + ] + + let _list = list + + let _result: AsyncRecursiveMapSequence = list.async + .compactMap { $0.parent == nil ? Path(id: $0.id, path: "/\($0.name)") : nil } + .recursiveMap { parent in _list.async.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } + + var result: [Path] = [] + + for await item in _result { + result.append(item) + } + + XCTAssertEqual(result, answer) + } + + func testAsyncThrowingRecursiveMap() async throws { + + var list: [Dir] = [] + list.append(Dir(name: "root")) + list.append(Dir(parent: list[0].id, name: "images")) + list.append(Dir(parent: list[0].id, name: "Users")) + list.append(Dir(parent: list[2].id, name: "Susan")) + list.append(Dir(parent: list[3].id, name: "Desktop")) + list.append(Dir(parent: list[1].id, name: "test.jpg")) + + let answer = [ + Path(id: list[0].id, path: "/root"), + Path(id: list[1].id, path: "/root/images"), + Path(id: list[2].id, path: "/root/Users"), + Path(id: list[5].id, path: "/root/images/test.jpg"), + Path(id: list[3].id, path: "/root/Users/Susan"), + Path(id: list[4].id, path: "/root/Users/Susan/Desktop"), + ] + + let _list = list + + let _result: AsyncThrowingRecursiveMapSequence = list.async + .compactMap { $0.parent == nil ? Path(id: $0.id, path: "/\($0.name)") : nil } + .recursiveMap { parent in _list.async.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } + + var result: [Path] = [] + + for try await item in _result { + result.append(item) + } + + XCTAssertEqual(result, answer) + } + +} From 84cccba1bbe3922236962c4687e4d370a57bd242 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:46:35 +0800 Subject: [PATCH 03/25] add document --- Guides/RecursiveMap.md | 56 +++++++++++++++++++ .../AsyncRecursiveMapSequence.swift | 32 ++++++++++- .../AsyncThrowingRecursiveMapSequence.swift | 32 ++++++++++- .../TestRecursiveMap.swift | 55 ++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 Guides/RecursiveMap.md diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md new file mode 100644 index 00000000..8b43cb93 --- /dev/null +++ b/Guides/RecursiveMap.md @@ -0,0 +1,56 @@ +# RecursiveMap + +* Author(s): [Susan Cheng](https://github.com/SusanDoggie) + +[ +[Source](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift) | +[Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift) +] + +Produces a sequence containing the original sequence followed by recursive mapped sequence. + +```swift +struct View { + var id: Int + var children: [View] = [] +} +let tree = [ + View(id: 1, children: [ + View(id: 3), + View(id: 4, children: [ + View(id: 6), + ]), + View(id: 5), + ]), + View(id: 2), +] +for await view in tree.async.recursiveMap({ $0.children.async }) { + print(view.id) +} +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +``` + +## Detailed Design + +The `recursiveMap(_:)` method is declared as `AsyncSequence` extensions, and return `AsyncRecursiveMapSequence` or `AsyncThrowingRecursiveMapSequence` instance: + +```swift +extension AsyncSequence { + public func recursiveMap( + _ transform: @Sendable @escaping (Element) async -> S + ) -> AsyncRecursiveMapSequence + + public func recursiveMap( + _ transform: @Sendable @escaping (Element) async throws -> S + ) -> AsyncThrowingRecursiveMapSequence +} +``` + +### Complexity + +Calling this method is O(_1_). diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift index 2f8837a9..9a0ab16c 100644 --- a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -10,7 +10,37 @@ //===----------------------------------------------------------------------===// extension AsyncSequence { - + /// Returns a sequence containing the original sequence followed by recursive mapped sequence. + /// + /// ``` + /// struct View { + /// var id: Int + /// var children: [View] = [] + /// } + /// let tree = [ + /// View(id: 1, children: [ + /// View(id: 3), + /// View(id: 4, children: [ + /// View(id: 6), + /// ]), + /// View(id: 5), + /// ]), + /// View(id: 2), + /// ] + /// for await view in tree.async.recursiveMap({ $0.children.async }) { + /// print(view.id) + /// } + /// // 1 + /// // 2 + /// // 3 + /// // 4 + /// // 5 + /// // 6 + /// ``` + /// + /// - Parameters: + /// - transform: A closure that map the element to new sequence. + /// - Returns: A sequence of the original sequence followed by recursive mapped sequence. @inlinable public func recursiveMap(_ transform: @Sendable @escaping (Element) async -> C) -> AsyncRecursiveMapSequence { return AsyncRecursiveMapSequence(self, transform) diff --git a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift index 6d920126..6fc00c0a 100644 --- a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift @@ -10,7 +10,37 @@ //===----------------------------------------------------------------------===// extension AsyncSequence { - + /// Returns a sequence containing the original sequence followed by recursive mapped sequence. + /// + /// ``` + /// struct View { + /// var id: Int + /// var children: [View] = [] + /// } + /// let tree = [ + /// View(id: 1, children: [ + /// View(id: 3), + /// View(id: 4, children: [ + /// View(id: 6), + /// ]), + /// View(id: 5), + /// ]), + /// View(id: 2), + /// ] + /// for await view in tree.async.recursiveMap({ $0.children.async }) { + /// print(view.id) + /// } + /// // 1 + /// // 2 + /// // 3 + /// // 4 + /// // 5 + /// // 6 + /// ``` + /// + /// - Parameters: + /// - transform: A closure that map the element to new sequence. + /// - Returns: A sequence of the original sequence followed by recursive mapped sequence. @inlinable public func recursiveMap(_ transform: @Sendable @escaping (Element) async throws -> C) -> AsyncThrowingRecursiveMapSequence { return AsyncThrowingRecursiveMapSequence(self, transform) diff --git a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift index 523696be..2f0c667c 100644 --- a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift +++ b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift @@ -100,4 +100,59 @@ final class TestRecursiveMap: XCTestCase { XCTAssertEqual(result, answer) } + struct View { + + var id: Int + + var children: [View] = [] + } + + func testAsyncRecursiveMap2() async { + + let tree = [ + View(id: 1, children: [ + View(id: 3), + View(id: 4, children: [ + View(id: 6), + ]), + View(id: 5), + ]), + View(id: 2), + ] + + let views: AsyncRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } + + var result: [Int] = [] + + for await view in views { + result.append(view.id) + } + + XCTAssertEqual(result, Array(1...6)) + } + + func testAsyncThrowingRecursiveMap2() async throws { + + let tree = [ + View(id: 1, children: [ + View(id: 3), + View(id: 4, children: [ + View(id: 6), + ]), + View(id: 5), + ]), + View(id: 2), + ] + + let views: AsyncThrowingRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } + + var result: [Int] = [] + + for try await view in views { + result.append(view.id) + } + + XCTAssertEqual(result, Array(1...6)) + } + } From eaff804efcdd8f848f69f346c885e6eb97d71de2 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:46:40 +0800 Subject: [PATCH 04/25] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 51344b1b..fe982b2c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ This package is the home for these APIs. Development and API design take place o #### Other useful asynchronous sequences - [`chunks(...)` and `chunked(...)`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/Chunked.md): Collect values into chunks. - [`compacted()`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/Compacted.md): Remove nil values from an asynchronous sequence. +- [`recursiveMap(_:)`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/RecursiveMap.md): Produces a sequence containing the original sequence followed by recursive mapped sequence. - [`removeDuplicates()`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/RemoveDuplicates.md): Remove sequentially adjacent duplicate values. - [`interspersed(with:)`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/Intersperse.md): Place a value between every two elements of an asynchronous sequence. From 38199a0e6df2ac7e2291602f47245f254d96d1a4 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:51:41 +0800 Subject: [PATCH 05/25] rename --- Guides/RecursiveMap.md | 20 +++++----- .../AsyncRecursiveMapSequence.swift | 20 +++++----- .../AsyncThrowingRecursiveMapSequence.swift | 20 +++++----- .../TestRecursiveMap.swift | 40 +++++++++---------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 8b43cb93..0a5b4d55 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -10,22 +10,22 @@ Produces a sequence containing the original sequence followed by recursive mapped sequence. ```swift -struct View { +struct Node { var id: Int - var children: [View] = [] + var children: [Node] = [] } let tree = [ - View(id: 1, children: [ - View(id: 3), - View(id: 4, children: [ - View(id: 6), + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), ]), - View(id: 5), + Node(id: 5), ]), - View(id: 2), + Node(id: 2), ] -for await view in tree.async.recursiveMap({ $0.children.async }) { - print(view.id) +for await node in tree.async.recursiveMap({ $0.children.async }) { + print(node.id) } // 1 // 2 diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift index 9a0ab16c..62a6417d 100644 --- a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -13,22 +13,22 @@ extension AsyncSequence { /// Returns a sequence containing the original sequence followed by recursive mapped sequence. /// /// ``` - /// struct View { + /// struct Node { /// var id: Int - /// var children: [View] = [] + /// var children: [Node] = [] /// } /// let tree = [ - /// View(id: 1, children: [ - /// View(id: 3), - /// View(id: 4, children: [ - /// View(id: 6), + /// Node(id: 1, children: [ + /// Node(id: 3), + /// Node(id: 4, children: [ + /// Node(id: 6), /// ]), - /// View(id: 5), + /// Node(id: 5), /// ]), - /// View(id: 2), + /// Node(id: 2), /// ] - /// for await view in tree.async.recursiveMap({ $0.children.async }) { - /// print(view.id) + /// for await node in tree.async.recursiveMap({ $0.children.async }) { + /// print(node.id) /// } /// // 1 /// // 2 diff --git a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift index 6fc00c0a..42246cd9 100644 --- a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift @@ -13,22 +13,22 @@ extension AsyncSequence { /// Returns a sequence containing the original sequence followed by recursive mapped sequence. /// /// ``` - /// struct View { + /// struct Node { /// var id: Int - /// var children: [View] = [] + /// var children: [Node] = [] /// } /// let tree = [ - /// View(id: 1, children: [ - /// View(id: 3), - /// View(id: 4, children: [ - /// View(id: 6), + /// Node(id: 1, children: [ + /// Node(id: 3), + /// Node(id: 4, children: [ + /// Node(id: 6), /// ]), - /// View(id: 5), + /// Node(id: 5), /// ]), - /// View(id: 2), + /// Node(id: 2), /// ] - /// for await view in tree.async.recursiveMap({ $0.children.async }) { - /// print(view.id) + /// for await node in tree.async.recursiveMap({ $0.children.async }) { + /// print(node.id) /// } /// // 1 /// // 2 diff --git a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift index 2f0c667c..9b3739a7 100644 --- a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift +++ b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift @@ -100,32 +100,32 @@ final class TestRecursiveMap: XCTestCase { XCTAssertEqual(result, answer) } - struct View { + struct Node { var id: Int - var children: [View] = [] + var children: [Node] = [] } func testAsyncRecursiveMap2() async { let tree = [ - View(id: 1, children: [ - View(id: 3), - View(id: 4, children: [ - View(id: 6), + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), ]), - View(id: 5), + Node(id: 5), ]), - View(id: 2), + Node(id: 2), ] - let views: AsyncRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } + let nodes: AsyncRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } var result: [Int] = [] - for await view in views { - result.append(view.id) + for await node in nodes { + result.append(node.id) } XCTAssertEqual(result, Array(1...6)) @@ -134,22 +134,22 @@ final class TestRecursiveMap: XCTestCase { func testAsyncThrowingRecursiveMap2() async throws { let tree = [ - View(id: 1, children: [ - View(id: 3), - View(id: 4, children: [ - View(id: 6), + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), ]), - View(id: 5), + Node(id: 5), ]), - View(id: 2), + Node(id: 2), ] - let views: AsyncThrowingRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } + let nodes: AsyncThrowingRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } var result: [Int] = [] - for try await view in views { - result.append(view.id) + for try await node in nodes { + result.append(node.id) } XCTAssertEqual(result, Array(1...6)) From 6774fc697619584494bec589d4e36f18facba59c Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:56:08 +0800 Subject: [PATCH 06/25] Update Effects.md --- Guides/Effects.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Guides/Effects.md b/Guides/Effects.md index 09c1b28a..42b681f9 100644 --- a/Guides/Effects.md +++ b/Guides/Effects.md @@ -40,6 +40,8 @@ | `AsyncMerge2Sequence.Iterator` | rethrows | Sendable | | `AsyncMerge3Sequence` | rethrows | Sendable | | `AsyncMerge3Sequence.Iterator` | rethrows | Sendable | +| `AsyncRecursiveMapSequence` | rethrows | Conditional | +| `AsyncRecursiveMapSequence.Iterator` | rethrows | Conditional | | `AsyncRemoveDuplicatesSequence` | rethrows | Conditional | | `AsyncRemoveDuplicatesSequence.Iterator` | rethrows | Conditional | | `AsyncThrottleSequence` | rethrows | Conditional | @@ -50,6 +52,8 @@ | `AsyncThrowingExclusiveReductionsSequence.Iterator` | throws | Conditional | | `AsyncThrowingInclusiveReductionsSequence` | throws | Conditional | | `AsyncThrowingInclusiveReductionsSequence.Iterator` | throws | Conditional | +| `AsyncThrowingRecursiveMapSequence` | throws | Conditional | +| `AsyncThrowingRecursiveMapSequence.Iterator` | throws | Conditional | | `AsyncTimerSequence` | non-throwing | Sendable | | `AsyncTimerSequence.Iterator` | non-throwing | Sendable | | `AsyncZip2Sequence` | rethrows | Sendable | From 5d63ffec3e439331f2289be179812354d528c6b9 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 15:20:18 +0800 Subject: [PATCH 07/25] add TraversalOption --- .../AsyncRecursiveMapSequence.swift | 108 +++++++++++---- .../AsyncThrowingRecursiveMapSequence.swift | 126 ++++++++++++++---- 2 files changed, 184 insertions(+), 50 deletions(-) diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift index 62a6417d..9c5292d5 100644 --- a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -19,13 +19,13 @@ extension AsyncSequence { /// } /// let tree = [ /// Node(id: 1, children: [ - /// Node(id: 3), - /// Node(id: 4, children: [ - /// Node(id: 6), + /// Node(id: 2), + /// Node(id: 3, children: [ + /// Node(id: 4), /// ]), /// Node(id: 5), /// ]), - /// Node(id: 2), + /// Node(id: 6), /// ] /// for await node in tree.async.recursiveMap({ $0.children.async }) { /// print(node.id) @@ -39,11 +39,15 @@ extension AsyncSequence { /// ``` /// /// - Parameters: + /// - option: Traversal option. default depth-first. /// - transform: A closure that map the element to new sequence. /// - Returns: A sequence of the original sequence followed by recursive mapped sequence. @inlinable - public func recursiveMap(_ transform: @Sendable @escaping (Element) async -> C) -> AsyncRecursiveMapSequence { - return AsyncRecursiveMapSequence(self, transform) + public func recursiveMap( + option: AsyncRecursiveMapSequence.TraversalOption = .depthFirst, + _ transform: @Sendable @escaping (Element) async -> C + ) -> AsyncRecursiveMapSequence { + return AsyncRecursiveMapSequence(self, option, transform) } } @@ -54,30 +58,49 @@ public struct AsyncRecursiveMapSequence Transformed @inlinable - init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async -> Transformed) { + init( + _ base: Base, + _ option: TraversalOption, + _ transform: @Sendable @escaping (Base.Element) async -> Transformed + ) { self.base = base + self.option = option self.transform = transform } @inlinable public func makeAsyncIterator() -> AsyncIterator { - return AsyncIterator(base, transform) + return AsyncIterator(base, option, transform) } } extension AsyncRecursiveMapSequence { + public enum TraversalOption: Sendable { + + case depthFirst + + case breadthFirst + + } + public struct AsyncIterator: AsyncIteratorProtocol { @usableFromInline var base: Base.AsyncIterator? @usableFromInline - var mapped: ArraySlice = [] + let option: TraversalOption + + @usableFromInline + var mapped: ArraySlice = [] @usableFromInline var mapped_iterator: Transformed.AsyncIterator? @@ -86,36 +109,71 @@ extension AsyncRecursiveMapSequence { var transform: @Sendable (Base.Element) async -> Transformed @inlinable - init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async -> Transformed) { + init( + _ base: Base, + _ option: TraversalOption, + _ transform: @Sendable @escaping (Base.Element) async -> Transformed + ) { self.base = base.makeAsyncIterator() + self.option = option self.transform = transform } @inlinable public mutating func next() async rethrows -> Base.Element? { - if self.base != nil { + switch option { - if let element = try await self.base?.next() { - await mapped.append(transform(element)) - return element + case .depthFirst: + + while self.mapped_iterator != nil { + + if let element = try await self.mapped_iterator!.next() { + mapped.append(self.mapped_iterator!) + self.mapped_iterator = await transform(element).makeAsyncIterator() + return element + } + + self.mapped_iterator = mapped.popLast() } - self.base = nil - self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() - } - - while self.mapped_iterator != nil { + if self.base != nil { + + if let element = try await self.base!.next() { + self.mapped_iterator = await transform(element).makeAsyncIterator() + return element + } + + self.base = nil + } + + return nil - if let element = try await self.mapped_iterator?.next() { - await mapped.append(transform(element)) - return element + case .breadthFirst: + + if self.base != nil { + + if let element = try await self.base!.next() { + await mapped.append(transform(element).makeAsyncIterator()) + return element + } + + self.base = nil + self.mapped_iterator = mapped.popFirst() + } + + while self.mapped_iterator != nil { + + if let element = try await self.mapped_iterator!.next() { + await mapped.append(transform(element).makeAsyncIterator()) + return element + } + + self.mapped_iterator = mapped.popFirst() } - self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() + return nil } - - return nil } } } diff --git a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift index 42246cd9..dace10f8 100644 --- a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift @@ -19,13 +19,13 @@ extension AsyncSequence { /// } /// let tree = [ /// Node(id: 1, children: [ - /// Node(id: 3), - /// Node(id: 4, children: [ - /// Node(id: 6), + /// Node(id: 2), + /// Node(id: 3, children: [ + /// Node(id: 4), /// ]), /// Node(id: 5), /// ]), - /// Node(id: 2), + /// Node(id: 6), /// ] /// for await node in tree.async.recursiveMap({ $0.children.async }) { /// print(node.id) @@ -39,11 +39,15 @@ extension AsyncSequence { /// ``` /// /// - Parameters: + /// - option: Traversal option. default depth-first. /// - transform: A closure that map the element to new sequence. /// - Returns: A sequence of the original sequence followed by recursive mapped sequence. @inlinable - public func recursiveMap(_ transform: @Sendable @escaping (Element) async throws -> C) -> AsyncThrowingRecursiveMapSequence { - return AsyncThrowingRecursiveMapSequence(self, transform) + public func recursiveMap( + option: AsyncThrowingRecursiveMapSequence.TraversalOption = .depthFirst, + _ transform: @Sendable @escaping (Element) async throws -> C + ) -> AsyncThrowingRecursiveMapSequence { + return AsyncThrowingRecursiveMapSequence(self, option, transform) } } @@ -54,30 +58,49 @@ public struct AsyncThrowingRecursiveMapSequence Transformed @inlinable - init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async throws -> Transformed) { + init( + _ base: Base, + _ option: TraversalOption, + _ transform: @Sendable @escaping (Base.Element) async throws -> Transformed + ) { self.base = base + self.option = option self.transform = transform } @inlinable public func makeAsyncIterator() -> AsyncIterator { - return AsyncIterator(base, transform) + return AsyncIterator(base, option, transform) } } extension AsyncThrowingRecursiveMapSequence { + public enum TraversalOption: Sendable { + + case depthFirst + + case breadthFirst + + } + public struct AsyncIterator: AsyncIteratorProtocol { @usableFromInline var base: Base.AsyncIterator? @usableFromInline - var mapped: ArraySlice = [] + let option: TraversalOption + + @usableFromInline + var mapped: ArraySlice = [] @usableFromInline var mapped_iterator: Transformed.AsyncIterator? @@ -86,36 +109,89 @@ extension AsyncThrowingRecursiveMapSequence { var transform: @Sendable (Base.Element) async throws -> Transformed @inlinable - init(_ base: Base, _ transform: @Sendable @escaping (Base.Element) async throws -> Transformed) { + init( + _ base: Base, + _ option: TraversalOption, + _ transform: @Sendable @escaping (Base.Element) async throws -> Transformed + ) { self.base = base.makeAsyncIterator() + self.option = option self.transform = transform } @inlinable - public mutating func next() async throws -> Base.Element? { + mutating func _tryMakeIterator(_ element: Element) async throws -> Transformed.AsyncIterator { - if self.base != nil { + do { - if let element = try await self.base?.next() { - try await mapped.append(transform(element)) - return element - } + return try await transform(element).makeAsyncIterator() - self.base = nil - self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() + } catch { + + // set all state to empty + base = nil + mapped = [] + mapped_iterator = nil + + throw error } + } + + @inlinable + public mutating func next() async throws -> Base.Element? { - while self.mapped_iterator != nil { + switch option { + + case .depthFirst: - if let element = try await self.mapped_iterator?.next() { - try await mapped.append(transform(element)) - return element + while self.mapped_iterator != nil { + + if let element = try await self.mapped_iterator!.next() { + mapped.append(self.mapped_iterator!) + self.mapped_iterator = try await _tryMakeIterator(element) + return element + } + + self.mapped_iterator = mapped.popLast() } - self.mapped_iterator = mapped.popFirst()?.makeAsyncIterator() + if self.base != nil { + + if let element = try await self.base!.next() { + self.mapped_iterator = try await _tryMakeIterator(element) + return element + } + + self.base = nil + } + + return nil + + case .breadthFirst: + + if self.base != nil { + + if let element = try await self.base!.next() { + try await mapped.append(_tryMakeIterator(element)) + return element + } + + self.base = nil + self.mapped_iterator = mapped.popFirst() + } + + while self.mapped_iterator != nil { + + if let element = try await self.mapped_iterator!.next() { + try await mapped.append(_tryMakeIterator(element)) + return element + } + + self.mapped_iterator = mapped.popFirst() + } + + return nil } - - return nil } } } From de6e51b19a3db3525aef127a72487c0d46c47344 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 15:22:18 +0800 Subject: [PATCH 08/25] Update TestRecursiveMap.swift --- .../TestRecursiveMap.swift | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift index 9b3739a7..78547c69 100644 --- a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift +++ b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift @@ -55,7 +55,7 @@ final class TestRecursiveMap: XCTestCase { let _result: AsyncRecursiveMapSequence = list.async .compactMap { $0.parent == nil ? Path(id: $0.id, path: "/\($0.name)") : nil } - .recursiveMap { parent in _list.async.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } + .recursiveMap(option: .breadthFirst) { parent in _list.async.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } var result: [Path] = [] @@ -89,7 +89,7 @@ final class TestRecursiveMap: XCTestCase { let _result: AsyncThrowingRecursiveMapSequence = list.async .compactMap { $0.parent == nil ? Path(id: $0.id, path: "/\($0.name)") : nil } - .recursiveMap { parent in _list.async.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } + .recursiveMap(option: .breadthFirst) { parent in _list.async.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } var result: [Path] = [] @@ -109,6 +109,54 @@ final class TestRecursiveMap: XCTestCase { func testAsyncRecursiveMap2() async { + let tree = [ + Node(id: 1, children: [ + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), + ]), + Node(id: 5), + ]), + Node(id: 6), + ] + + let nodes: AsyncRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } // default depthFirst option + + var result: [Int] = [] + + for await node in nodes { + result.append(node.id) + } + + XCTAssertEqual(result, Array(1...6)) + } + + func testAsyncThrowingRecursiveMap2() async throws { + + let tree = [ + Node(id: 1, children: [ + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), + ]), + Node(id: 5), + ]), + Node(id: 6), + ] + + let nodes: AsyncThrowingRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } // default depthFirst option + + var result: [Int] = [] + + for try await node in nodes { + result.append(node.id) + } + + XCTAssertEqual(result, Array(1...6)) + } + + func testAsyncRecursiveMap3() async { + let tree = [ Node(id: 1, children: [ Node(id: 3), @@ -120,7 +168,7 @@ final class TestRecursiveMap: XCTestCase { Node(id: 2), ] - let nodes: AsyncRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } + let nodes: AsyncRecursiveMapSequence = tree.async.recursiveMap(option: .breadthFirst) { $0.children.async } var result: [Int] = [] @@ -131,7 +179,7 @@ final class TestRecursiveMap: XCTestCase { XCTAssertEqual(result, Array(1...6)) } - func testAsyncThrowingRecursiveMap2() async throws { + func testAsyncThrowingRecursiveMap3() async throws { let tree = [ Node(id: 1, children: [ @@ -144,7 +192,7 @@ final class TestRecursiveMap: XCTestCase { Node(id: 2), ] - let nodes: AsyncThrowingRecursiveMapSequence = tree.async.recursiveMap { $0.children.async } + let nodes: AsyncThrowingRecursiveMapSequence = tree.async.recursiveMap(option: .breadthFirst) { $0.children.async } var result: [Int] = [] From e01a2ae23dfcbfeec5bac0a14c6a569b074defa6 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 15:25:13 +0800 Subject: [PATCH 09/25] immutable --- Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift | 2 +- Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift index 9c5292d5..e29f3c1c 100644 --- a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -106,7 +106,7 @@ extension AsyncRecursiveMapSequence { var mapped_iterator: Transformed.AsyncIterator? @usableFromInline - var transform: @Sendable (Base.Element) async -> Transformed + let transform: @Sendable (Base.Element) async -> Transformed @inlinable init( diff --git a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift index dace10f8..0dffc1c4 100644 --- a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift @@ -106,7 +106,7 @@ extension AsyncThrowingRecursiveMapSequence { var mapped_iterator: Transformed.AsyncIterator? @usableFromInline - var transform: @Sendable (Base.Element) async throws -> Transformed + let transform: @Sendable (Base.Element) async throws -> Transformed @inlinable init( From a9fffdd92e99f0132056d4cd8860b37f7b318110 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 15:51:07 +0800 Subject: [PATCH 10/25] add closure throws test --- .../TestRecursiveMap.swift | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift index 78547c69..f16ac07c 100644 --- a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift +++ b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift @@ -203,4 +203,42 @@ final class TestRecursiveMap: XCTestCase { XCTAssertEqual(result, Array(1...6)) } + func testAsyncThrowingRecursiveMapWithClosureThrows() async throws { + + let tree = [ + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), + ]), + Node(id: 5), + ]), + Node(id: 2), + ] + + let nodes = tree.async.recursiveMap { node async throws -> AsyncLazySequence<[TestRecursiveMap.Node]> in + if node.id == 4 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return node.children.async + } + + var result: [Int] = [] + var iterator = nodes.makeAsyncIterator() + + do { + + while let node = try await iterator.next() { + result.append(node.id) + } + + XCTFail() + + } catch { + + XCTAssertEqual((error as NSError).code, -1) // we got throw from the closure + } + + let expectedNil = try await iterator.next() // we should get nil in here + XCTAssertNil(expectedNil) + } + } From db8f025c86ee8b41df97bba167d6369d439e612f Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 16:37:34 +0800 Subject: [PATCH 11/25] documentation --- Guides/RecursiveMap.md | 12 ++++++++++-- README.md | 2 +- .../AsyncAlgorithms/AsyncRecursiveMapSequence.swift | 12 +++++++++--- .../AsyncThrowingRecursiveMapSequence.swift | 12 +++++++++--- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 0a5b4d55..7b1612bd 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -7,7 +7,9 @@ [Tests](https://github.com/apple/swift-async-algorithms/blob/main/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift) ] -Produces a sequence containing the original sequence followed by recursive mapped sequence. +## Proposed Solution + +Produces a sequence containing the original sequence and the recursive mapped sequence. The order of ouput elements affects by the traversal option. ```swift struct Node { @@ -35,17 +37,23 @@ for await node in tree.async.recursiveMap({ $0.children.async }) { // 6 ``` +### Traversal Option + +This function comes with two different traversal methods. This option affects the element order of the output sequence. + ## Detailed Design -The `recursiveMap(_:)` method is declared as `AsyncSequence` extensions, and return `AsyncRecursiveMapSequence` or `AsyncThrowingRecursiveMapSequence` instance: +The `recursiveMap(option:_:)` method is declared as `AsyncSequence` extensions, and return `AsyncRecursiveMapSequence` or `AsyncThrowingRecursiveMapSequence` instance: ```swift extension AsyncSequence { public func recursiveMap( + option: AsyncRecursiveMapSequence.TraversalOption = .depthFirst, _ transform: @Sendable @escaping (Element) async -> S ) -> AsyncRecursiveMapSequence public func recursiveMap( + option: AsyncThrowingRecursiveMapSequence.TraversalOption = .depthFirst, _ transform: @Sendable @escaping (Element) async throws -> S ) -> AsyncThrowingRecursiveMapSequence } diff --git a/README.md b/README.md index fe982b2c..e239e296 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ This package is the home for these APIs. Development and API design take place o #### Other useful asynchronous sequences - [`chunks(...)` and `chunked(...)`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/Chunked.md): Collect values into chunks. - [`compacted()`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/Compacted.md): Remove nil values from an asynchronous sequence. -- [`recursiveMap(_:)`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/RecursiveMap.md): Produces a sequence containing the original sequence followed by recursive mapped sequence. +- [`recursiveMap(option:_:)`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/RecursiveMap.md): Produces a sequence containing the original sequence and the recursive mapped sequence. The order of ouput elements affects by the traversal option. - [`removeDuplicates()`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/RemoveDuplicates.md): Remove sequentially adjacent duplicate values. - [`interspersed(with:)`](https://github.com/apple/swift-async-algorithms/blob/main/Guides/Intersperse.md): Place a value between every two elements of an asynchronous sequence. diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift index e29f3c1c..0213f82a 100644 --- a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -10,7 +10,8 @@ //===----------------------------------------------------------------------===// extension AsyncSequence { - /// Returns a sequence containing the original sequence followed by recursive mapped sequence. + /// Returns a sequence containing the original sequence and the recursive mapped sequence. + /// The order of ouput elements affects by the traversal option. /// /// ``` /// struct Node { @@ -39,7 +40,7 @@ extension AsyncSequence { /// ``` /// /// - Parameters: - /// - option: Traversal option. default depth-first. + /// - option: Traversal option. This option affects the element order of the output sequence. default depth-first. /// - transform: A closure that map the element to new sequence. /// - Returns: A sequence of the original sequence followed by recursive mapped sequence. @inlinable @@ -51,6 +52,8 @@ extension AsyncSequence { } } +/// A sequence containing the original sequence and the recursive mapped sequence. +/// The order of ouput elements affects by the traversal option. public struct AsyncRecursiveMapSequence: AsyncSequence where Base.Element == Transformed.Element { public typealias Element = Base.Element @@ -83,10 +86,13 @@ public struct AsyncRecursiveMapSequence: AsyncSequence where Base.Element == Transformed.Element { public typealias Element = Base.Element @@ -83,10 +86,13 @@ public struct AsyncThrowingRecursiveMapSequence Date: Mon, 28 Mar 2022 16:38:22 +0800 Subject: [PATCH 12/25] fix the missing Sendable --- Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift | 2 +- Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift index 0213f82a..e4805293 100644 --- a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -87,7 +87,7 @@ public struct AsyncRecursiveMapSequence Date: Mon, 28 Mar 2022 16:40:07 +0800 Subject: [PATCH 13/25] Update RecursiveMap.md --- Guides/RecursiveMap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 7b1612bd..3d9479f6 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -41,6 +41,10 @@ for await node in tree.async.recursiveMap({ $0.children.async }) { This function comes with two different traversal methods. This option affects the element order of the output sequence. +- `depthFirst`: The algorithm will go down first and produce the resulting path. + +- `breadthFirst`: The algorithm will go through the previous sequence first and chaining all the occurring sequences. + ## Detailed Design The `recursiveMap(option:_:)` method is declared as `AsyncSequence` extensions, and return `AsyncRecursiveMapSequence` or `AsyncThrowingRecursiveMapSequence` instance: From edeeffde03aec2acf9f36d1db867118ca3d861a1 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 16:49:32 +0800 Subject: [PATCH 14/25] Update RecursiveMap.md --- Guides/RecursiveMap.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 3d9479f6..39d03e12 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -18,13 +18,13 @@ struct Node { } let tree = [ Node(id: 1, children: [ - Node(id: 3), - Node(id: 4, children: [ - Node(id: 6), + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), ]), Node(id: 5), ]), - Node(id: 2), + Node(id: 6), ] for await node in tree.async.recursiveMap({ $0.children.async }) { print(node.id) @@ -41,7 +41,8 @@ for await node in tree.async.recursiveMap({ $0.children.async }) { This function comes with two different traversal methods. This option affects the element order of the output sequence. -- `depthFirst`: The algorithm will go down first and produce the resulting path. +- `depthFirst`: The algorithm will go down first and produce the resulting path. The algorithm starts with original + sequence and calling the supplied closure first. This is default option. - `breadthFirst`: The algorithm will go through the previous sequence first and chaining all the occurring sequences. From 8588e7136baaa30b3b77934e56f4fa9c60e23221 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 16:52:43 +0800 Subject: [PATCH 15/25] Update RecursiveMap.md --- Guides/RecursiveMap.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 39d03e12..a81f7847 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -43,9 +43,41 @@ This function comes with two different traversal methods. This option affects th - `depthFirst`: The algorithm will go down first and produce the resulting path. The algorithm starts with original sequence and calling the supplied closure first. This is default option. + + With the structure of tree: + ```swift + let tree = [ + Node(id: 1, children: [ + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), + ]), + Node(id: 5), + ]), + Node(id: 6), + ] + ``` + + The resulting sequence will be 1 -> 2 -> 3 -> 4 -> 5 -> 6 - `breadthFirst`: The algorithm will go through the previous sequence first and chaining all the occurring sequences. + With the structure of tree: + ```swift + let tree = [ + Node(id: 1, children: [ + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), + ]), + Node(id: 5), + ]), + Node(id: 6), + ] + ``` + + The resulting sequence will be 1 -> 6 -> 2 -> 3 -> 5 -> 4 + ## Detailed Design The `recursiveMap(option:_:)` method is declared as `AsyncSequence` extensions, and return `AsyncRecursiveMapSequence` or `AsyncThrowingRecursiveMapSequence` instance: From c7e313ea78d6001bb89200356662cd51a985fd82 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 17:25:00 +0800 Subject: [PATCH 16/25] Update RecursiveMap.md --- Guides/RecursiveMap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index a81f7847..43e3e06a 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -96,6 +96,10 @@ extension AsyncSequence { } ``` +For the non-throwing recursive map sequence, `AsyncRecursiveMapSequence` will throw only if the base sequence or the transformed sequence throws. As the opposite side, `AsyncThrowingRecursiveMapSequence` throws when the base sequence, tarnsformed sequence or the supplied closure throws. + +The sendability behavior of `Async[Throwing]RecursiveMapSequence` is such that when the base, base iterator, and element are `Sendable` then `Async[Throwing]RecursiveMapSequence` is `Sendable`. + ### Complexity Calling this method is O(_1_). From 7f0afd02cf07fe717ef7647799d9e9381038e583 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 17:25:41 +0800 Subject: [PATCH 17/25] Update RecursiveMap.md --- Guides/RecursiveMap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 43e3e06a..73675932 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -96,7 +96,7 @@ extension AsyncSequence { } ``` -For the non-throwing recursive map sequence, `AsyncRecursiveMapSequence` will throw only if the base sequence or the transformed sequence throws. As the opposite side, `AsyncThrowingRecursiveMapSequence` throws when the base sequence, tarnsformed sequence or the supplied closure throws. +For the non-throwing recursive map sequence, `AsyncRecursiveMapSequence` will throw only if the base sequence or the transformed sequence throws. As the opposite side, `AsyncThrowingRecursiveMapSequence` throws when the base sequence, the transformed sequence or the supplied closure throws. The sendability behavior of `Async[Throwing]RecursiveMapSequence` is such that when the base, base iterator, and element are `Sendable` then `Async[Throwing]RecursiveMapSequence` is `Sendable`. From 2f398cf0e599f9cdc6ae2716e83b4e23651e1803 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 17:32:12 +0800 Subject: [PATCH 18/25] Update RecursiveMap.md --- Guides/RecursiveMap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 73675932..9d2c64e5 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -59,6 +59,8 @@ This function comes with two different traversal methods. This option affects th ``` The resulting sequence will be 1 -> 2 -> 3 -> 4 -> 5 -> 6 + + The sequence using a buffer keep tracking the path of nodes. It should not using this option for searching the indefinite deep of tree. - `breadthFirst`: The algorithm will go through the previous sequence first and chaining all the occurring sequences. @@ -77,6 +79,8 @@ This function comes with two different traversal methods. This option affects th ``` The resulting sequence will be 1 -> 6 -> 2 -> 3 -> 5 -> 4 + + The sequence using a buffer storing occuring nodes of sequences. It should not using this option for searching the indefinite length of occuring sequences. ## Detailed Design From 5135e3d90e0d3ec025ec239925ffffe5d37bdaa9 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 17:52:48 +0800 Subject: [PATCH 19/25] test all route of throws --- .../AsyncRecursiveMapSequence.swift | 20 ++- .../AsyncThrowingRecursiveMapSequence.swift | 46 +++--- .../TestRecursiveMap.swift | 156 ++++++++++++++++++ 3 files changed, 198 insertions(+), 24 deletions(-) diff --git a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift index e4805293..2ab47a3c 100644 --- a/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncRecursiveMapSequence.swift @@ -126,7 +126,7 @@ extension AsyncRecursiveMapSequence { } @inlinable - public mutating func next() async rethrows -> Base.Element? { + mutating func tryNext() async rethrows -> Base.Element? { switch option { @@ -181,6 +181,24 @@ extension AsyncRecursiveMapSequence { return nil } } + + @inlinable + public mutating func next() async rethrows -> Base.Element? { + + do { + + return try await self.tryNext() + + } catch { + + // set all state to empty + base = nil + mapped = [] + mapped_iterator = nil + + throw error + } + } } } diff --git a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift index ad5c4b3d..c98a5859 100644 --- a/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift +++ b/Sources/AsyncAlgorithms/AsyncThrowingRecursiveMapSequence.swift @@ -126,25 +126,7 @@ extension AsyncThrowingRecursiveMapSequence { } @inlinable - mutating func _tryMakeIterator(_ element: Element) async throws -> Transformed.AsyncIterator { - - do { - - return try await transform(element).makeAsyncIterator() - - } catch { - - // set all state to empty - base = nil - mapped = [] - mapped_iterator = nil - - throw error - } - } - - @inlinable - public mutating func next() async throws -> Base.Element? { + mutating func tryNext() async throws -> Base.Element? { switch option { @@ -154,7 +136,7 @@ extension AsyncThrowingRecursiveMapSequence { if let element = try await self.mapped_iterator!.next() { mapped.append(self.mapped_iterator!) - self.mapped_iterator = try await _tryMakeIterator(element) + self.mapped_iterator = try await transform(element).makeAsyncIterator() return element } @@ -164,7 +146,7 @@ extension AsyncThrowingRecursiveMapSequence { if self.base != nil { if let element = try await self.base!.next() { - self.mapped_iterator = try await _tryMakeIterator(element) + self.mapped_iterator = try await transform(element).makeAsyncIterator() return element } @@ -178,7 +160,7 @@ extension AsyncThrowingRecursiveMapSequence { if self.base != nil { if let element = try await self.base!.next() { - try await mapped.append(_tryMakeIterator(element)) + try await mapped.append(transform(element).makeAsyncIterator()) return element } @@ -189,7 +171,7 @@ extension AsyncThrowingRecursiveMapSequence { while self.mapped_iterator != nil { if let element = try await self.mapped_iterator!.next() { - try await mapped.append(_tryMakeIterator(element)) + try await mapped.append(transform(element).makeAsyncIterator()) return element } @@ -199,6 +181,24 @@ extension AsyncThrowingRecursiveMapSequence { return nil } } + + @inlinable + public mutating func next() async throws -> Base.Element? { + + do { + + return try await self.tryNext() + + } catch { + + // set all state to empty + base = nil + mapped = [] + mapped_iterator = nil + + throw error + } + } } } diff --git a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift index f16ac07c..01f57f4f 100644 --- a/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift +++ b/Tests/AsyncAlgorithmsTests/TestRecursiveMap.swift @@ -203,6 +203,162 @@ final class TestRecursiveMap: XCTestCase { XCTAssertEqual(result, Array(1...6)) } + func testAsyncRecursiveMapWithBaseSequenceThrows() async throws { + + let tree = [ + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), + ]), + Node(id: 5), + ]), + Node(id: 2), + ] + + let throwing_base = tree.async.map { node async throws -> Node in + if node.id == 2 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return node + } + + let nodes: AsyncRecursiveMapSequence = throwing_base.recursiveMap { $0.children.async } + + var result: [Int] = [] + var iterator = nodes.makeAsyncIterator() + + do { + + while let node = try await iterator.next() { + result.append(node.id) + } + + XCTFail() + + } catch { + + XCTAssertEqual((error as NSError).code, -1) // we got throw from the closure + } + + let expectedNil = try await iterator.next() // we should get nil in here + XCTAssertNil(expectedNil) + } + + func testAsyncThrowingRecursiveMapWithBaseSequenceThrows() async throws { + + let tree = [ + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), + ]), + Node(id: 5), + ]), + Node(id: 2), + ] + + let throwing_base = tree.async.map { node async throws -> Node in + if node.id == 2 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return node + } + + let nodes: AsyncThrowingRecursiveMapSequence = throwing_base.recursiveMap { $0.children.async } + + var result: [Int] = [] + var iterator = nodes.makeAsyncIterator() + + do { + + while let node = try await iterator.next() { + result.append(node.id) + } + + XCTFail() + + } catch { + + XCTAssertEqual((error as NSError).code, -1) // we got throw from the closure + } + + let expectedNil = try await iterator.next() // we should get nil in here + XCTAssertNil(expectedNil) + } + + func testAsyncRecursiveMapWithMappedSequenceThrows() async throws { + + let tree = [ + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), + ]), + Node(id: 5), + ]), + Node(id: 2), + ] + + let nodes: AsyncRecursiveMapSequence = tree.async.recursiveMap { $0.children.async.map { node async throws -> Node in + if node.id == 4 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return node + } } + + var result: [Int] = [] + var iterator = nodes.makeAsyncIterator() + + do { + + while let node = try await iterator.next() { + result.append(node.id) + } + + XCTFail() + + } catch { + + XCTAssertEqual((error as NSError).code, -1) // we got throw from the closure + } + + let expectedNil = try await iterator.next() // we should get nil in here + XCTAssertNil(expectedNil) + } + + func testAsyncThrowingRecursiveMapWithMappedSequenceThrows() async throws { + + let tree = [ + Node(id: 1, children: [ + Node(id: 3), + Node(id: 4, children: [ + Node(id: 6), + ]), + Node(id: 5), + ]), + Node(id: 2), + ] + + let nodes: AsyncThrowingRecursiveMapSequence = tree.async.recursiveMap { $0.children.async.map { node async throws -> Node in + if node.id == 4 { throw NSError(domain: NSCocoaErrorDomain, code: -1, userInfo: nil) } + return node + } } + + var result: [Int] = [] + var iterator = nodes.makeAsyncIterator() + + do { + + while let node = try await iterator.next() { + result.append(node.id) + } + + XCTFail() + + } catch { + + XCTAssertEqual((error as NSError).code, -1) // we got throw from the closure + } + + let expectedNil = try await iterator.next() // we should get nil in here + XCTAssertNil(expectedNil) + } + func testAsyncThrowingRecursiveMapWithClosureThrows() async throws { let tree = [ From 864bd809d355e03517cb81daa772b905f8d739b6 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Tue, 19 Apr 2022 14:05:09 +0800 Subject: [PATCH 20/25] move file --- .../AsyncAlgorithms/AsyncAlgorithms.docc/Guides}/RecursiveMap.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {Guides => Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides}/RecursiveMap.md (100%) diff --git a/Guides/RecursiveMap.md b/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/RecursiveMap.md similarity index 100% rename from Guides/RecursiveMap.md rename to Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/RecursiveMap.md From 9f43c56fa6fc173c68259f87d8cdbce71c229c3d Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Tue, 19 Apr 2022 14:14:42 +0800 Subject: [PATCH 21/25] Create NNN-recursiveMap.md --- Evolution/NNN-recursiveMap.md | 126 ++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Evolution/NNN-recursiveMap.md diff --git a/Evolution/NNN-recursiveMap.md b/Evolution/NNN-recursiveMap.md new file mode 100644 index 00000000..acfa3eff --- /dev/null +++ b/Evolution/NNN-recursiveMap.md @@ -0,0 +1,126 @@ +# Feature name + +* Proposal: [NNNN](NNNN-filename.md) +* Authors: [Author 1](https://github.com/swiftdev), [Author 2](https://github.com/swiftdev) +* Review Manager: TBD +* Status: **Awaiting implementation** +* Implementation: [apple/swift-async-algorithms#NNNNN](https://github.com/apple/swift-async-algorithms/pull/118) + +## Introduction + +Swift forums thread: [Discussion thread topic for that proposal](https://forums.swift.org/) + +## Motivation + +## Proposed Solution + +Produces a sequence containing the original sequence and the recursive mapped sequence. The order of ouput elements affects by the traversal option. + +```swift +struct Node { + var id: Int + var children: [Node] = [] +} +let tree = [ + Node(id: 1, children: [ + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), + ]), + Node(id: 5), + ]), + Node(id: 6), +] +for await node in tree.async.recursiveMap({ $0.children.async }) { + print(node.id) +} +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +``` + +### Traversal Option + +This function comes with two different traversal methods. This option affects the element order of the output sequence. + +- `depthFirst`: The algorithm will go down first and produce the resulting path. The algorithm starts with original + sequence and calling the supplied closure first. This is default option. + + With the structure of tree: + ```swift + let tree = [ + Node(id: 1, children: [ + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), + ]), + Node(id: 5), + ]), + Node(id: 6), + ] + ``` + + The resulting sequence will be 1 -> 2 -> 3 -> 4 -> 5 -> 6 + + The sequence using a buffer keep tracking the path of nodes. It should not using this option for searching the indefinite deep of tree. + +- `breadthFirst`: The algorithm will go through the previous sequence first and chaining all the occurring sequences. + + With the structure of tree: + ```swift + let tree = [ + Node(id: 1, children: [ + Node(id: 2), + Node(id: 3, children: [ + Node(id: 4), + ]), + Node(id: 5), + ]), + Node(id: 6), + ] + ``` + + The resulting sequence will be 1 -> 6 -> 2 -> 3 -> 5 -> 4 + + The sequence using a buffer storing occuring nodes of sequences. It should not using this option for searching the indefinite length of occuring sequences. + +## Detailed Design + +The `recursiveMap(option:_:)` method is declared as `AsyncSequence` extensions, and return `AsyncRecursiveMapSequence` or `AsyncThrowingRecursiveMapSequence` instance: + +```swift +extension AsyncSequence { + public func recursiveMap( + option: AsyncRecursiveMapSequence.TraversalOption = .depthFirst, + _ transform: @Sendable @escaping (Element) async -> S + ) -> AsyncRecursiveMapSequence + + public func recursiveMap( + option: AsyncThrowingRecursiveMapSequence.TraversalOption = .depthFirst, + _ transform: @Sendable @escaping (Element) async throws -> S + ) -> AsyncThrowingRecursiveMapSequence +} +``` + +For the non-throwing recursive map sequence, `AsyncRecursiveMapSequence` will throw only if the base sequence or the transformed sequence throws. As the opposite side, `AsyncThrowingRecursiveMapSequence` throws when the base sequence, the transformed sequence or the supplied closure throws. + +The sendability behavior of `Async[Throwing]RecursiveMapSequence` is such that when the base, base iterator, and element are `Sendable` then `Async[Throwing]RecursiveMapSequence` is `Sendable`. + +### Complexity + +Calling this method is O(_1_). + +## Effect on API resilience + +none. + +## Alternatives considered + +none. + +## Acknowledgments + +none. From 8ecc2fb0aa8086e88b8ab4d53c7f37c8f924edb0 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Tue, 19 Apr 2022 14:20:17 +0800 Subject: [PATCH 22/25] Update NNN-recursiveMap.md --- Evolution/NNN-recursiveMap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Evolution/NNN-recursiveMap.md b/Evolution/NNN-recursiveMap.md index acfa3eff..cbf23f3d 100644 --- a/Evolution/NNN-recursiveMap.md +++ b/Evolution/NNN-recursiveMap.md @@ -8,9 +8,9 @@ ## Introduction -Swift forums thread: [Discussion thread topic for that proposal](https://forums.swift.org/) +Bring SQL's recursive CTE like operation to Swift. This method traverses all nodes of the tree and produces a flat sequence. -## Motivation +Swift forums thread: [Discussion thread topic for that proposal](https://forums.swift.org/) ## Proposed Solution From 25f855f296d2eb862257678583036a8edfff8e40 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Tue, 19 Apr 2022 14:20:48 +0800 Subject: [PATCH 23/25] Update NNN-recursiveMap.md --- Evolution/NNN-recursiveMap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Evolution/NNN-recursiveMap.md b/Evolution/NNN-recursiveMap.md index cbf23f3d..99c85fff 100644 --- a/Evolution/NNN-recursiveMap.md +++ b/Evolution/NNN-recursiveMap.md @@ -1,4 +1,4 @@ -# Feature name +# recursiveMap * Proposal: [NNNN](NNNN-filename.md) * Authors: [Author 1](https://github.com/swiftdev), [Author 2](https://github.com/swiftdev) From a669b6864d9fcf67aa5298756a043965d0344299 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Tue, 19 Apr 2022 14:29:36 +0800 Subject: [PATCH 24/25] Update NNN-recursiveMap.md --- Evolution/NNN-recursiveMap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Evolution/NNN-recursiveMap.md b/Evolution/NNN-recursiveMap.md index 99c85fff..e5fd2aff 100644 --- a/Evolution/NNN-recursiveMap.md +++ b/Evolution/NNN-recursiveMap.md @@ -1,7 +1,7 @@ # recursiveMap * Proposal: [NNNN](NNNN-filename.md) -* Authors: [Author 1](https://github.com/swiftdev), [Author 2](https://github.com/swiftdev) +* Authors: [SusanDoggie](https://github.com/SusanDoggie) * Review Manager: TBD * Status: **Awaiting implementation** * Implementation: [apple/swift-async-algorithms#NNNNN](https://github.com/apple/swift-async-algorithms/pull/118) From 2e05d91ff957a54d053f4f4c5c0747281acc7e5c Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Tue, 19 Apr 2022 14:30:36 +0800 Subject: [PATCH 25/25] Update NNN-recursiveMap.md --- Evolution/NNN-recursiveMap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Evolution/NNN-recursiveMap.md b/Evolution/NNN-recursiveMap.md index e5fd2aff..90fe1499 100644 --- a/Evolution/NNN-recursiveMap.md +++ b/Evolution/NNN-recursiveMap.md @@ -10,7 +10,7 @@ Bring SQL's recursive CTE like operation to Swift. This method traverses all nodes of the tree and produces a flat sequence. -Swift forums thread: [Discussion thread topic for that proposal](https://forums.swift.org/) +Swift forums thread: [[Pitch] Add recursiveMap(_:) methods](https://forums.swift.org/t/pitch-add-recursivemap-methods/56810) ## Proposed Solution