Skip to content

Add recursiveMap(_:) #185

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ This project follows semantic versioning.

## [Unreleased]

*No new changes.*
### Additions

One new addition to the list of algorithms:

- `recursiveMap(option:_:)` Produces a sequence containing the original sequence and
the recursive mapped sequence. The order of ouput elements affects by the traversal
option. ([#185])

---

Expand Down Expand Up @@ -308,6 +314,7 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co
[#130]: https://github.com/apple/swift-algorithms/pull/130
[#138]: https://github.com/apple/swift-algorithms/pull/138
[#162]: https://github.com/apple/swift-algorithms/pull/162
[#185]: https://github.com/apple/swift-algorithms/pull/185

<!-- Link references for contributors -->

Expand Down
96 changes: 96 additions & 0 deletions Guides/RecursiveMap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# RecursiveMap

[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/RecursiveMap.swift) |
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/RecursiveMapTests.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 node in tree.recursiveMap({ $0.children }) {
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 `Sequence` extensions, and return `RecursiveMapSequence` instance:

```swift
extension Sequence {
public func recursiveMap<S>(
option: RecursiveMapSequence<Self, S>.TraversalOption = .depthFirst,
_ transform: @escaping (Element) -> S
) -> RecursiveMapSequence<Self, S>
}
```

### Complexity

Calling this method is O(_1_).
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Read more about the package, and the intent behind it, in the [announcement on s
- [`indexed()`](https://github.com/apple/swift-algorithms/blob/main/Guides/Indexed.md): Iterate over tuples of a collection's indices and elements.
- [`interspersed(with:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Intersperse.md): Place a value between every two elements of a sequence.
- [`partitioningIndex(where:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Partition.md): Returns the starting index of the partition of a collection that matches a predicate.
- [`recursiveMap(option:_:)`](https://github.com/apple/swift-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.
- [`reductions(_:)`, `reductions(_:_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Reductions.md): Returns all the intermediate states of reducing the elements of a sequence or collection.
- [`split(maxSplits:omittingEmptySubsequences:whereSeparator)`, `split(separator:maxSplits:omittingEmptySubsequences)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Split.md): Lazy versions of the Standard Library's eager operations that split sequences and collections into subsequences separated by the specified separator element.
- [`windows(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Windows.md): Breaks a collection into overlapping subsequences where elements are slices from the original collection.
Expand Down
187 changes: 187 additions & 0 deletions Sources/Algorithms/RecursiveMap.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift 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 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 {
/// 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 node in tree.recursiveMap({ $0.children }) {
/// print(node.id)
/// }
/// // 1
/// // 2
/// // 3
/// // 4
/// // 5
/// // 6
/// ```
///
/// - Parameters:
/// - 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.
///
/// - Complexity: O(1)
@inlinable
public func recursiveMap<S>(
option: RecursiveMapSequence<Self, S>.TraversalOption = .depthFirst,
_ transform: @escaping (Element) -> S
) -> RecursiveMapSequence<Self, S> {
return RecursiveMapSequence(self, option, transform)
}
}

/// A sequence containing the original sequence and the recursive mapped sequence.
/// The order of ouput elements affects by the traversal option.
public struct RecursiveMapSequence<Base: Sequence, Transformed: Sequence>: Sequence where Base.Element == Transformed.Element {

@usableFromInline
let base: Base

@usableFromInline
let option: TraversalOption

@usableFromInline
let transform: (Base.Element) -> Transformed

@inlinable
init(
_ base: Base,
_ option: TraversalOption,
_ transform: @escaping (Base.Element) -> Transformed
) {
self.base = base
self.option = option
self.transform = transform
}

@inlinable
public func makeIterator() -> Iterator {
return Iterator(base, option, transform)
}
}

extension RecursiveMapSequence {

/// Traversal option. This option affects the element order of the output sequence.
public enum TraversalOption {

/// The algorithm will go down first and produce the resulting path.
case depthFirst

/// The algorithm will go through the previous sequence first and chaining all the occurring sequences.
case breadthFirst

}

public struct Iterator: IteratorProtocol {

@usableFromInline
var base: Base.Iterator?

@usableFromInline
let option: TraversalOption

@usableFromInline
var mapped: ArraySlice<Transformed.Iterator> = []

@usableFromInline
var mapped_iterator: Transformed.Iterator?

@usableFromInline
let transform: (Base.Element) -> Transformed

@inlinable
init(
_ base: Base,
_ option: TraversalOption,
_ transform: @escaping (Base.Element) -> Transformed
) {
self.base = base.makeIterator()
self.option = option
self.transform = transform
}

@inlinable
public mutating func next() -> Base.Element? {

switch option {

case .depthFirst:

while self.mapped_iterator != nil {

if let element = self.mapped_iterator!.next() {
mapped.append(self.mapped_iterator!)
self.mapped_iterator = transform(element).makeIterator()
return element
}

self.mapped_iterator = mapped.popLast()
}

if self.base != nil {

if let element = self.base!.next() {
self.mapped_iterator = transform(element).makeIterator()
return element
}

self.base = nil
}

return nil

case .breadthFirst:

if self.base != nil {

if let element = self.base!.next() {
mapped.append(transform(element).makeIterator())
return element
}

self.base = nil
self.mapped_iterator = mapped.popFirst()
}

while self.mapped_iterator != nil {

if let element = self.mapped_iterator!.next() {
mapped.append(transform(element).makeIterator())
return element
}

self.mapped_iterator = mapped.popFirst()
}

return nil
}
}
}
}

extension RecursiveMapSequence: LazySequenceProtocol where Base: LazySequenceProtocol { }
Loading