From 5d470477b887cdabb148ad9552652fa217f530e1 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 00:33:43 +0800 Subject: [PATCH 01/17] Create RecursiveMapSequence.swift --- Sources/Algorithms/RecursiveMapSequence.swift | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 Sources/Algorithms/RecursiveMapSequence.swift diff --git a/Sources/Algorithms/RecursiveMapSequence.swift b/Sources/Algorithms/RecursiveMapSequence.swift new file mode 100644 index 00000000..d4a90972 --- /dev/null +++ b/Sources/Algorithms/RecursiveMapSequence.swift @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 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 { + + @inlinable + public func recursiveMap(_ transform: @escaping (Element) -> S) -> RecursiveMapSequence { + return RecursiveMapSequence(self, transform) + } +} + +@frozen +public struct RecursiveMapSequence: Sequence where Base.Element == Transformed.Element { + + @usableFromInline + let base: Base + + @usableFromInline + let transform: (Base.Element) -> Transformed + + @inlinable + init(_ base: Base, _ transform: @escaping (Base.Element) -> Transformed) { + self.base = base + self.transform = transform + } + + @inlinable + public func makeIterator() -> Iterator { + return Iterator(base, transform) + } +} + +extension RecursiveMapSequence { + + @frozen + public struct Iterator: IteratorProtocol { + + @usableFromInline + var base: Base.Iterator? + + @usableFromInline + var mapped: ArraySlice = [] + + @usableFromInline + var mapped_iterator: Transformed.Iterator? + + @usableFromInline + var transform: (Base.Element) -> Transformed + + @inlinable + init(_ base: Base, _ transform: @escaping (Base.Element) -> Transformed) { + self.base = base.makeIterator() + self.transform = transform + } + + @inlinable + public mutating func next() -> Base.Element? { + + if self.base != nil { + + if let element = self.base?.next() { + mapped.append(transform(element)) + return element + } + + self.base = nil + self.mapped_iterator = mapped.popFirst()?.makeIterator() + } + + while self.mapped_iterator != nil { + + if let element = self.mapped_iterator?.next() { + mapped.append(transform(element)) + return element + } + + self.mapped_iterator = mapped.popFirst()?.makeIterator() + } + + return nil + } + } +} + +extension RecursiveMapSequence: LazySequenceProtocol where Base: LazySequenceProtocol { } From 6bc12212344f95c36a8c42f0d30dd7340b45585f Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 00:37:28 +0800 Subject: [PATCH 02/17] update license --- Sources/Algorithms/RecursiveMapSequence.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Algorithms/RecursiveMapSequence.swift b/Sources/Algorithms/RecursiveMapSequence.swift index d4a90972..b81b0544 100644 --- a/Sources/Algorithms/RecursiveMapSequence.swift +++ b/Sources/Algorithms/RecursiveMapSequence.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Algorithms open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// 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 From 9103e5de7c9618fcb40e80f74f43ee5eee39f63e Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 00:56:40 +0800 Subject: [PATCH 03/17] rename file --- .../Algorithms/{RecursiveMapSequence.swift => RecursiveMap.swift} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/Algorithms/{RecursiveMapSequence.swift => RecursiveMap.swift} (100%) diff --git a/Sources/Algorithms/RecursiveMapSequence.swift b/Sources/Algorithms/RecursiveMap.swift similarity index 100% rename from Sources/Algorithms/RecursiveMapSequence.swift rename to Sources/Algorithms/RecursiveMap.swift From 0de33f1e163a5c2e4b52897d1842487dc7e29bfa Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 01:06:59 +0800 Subject: [PATCH 04/17] Create RecursiveMapTests.swift --- .../RecursiveMapTests.swift | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift diff --git a/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift new file mode 100644 index 00000000..2ef7204d --- /dev/null +++ b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Algorithms + +final class RecursiveMapTests: XCTestCase { + + struct Dir: Hashable { + + var id: UUID = UUID() + + var parent: UUID? + + var name: String + + } + + struct Path: Hashable { + + var id: UUID + + var path: String + + } + + func testRecursiveMap() { + + 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 result = list.lazy.compactMap { $0.parent == nil ? Path(id: $0.id, path: "/\($0.name)") : nil } + .recursiveMap { parent in list.lazy.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } + + XCTAssert(result.elementsEqual(answer)) + } + +} From 821415fbbac810445f6be57a56ccb51065867176 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:15:57 +0800 Subject: [PATCH 05/17] add documentation --- Sources/Algorithms/RecursiveMap.swift | 37 ++++++++++- .../RecursiveMapTests.swift | 61 +++++++++++++------ 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/Sources/Algorithms/RecursiveMap.swift b/Sources/Algorithms/RecursiveMap.swift index b81b0544..e4cdd8c6 100644 --- a/Sources/Algorithms/RecursiveMap.swift +++ b/Sources/Algorithms/RecursiveMap.swift @@ -10,14 +10,46 @@ //===----------------------------------------------------------------------===// extension Sequence { - + /// 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 view in tree.recursiveMap({ $0.children }) { + /// 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 + /// elements. + /// + /// - Complexity: O(1) @inlinable public func recursiveMap(_ transform: @escaping (Element) -> S) -> RecursiveMapSequence { return RecursiveMapSequence(self, transform) } } -@frozen public struct RecursiveMapSequence: Sequence where Base.Element == Transformed.Element { @usableFromInline @@ -40,7 +72,6 @@ public struct RecursiveMapSequence: Seque extension RecursiveMapSequence { - @frozen public struct Iterator: IteratorProtocol { @usableFromInline diff --git a/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift index 2ef7204d..21899dbe 100644 --- a/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift +++ b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift @@ -14,25 +14,25 @@ import Algorithms final class RecursiveMapTests: XCTestCase { - struct Dir: Hashable { - - var id: UUID = UUID() - - var parent: UUID? - - var name: String - - } - - struct Path: Hashable { - - var id: UUID + func testRecursiveMap() { - var path: String + struct Dir: Hashable { + + var id: UUID = UUID() + + var parent: UUID? + + var name: String + + } - } - - func testRecursiveMap() { + struct Path: Hashable { + + var id: UUID + + var path: String + + } var list: [Dir] = [] list.append(Dir(name: "root")) @@ -54,7 +54,32 @@ final class RecursiveMapTests: XCTestCase { let result = list.lazy.compactMap { $0.parent == nil ? Path(id: $0.id, path: "/\($0.name)") : nil } .recursiveMap { parent in list.lazy.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } - XCTAssert(result.elementsEqual(answer)) + XCTAssertEqualSequences(result, answer) + } + + func testRecursiveMap2() { + + 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), + ] + + let views = tree.recursiveMap { $0.children } + + XCTAssertEqualSequences(views.map { $0.id }, 1...6) } } From d3702aeeff03cddc904cf2b1d1059680c9c0d577 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:21:49 +0800 Subject: [PATCH 06/17] Update RecursiveMap.swift --- Sources/Algorithms/RecursiveMap.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Algorithms/RecursiveMap.swift b/Sources/Algorithms/RecursiveMap.swift index e4cdd8c6..f1d36304 100644 --- a/Sources/Algorithms/RecursiveMap.swift +++ b/Sources/Algorithms/RecursiveMap.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// extension Sequence { - /// Returns a sequence containing the original sequence followed by recursive mapped sequence + /// Returns a sequence containing the original sequence followed by recursive mapped sequence. /// /// ``` ///struct View { @@ -40,8 +40,7 @@ extension Sequence { /// /// - Parameters: /// - transform: A closure that map the element to new sequence. - /// - Returns: A sequence of the original sequence followed by recursive mapped sequence - /// elements. + /// - Returns: A sequence of the original sequence followed by recursive mapped sequence. /// /// - Complexity: O(1) @inlinable From c64c3d5c389cde96744960db73dc2ff867e40544 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:27:07 +0800 Subject: [PATCH 07/17] add Guides --- Guides/RecursiveMap.md | 48 +++++++++++++++++++++++++++ Sources/Algorithms/RecursiveMap.swift | 46 ++++++++++++------------- 2 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 Guides/RecursiveMap.md diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md new file mode 100644 index 00000000..a24d7473 --- /dev/null +++ b/Guides/RecursiveMap.md @@ -0,0 +1,48 @@ +# 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)] + +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 view in tree.recursiveMap({ $0.children }) { + print(view.id) +} +// 1 +// 2 +// 3 +// 4 +// 5 +// 6 +``` + +## Detailed Design + +The `recursiveMap()` method is declared as `Sequence` extensions, and return `RecursiveMapSequence` instance: + +```swift +extension Sequence { + public func recursiveMap( + _ transform: @escaping (Element) -> S + ) -> RecursiveMapSequence +} +``` + +### Complexity + +Calling this method is O(_1_). diff --git a/Sources/Algorithms/RecursiveMap.swift b/Sources/Algorithms/RecursiveMap.swift index f1d36304..a13f4ed4 100644 --- a/Sources/Algorithms/RecursiveMap.swift +++ b/Sources/Algorithms/RecursiveMap.swift @@ -13,29 +13,29 @@ extension Sequence { /// 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 view in tree.recursiveMap({ $0.children }) { - /// print(view.id) - ///} - ///// 1 - ///// 2 - ///// 3 - ///// 4 - ///// 5 - ///// 6 + /// 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 view in tree.recursiveMap({ $0.children }) { + /// print(view.id) + /// } + /// // 1 + /// // 2 + /// // 3 + /// // 4 + /// // 5 + /// // 6 /// ``` /// /// - Parameters: From 0a48fdb58d3ba517491d3a4eb5d3623b5eed3e30 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:31:25 +0800 Subject: [PATCH 08/17] update guides --- Guides/RecursiveMap.md | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index a24d7473..cb235ba8 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -33,7 +33,7 @@ for view in tree.recursiveMap({ $0.children }) { ## Detailed Design -The `recursiveMap()` method is declared as `Sequence` extensions, and return `RecursiveMapSequence` instance: +The `recursiveMap(_:)` method is declared as `Sequence` extensions, and return `RecursiveMapSequence` instance: ```swift extension Sequence { diff --git a/README.md b/README.md index 270055fb..4576fa73 100644 --- a/README.md +++ b/README.md @@ -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(_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/RecursiveMap.md): Produces a sequence containing the original sequence followed by recursive mapped sequence. - [`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. From 3fa547e2ab35e870978b607407cf87d59f0f3642 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 11:51:35 +0800 Subject: [PATCH 09/17] rename --- Guides/RecursiveMap.md | 20 +++++++++---------- Sources/Algorithms/RecursiveMap.swift | 20 +++++++++---------- .../RecursiveMapTests.swift | 20 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index cb235ba8..34834dd3 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -6,22 +6,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 view in tree.recursiveMap({ $0.children }) { - print(view.id) +for node in tree.recursiveMap({ $0.children }) { + print(node.id) } // 1 // 2 diff --git a/Sources/Algorithms/RecursiveMap.swift b/Sources/Algorithms/RecursiveMap.swift index a13f4ed4..eb0259dc 100644 --- a/Sources/Algorithms/RecursiveMap.swift +++ b/Sources/Algorithms/RecursiveMap.swift @@ -13,22 +13,22 @@ extension Sequence { /// 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 view in tree.recursiveMap({ $0.children }) { - /// print(view.id) + /// for node in tree.recursiveMap({ $0.children }) { + /// print(node.id) /// } /// // 1 /// // 2 diff --git a/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift index 21899dbe..e0e056db 100644 --- a/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift +++ b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift @@ -59,27 +59,27 @@ final class RecursiveMapTests: XCTestCase { func testRecursiveMap2() { - 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), ] - let views = tree.recursiveMap { $0.children } + let nodes = tree.recursiveMap { $0.children } - XCTAssertEqualSequences(views.map { $0.id }, 1...6) + XCTAssertEqualSequences(nodes.map { $0.id }, 1...6) } } From 5ffa07d050e47b2c9dffa260c94e5edff9dc68cf Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 12:02:44 +0800 Subject: [PATCH 10/17] Update CHANGELOG.md --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b070cf5a..5a0f34af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,11 @@ This project follows semantic versioning. ## [Unreleased] -*No new changes.* +### Additions + + One new addition to the list of algorithms: + + - `recursiveMap(_:)` Produces a sequence containing the original sequence followed by recursive mapped sequence. ([#185]) --- @@ -308,6 +312,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 From 47a750c26e05ec0a68781badd2a70f70110dc850 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 15:06:23 +0800 Subject: [PATCH 11/17] try add TraversalOption --- Sources/Algorithms/RecursiveMap.swift | 108 ++++++++++++++---- .../RecursiveMapTests.swift | 29 ++++- 2 files changed, 110 insertions(+), 27 deletions(-) diff --git a/Sources/Algorithms/RecursiveMap.swift b/Sources/Algorithms/RecursiveMap.swift index eb0259dc..d57b9b09 100644 --- a/Sources/Algorithms/RecursiveMap.swift +++ b/Sources/Algorithms/RecursiveMap.swift @@ -19,13 +19,13 @@ extension Sequence { /// } /// 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 node in tree.recursiveMap({ $0.children }) { /// print(node.id) @@ -39,13 +39,17 @@ extension Sequence { /// ``` /// /// - 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. /// /// - Complexity: O(1) @inlinable - public func recursiveMap(_ transform: @escaping (Element) -> S) -> RecursiveMapSequence { - return RecursiveMapSequence(self, transform) + public func recursiveMap( + option: RecursiveMapSequence.TraversalOption = .depthFirst, + _ transform: @escaping (Element) -> S + ) -> RecursiveMapSequence { + return RecursiveMapSequence(self, option, transform) } } @@ -54,30 +58,49 @@ public struct RecursiveMapSequence: Seque @usableFromInline let base: Base + @usableFromInline + let option: TraversalOption + @usableFromInline let transform: (Base.Element) -> Transformed @inlinable - init(_ base: Base, _ transform: @escaping (Base.Element) -> Transformed) { + 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, transform) + return Iterator(base, option, transform) } } extension RecursiveMapSequence { + public enum TraversalOption { + + case depthFirst + + case breadthFirst + + } + public struct Iterator: IteratorProtocol { @usableFromInline var base: Base.Iterator? @usableFromInline - var mapped: ArraySlice = [] + let option: TraversalOption + + @usableFromInline + var mapped: ArraySlice = [] @usableFromInline var mapped_iterator: Transformed.Iterator? @@ -86,36 +109,71 @@ extension RecursiveMapSequence { var transform: (Base.Element) -> Transformed @inlinable - init(_ base: Base, _ transform: @escaping (Base.Element) -> Transformed) { + 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? { - if self.base != nil { + switch option { - if let element = self.base?.next() { - mapped.append(transform(element)) - return element + 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() } - self.base = nil - self.mapped_iterator = mapped.popFirst()?.makeIterator() - } - - while self.mapped_iterator != nil { + if self.base != nil { + + if let element = self.base!.next() { + self.mapped_iterator = transform(element).makeIterator() + return element + } + + self.base = nil + } + + return nil - if let element = self.mapped_iterator?.next() { - mapped.append(transform(element)) - return element + 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() } - self.mapped_iterator = mapped.popFirst()?.makeIterator() + return nil } - - return nil } } } diff --git a/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift index e0e056db..853ea699 100644 --- a/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift +++ b/Tests/SwiftAlgorithmsTests/RecursiveMapTests.swift @@ -52,7 +52,7 @@ final class RecursiveMapTests: XCTestCase { ] let result = list.lazy.compactMap { $0.parent == nil ? Path(id: $0.id, path: "/\($0.name)") : nil } - .recursiveMap { parent in list.lazy.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } + .recursiveMap(option: .breadthFirst) { parent in list.lazy.compactMap { $0.parent == parent.id ? Path(id: $0.id, path: "\(parent.path)/\($0.name)") : nil } } XCTAssertEqualSequences(result, answer) } @@ -66,6 +66,31 @@ final class RecursiveMapTests: XCTestCase { 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), + ] + + let nodes = tree.recursiveMap { $0.children } // default depthFirst option + + XCTAssertEqualSequences(nodes.map { $0.id }, 1...6) + } + + func testRecursiveMap3() { + + struct Node { + + var id: Int + + var children: [Node] = [] + } + let tree = [ Node(id: 1, children: [ Node(id: 3), @@ -77,7 +102,7 @@ final class RecursiveMapTests: XCTestCase { Node(id: 2), ] - let nodes = tree.recursiveMap { $0.children } + let nodes = tree.recursiveMap(option: .breadthFirst) { $0.children } XCTAssertEqualSequences(nodes.map { $0.id }, 1...6) } From fc44b6430a94e8fc57d9f0a39871d9a950e40aaa Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 15:25:09 +0800 Subject: [PATCH 12/17] immutable --- Sources/Algorithms/RecursiveMap.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Algorithms/RecursiveMap.swift b/Sources/Algorithms/RecursiveMap.swift index d57b9b09..aedcbf7d 100644 --- a/Sources/Algorithms/RecursiveMap.swift +++ b/Sources/Algorithms/RecursiveMap.swift @@ -106,7 +106,7 @@ extension RecursiveMapSequence { var mapped_iterator: Transformed.Iterator? @usableFromInline - var transform: (Base.Element) -> Transformed + let transform: (Base.Element) -> Transformed @inlinable init( From 586edc8f3711c08a5581c0ad91ae0e7fcfea58bb Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 16:37:29 +0800 Subject: [PATCH 13/17] documentation --- CHANGELOG.md | 4 +++- Guides/RecursiveMap.md | 11 +++++++++-- README.md | 2 +- Sources/Algorithms/RecursiveMap.swift | 10 ++++++++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a0f34af..614e24db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ This project follows semantic versioning. One new addition to the list of algorithms: - - `recursiveMap(_:)` Produces a sequence containing the original sequence followed by recursive mapped sequence. ([#185]) + - `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]) --- diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 34834dd3..97fe941e 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -3,7 +3,9 @@ [[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)] -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 { @@ -31,13 +33,18 @@ for node in tree.recursiveMap({ $0.children }) { // 6 ``` +### Traversal Option + +This function comes with two different traversal methods. + ## Detailed Design -The `recursiveMap(_:)` method is declared as `Sequence` extensions, and return `RecursiveMapSequence` instance: +The `recursiveMap(option:_:)` method is declared as `Sequence` extensions, and return `RecursiveMapSequence` instance: ```swift extension Sequence { public func recursiveMap( + option: RecursiveMapSequence.TraversalOption = .depthFirst, _ transform: @escaping (Element) -> S ) -> RecursiveMapSequence } diff --git a/README.md b/README.md index 4576fa73..1c981376 100644 --- a/README.md +++ b/README.md @@ -48,7 +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(_:)`](https://github.com/apple/swift-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-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. diff --git a/Sources/Algorithms/RecursiveMap.swift b/Sources/Algorithms/RecursiveMap.swift index aedcbf7d..6082a238 100644 --- a/Sources/Algorithms/RecursiveMap.swift +++ b/Sources/Algorithms/RecursiveMap.swift @@ -10,7 +10,8 @@ //===----------------------------------------------------------------------===// extension Sequence { - /// 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 Sequence { /// ``` /// /// - 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. /// @@ -53,6 +54,8 @@ extension Sequence { } } +/// A sequence containing the original sequence and the recursive mapped sequence. +/// The order of ouput elements affects by the traversal option. public struct RecursiveMapSequence: Sequence where Base.Element == Transformed.Element { @usableFromInline @@ -83,10 +86,13 @@ public struct RecursiveMapSequence: Seque 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 } From b7a7a9f36f9bafeefc0114d9246648e70076f883 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 16:40:40 +0800 Subject: [PATCH 14/17] Update RecursiveMap.md --- Guides/RecursiveMap.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 97fe941e..2be2bbfc 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -35,7 +35,11 @@ for node in tree.recursiveMap({ $0.children }) { ### Traversal Option -This function comes with two different traversal methods. +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 From e6daee54aabd1769c293767fd56d3deb0d32316f Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 16:49:30 +0800 Subject: [PATCH 15/17] 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 2be2bbfc..16122c85 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -14,13 +14,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 node in tree.recursiveMap({ $0.children }) { print(node.id) @@ -37,7 +37,8 @@ for node in tree.recursiveMap({ $0.children }) { 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 fca4ba8f3ddf2bb0f8cca65ad01c2082d92ee4c3 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 16:52:46 +0800 Subject: [PATCH 16/17] Update RecursiveMap.md --- Guides/RecursiveMap.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index 16122c85..e9c685bf 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -39,9 +39,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 `Sequence` extensions, and return `RecursiveMapSequence` instance: From 38799f3b3edfa9f8cf759506e1a6bc2400fcac14 Mon Sep 17 00:00:00 2001 From: Susan Cheng Date: Mon, 28 Mar 2022 17:33:53 +0800 Subject: [PATCH 17/17] Update RecursiveMap.md --- Guides/RecursiveMap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Guides/RecursiveMap.md b/Guides/RecursiveMap.md index e9c685bf..ef9d5df2 100644 --- a/Guides/RecursiveMap.md +++ b/Guides/RecursiveMap.md @@ -55,6 +55,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. @@ -73,6 +75,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