Skip to content
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

Add recursiveMap(_:) methods #118

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
27f397a
add recursiveMap methods
SusanDoggie Mar 27, 2022
79b0aae
Create TestRecursiveMap.swift
SusanDoggie Mar 27, 2022
84cccba
add document
SusanDoggie Mar 28, 2022
eaff804
Update README.md
SusanDoggie Mar 28, 2022
38199a0
rename
SusanDoggie Mar 28, 2022
6774fc6
Update Effects.md
SusanDoggie Mar 28, 2022
5d63ffe
add TraversalOption
SusanDoggie Mar 28, 2022
de6e51b
Update TestRecursiveMap.swift
SusanDoggie Mar 28, 2022
e01a2ae
immutable
SusanDoggie Mar 28, 2022
a9fffdd
add closure throws test
SusanDoggie Mar 28, 2022
db8f025
documentation
SusanDoggie Mar 28, 2022
4aebd4c
fix the missing Sendable
SusanDoggie Mar 28, 2022
80cd630
Update RecursiveMap.md
SusanDoggie Mar 28, 2022
edeeffd
Update RecursiveMap.md
SusanDoggie Mar 28, 2022
8588e71
Update RecursiveMap.md
SusanDoggie Mar 28, 2022
c7e313e
Update RecursiveMap.md
SusanDoggie Mar 28, 2022
7f0afd0
Update RecursiveMap.md
SusanDoggie Mar 28, 2022
2f398cf
Update RecursiveMap.md
SusanDoggie Mar 28, 2022
5135e3d
test all route of throws
SusanDoggie Mar 28, 2022
fd31319
Merge branch 'patch-1' of https://github.com/SusanDoggie/swift-async-…
SusanDoggie Mar 28, 2022
179c924
Merge branch 'main'
SusanDoggie Apr 19, 2022
864bd80
move file
SusanDoggie Apr 19, 2022
9f43c56
Create NNN-recursiveMap.md
SusanDoggie Apr 19, 2022
8ecc2fb
Update NNN-recursiveMap.md
SusanDoggie Apr 19, 2022
25f855f
Update NNN-recursiveMap.md
SusanDoggie Apr 19, 2022
a669b68
Update NNN-recursiveMap.md
SusanDoggie Apr 19, 2022
2e05d91
Update NNN-recursiveMap.md
SusanDoggie Apr 19, 2022
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
126 changes: 126 additions & 0 deletions Evolution/NNN-recursiveMap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# recursiveMap

* Proposal: [NNNN](NNNN-filename.md)
* 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)

## Introduction

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: [[Pitch] Add recursiveMap(_:) methods](https://forums.swift.org/t/pitch-add-recursivemap-methods/56810)

## 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<S>(
option: AsyncRecursiveMapSequence<Self, S>.TraversalOption = .depthFirst,
_ transform: @Sendable @escaping (Element) async -> S
) -> AsyncRecursiveMapSequence<Self, S>

public func recursiveMap<S>(
option: AsyncThrowingRecursiveMapSequence<Self, S>.TraversalOption = .depthFirst,
_ transform: @Sendable @escaping (Element) async throws -> S
) -> AsyncThrowingRecursiveMapSequence<Self, S>
}
```

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.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ This package is the home for these APIs. Development and API design take place o
- [`adjacentPairs()`](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/AdjacentPairs.md): Collects tuples of adjacent elements.
- [`chunks(...)` and `chunked(...)`](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/Chunked.md): Collect values into chunks.
- [`compacted()`](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/Compacted.md): Remove nil values from an asynchronous sequence.
- [`recursiveMap(option:_:)`](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/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/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/RemoveDuplicates.md): Remove sequentially adjacent duplicate values.
- [`interspersed(with:)`](https://github.com/apple/swift-async-algorithms/blob/main/Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/Intersperse.md): Place a value between every two elements of an asynchronous sequence.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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 |
Expand All @@ -51,6 +53,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 |
Expand Down
109 changes: 109 additions & 0 deletions Sources/AsyncAlgorithms/AsyncAlgorithms.docc/Guides/RecursiveMap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# 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)
]

## 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<S>(
option: AsyncRecursiveMapSequence<Self, S>.TraversalOption = .depthFirst,
_ transform: @Sendable @escaping (Element) async -> S
) -> AsyncRecursiveMapSequence<Self, S>

public func recursiveMap<S>(
option: AsyncThrowingRecursiveMapSequence<Self, S>.TraversalOption = .depthFirst,
_ transform: @Sendable @escaping (Element) async throws -> S
) -> AsyncThrowingRecursiveMapSequence<Self, S>
}
```

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_).
Loading