diff --git a/README.md b/README.md index 7fab861..a7bba60 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ _SwiftSimpleTree_ is part of the [OpenAlloc](https://github.com/openalloc) famil ## SimpleTree ```swift -let foo = SimpleTree(value: "foo") +let foo = SimpleTree(value: "foo") let bar = foo.addChild(for: "bar") let baz = bar.addChild(for: "baz") @@ -24,6 +24,10 @@ print(baz.getParentValues()) print(foo.getChildValues()) => ["bar", "baz"] + +print(foo.getSelfAndChildValues()) + +=> ["foo", "bar", "baz"] ``` ## Types @@ -34,6 +38,15 @@ Types scoped within `SimpleTree`: - `typealias ValueSet = Set` - a set of values, where `T` is your hashable type. +An enumeration is used for the `traversal` argument in the `getChildren()` methods: + +```swift +public enum Traversal { + case depthFirst + case breadthFirst +} +``` + ## Instance Methods #### Tree Maintenance @@ -44,15 +57,13 @@ Types scoped within `SimpleTree`: #### Node Retrieval -- `func getChildren(excludeValues: ValueSet) -> [Node]`: Fetch the child nodes of the node. Optional list of values for children to be excluded, along with their progeny. Traversal is breadth-first. +- `func getChildren(traversal: Traversal, maxDepth: UInt, excludeValues: ValueSet) -> [Node]`: Fetch the child nodes of the node. Optional list of values for children to be excluded, along with their progeny. Traversal is `.breadthFirst` by default. NOTE: breadth-first with `maxDepth` not yet supported. -- `func getChildren(maxDepth: Int, excludeValues: ValueSet) -> [Node]`: Fetch the child nodes of the node. Optional list of values for children to be excluded, along with their progeny. Traversal is depth-first. +- `func getSelfAndChildren(traversal: Traversal, maxDepth: UInt, excludeValues: ValueSet) -> [Node]`: Fetch the node and its child nodes. Optional list of values for nodes to be excluded, along with their progeny. Traversal is `.breadthFirst` by default. Self is at the first level of depth, so `maxDepth: 0` returns `[]`. NOTE: breadth-first with `maxDepth` not yet supported. - `func getParent(excludeValues: ValueSet) -> Node?`: Return the immediate parent node, if any. Optional list of parent values to be excluded. A match will cause this function to return nil. -- `func getParents(maxDepth: Int, excludeValues: ValueSet) -> [Node]`: Return the parent nodes, starting with immediate parent. Optional list of parent values to be excluded. A match will exclude further ancestors. Optional limit on depth. - -- `func getSelfAndChildren(excludeValues: ValueSet) -> [Node]`: Fetch the node and its child nodes. Optional list of values for nodes to be excluded, along with their progeny. Traversal is breadth-first. +- `func getParents(maxDepth: UInt, excludeValues: ValueSet) -> [Node]`: Return the parent nodes, starting with immediate parent. Optional list of parent values to be excluded. A match will exclude further ancestors. Optional limit on depth. #### Node Search @@ -68,15 +79,14 @@ Types scoped within `SimpleTree`: #### Value retrieval -- `func getChildValues(excludeValues: ValueSet) -> [T]`: Fetch the values of the child nodes. Optional list of values for children to be excluded, along with their progeny. Traversal is breadth-first. +- `func getChildValues(traversal: Traversal, maxDepth: UInt, excludeValues: ValueSet) -> [T]`: Fetch the values of the child nodes. Optional list of values for children to be excluded, along with their progeny. Traversal is `.breadthFirst` by default. NOTE: breadth-first with `maxDepth` not yet supported. -- `func getChildValues(maxDepth: Int, excludeValues: ValueSet) -> [T]`: Fetch the values of the child nodes. Optional list of values for children to be excluded, along with their progeny. Traversal is depth-first. +- `func getSelfAndChildValues(excludeValues: ValueSet) -> [T]`: Fetch values for the node and its child nodes. Includes value of current node. Optional list of values for nodes to be excluded, along with their progeny. Self is at the first level of depth, so `maxDepth: 0` returns `[]`. Traversal is breadth-first, by default. NOTE: breadth-first with `maxDepth` not yet supported. - `func getParentValue(excludeValues: ValueSet) -> T?`: Return the value of the immediate parent node, if any. Optional list of parent values to be excluded. A match will cause this function to return nil. -- `func getParentValues(maxDepth: Int, excludeValues: ValueSet) -> [T]`: Return the values of the parent nodes, starting with immediate parent. Optional list of parent values to be excluded. A match will exclude further ancestors. Optional limit on depth. +- `func getParentValues(maxDepth: UInt, excludeValues: ValueSet) -> [T]`: Return the values of the parent nodes, starting with immediate parent. Optional list of parent values to be excluded. A match will exclude further ancestors. Optional limit on depth. -- `func getSelfAndChildValues(excludeValues: ValueSet) -> [T]`: Fetch values for the node and its child nodes. Includes value of current node. Optional list of values for nodes to be excluded, along with their progeny. Traversal is breadth-first. ## See Also diff --git a/Sources/SimpleTree.swift b/Sources/SimpleTree.swift index 785efee..4467246 100644 --- a/Sources/SimpleTree.swift +++ b/Sources/SimpleTree.swift @@ -51,10 +51,15 @@ extension SimpleTree { extension SimpleTree { + public enum Traversal { + case depthFirst + case breadthFirst + } + /// Return the parent nodes, starting with immediate parent. /// Optional list of parent values to be excluded. A match will exclude further ancestors. /// Optional limit on depth. - public func getParents(maxDepth: Int = Int.max, excludeValues: ValueSet = ValueSet()) -> [Node] { + public func getParents(maxDepth: UInt = UInt.max, excludeValues: ValueSet = ValueSet()) -> [Node] { guard maxDepth > 0 else { return [] } let iter = self.makeParentIterator() var depth = maxDepth @@ -62,8 +67,8 @@ extension SimpleTree { while let parentNode = iter.next() { if excludeValues.contains(parentNode.value) { break } parents.append(parentNode) + if depth == 1 { break } depth -= 1 - if depth == 0 { break } } return parents } @@ -76,39 +81,42 @@ extension SimpleTree { /// Fetch the child nodes of the node. /// Optional list of values for children to be excluded, along with their progeny. - /// Traversal is depth-first. - public func getChildren(maxDepth: Int, excludeValues: ValueSet = ValueSet()) -> [Node] { - guard maxDepth > 0 else { return [] } + /// Traversal is breadth-first by default. + /// NOTE: breadth-first with maxDepth not yet supported. + public func getChildren(traversal: Traversal = .breadthFirst, maxDepth: UInt = UInt.max, excludeValues: ValueSet = ValueSet()) -> [Node] { var nodes = [Node]() - for child in children { - if excludeValues.contains(child.value) { continue } - nodes.append(child) - if maxDepth > 1 { - nodes.append(contentsOf: child.getChildren(maxDepth: maxDepth - 1, excludeValues: excludeValues)) + switch traversal { + case .depthFirst: + if maxDepth > 0 { + for child in children { + if excludeValues.contains(child.value) { continue } + nodes.append(child) + if maxDepth > 1 { + let _children = child.getChildren(traversal: .depthFirst, maxDepth: maxDepth - 1, excludeValues: excludeValues) + nodes.append(contentsOf: _children) + } + } + } + case .breadthFirst: + guard maxDepth == UInt.max else { fatalError("breadth-first with maxDepth not yet supported") } + let iter = self.makeChildIterator(excludeValues: excludeValues) + while let node = iter.next() { + nodes.append(node) } - } - return nodes - } - - /// Fetch the child nodes of the node. - /// Optional list of values for children to be excluded, along with their progeny. - /// Traversal is breadth-first. - public func getChildren(excludeValues: ValueSet = ValueSet()) -> [Node] { - var nodes = [Node]() - let iter = self.makeChildIterator(excludeValues: excludeValues) - while let node = iter.next() { - nodes.append(node) } return nodes } /// Fetch the node and its child nodes. /// Optional list of values for nodes to be excluded, along with their progeny. - /// Traversal is breadth-first. - public func getSelfAndChildren(excludeValues: ValueSet = ValueSet()) -> [Node] { - guard !excludeValues.contains(self.value) else { return [] } + /// Traversal is breadth-first by default. + /// Self is at the first level of depth, so maxDepth: 0 returns []. + /// NOTE: breadth-first with maxDepth not yet supported. + public func getSelfAndChildren(traversal: Traversal = .breadthFirst, maxDepth: UInt = UInt.max, excludeValues: ValueSet = ValueSet()) -> [Node] { + guard !excludeValues.contains(self.value), maxDepth > 0 else { return [] } var nodes: [Node] = [self] - nodes.append(contentsOf: getChildren(excludeValues: excludeValues)) + let netMaxDepth = maxDepth - (traversal == .depthFirst ? 1 : 0) + nodes.append(contentsOf: getChildren(traversal: traversal, maxDepth: netMaxDepth, excludeValues: excludeValues)) return nodes } } @@ -195,10 +203,28 @@ extension SimpleTree { extension SimpleTree { + /// Fetch the values of the child nodes. + /// Optional list of values for children to be excluded, along with their progeny. + /// Traversal is breadth-first by default. + /// NOTE: breadth-first with maxDepth not yet supported. + public func getChildValues(traversal: Traversal = .breadthFirst, maxDepth: UInt = UInt.max, excludeValues: ValueSet = ValueSet()) -> [T] { + getChildren(traversal: traversal, maxDepth: maxDepth, excludeValues: excludeValues).map(\.value) + } + + /// Fetch values for the node and its child nodes. + /// Includes value of current node. + /// Optional list of values for nodes to be excluded, along with their progeny. + /// Self is at the first level of depth, so maxDepth: 0 returns []. + /// Traversal is breadth-first, by default. + /// NOTE: breadth-first with maxDepth not yet supported. + public func getSelfAndChildValues(traversal: Traversal = .breadthFirst, maxDepth: UInt = UInt.max, excludeValues: ValueSet = ValueSet()) -> [T] { + getSelfAndChildren(traversal: traversal, maxDepth: maxDepth, excludeValues: excludeValues).map(\.value) + } + /// Return the values of the parent nodes, starting with immediate parent. /// Optional list of parent values to be excluded. A match will exclude further ancestors. /// Optional limit on depth. - public func getParentValues(maxDepth: Int = Int.max, excludeValues: ValueSet = ValueSet()) -> [T] { + public func getParentValues(maxDepth: UInt = UInt.max, excludeValues: ValueSet = ValueSet()) -> [T] { getParents(maxDepth: maxDepth, excludeValues: excludeValues).map(\.value) } @@ -207,25 +233,4 @@ extension SimpleTree { public func getParentValue(excludeValues: ValueSet = ValueSet()) -> T? { getParent(excludeValues: excludeValues)?.value } - - /// Fetch the values of the child nodes. - /// Optional list of values for children to be excluded, along with their progeny. - /// Traversal is depth-first. - public func getChildValues(maxDepth: Int, excludeValues: ValueSet = ValueSet()) -> [T] { - getChildren(maxDepth: maxDepth, excludeValues: excludeValues).map(\.value) - } - - /// Fetch the values of the child nodes. - /// Optional list of values for children to be excluded, along with their progeny. - /// Traversal is breadth-first. - public func getChildValues(excludeValues: ValueSet = ValueSet()) -> [T] { - getChildren(excludeValues: excludeValues).map(\.value) - } - - /// Fetch values for the node and its child nodes. - /// Optional list of values for nodes to be excluded, along with their progeny. - /// Traversal is breadth-first. - public func getSelfAndChildValues(excludeValues: ValueSet = ValueSet()) -> [T] { - getSelfAndChildren(excludeValues: excludeValues).map(\.value) - } } diff --git a/Tests/SimpleTreeTests.swift b/Tests/SimpleTreeTests.swift index 5b5dfcd..844903e 100644 --- a/Tests/SimpleTreeTests.swift +++ b/Tests/SimpleTreeTests.swift @@ -16,10 +16,9 @@ // limitations under the License. // +@testable import SimpleTree import XCTest -import SimpleTree - /// Shallow equator, exclusively for purposes of testing extension SimpleTree: Equatable { public static func == (lhs: SimpleTree.Node, rhs: SimpleTree.Node) -> Bool { @@ -27,10 +26,23 @@ extension SimpleTree: Equatable { } } -class SimpleTreeTests: XCTestCase { +final class SimpleTreeTests: XCTestCase { + typealias SST = SimpleTree + + public func testExample() { + let foo = SimpleTree(value: "foo") + let bar = foo.addChild(for: "bar") + let baz = bar.addChild(for: "baz") + + XCTAssertEqual("baz", foo.getFirst(for: "baz")?.value) + XCTAssertEqual(["bar", "foo"], baz.getParentValues()) + XCTAssertEqual(["bar", "baz"], foo.getChildValues()) + XCTAssertEqual(["foo", "bar", "baz"], foo.getSelfAndChildValues()) + } + public func testGetParentValues() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let baz2 = foo.addChild(for: "bar2") let baz = bar.addChild(for: "baz") @@ -56,7 +68,7 @@ class SimpleTreeTests: XCTestCase { } public func testMakeParentIterator() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let baz2 = foo.addChild(for: "baz2") let baz = bar.addChild(for: "baz") @@ -85,7 +97,7 @@ class SimpleTreeTests: XCTestCase { } public func testMakeChildIterator() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let baz2 = foo.addChild(for: "baz2") let bleep = baz2.addChild(for: "bleep") @@ -127,7 +139,7 @@ class SimpleTreeTests: XCTestCase { } public func testGetFirst() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let baz = bar.addChild(for: "baz") @@ -143,25 +155,25 @@ class SimpleTreeTests: XCTestCase { } public func testGetChildValuesNone() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") XCTAssertEqual(foo.getChildValues(), []) } public func testGetChildValuesOneChild() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") _ = foo.addChild(for: "bar") XCTAssertEqual(foo.getChildValues(), ["bar"]) } public func testGetChildValuesOneGrandChild() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") _ = bar.addChild(for: "baz") XCTAssertEqual(foo.getChildValues(), ["bar", "baz"]) } public func testGetChildValues() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let bar2 = foo.addChild(for: "bar2") _ = bar2.addChild(for: "bar3") @@ -169,21 +181,47 @@ class SimpleTreeTests: XCTestCase { let blah = bar.addChild(for: "blah") _ = blah.addChild(for: "bleh") - XCTAssertEqual(foo.getChildValues().sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh"].sorted()) - XCTAssertEqual(bar.getChildValues().sorted(), ["baz", "blah", "bleh"].sorted()) - XCTAssertEqual(bar2.getChildValues(), ["bar3"]) - XCTAssertEqual(baz.getChildValues(), []) - XCTAssertEqual(blah.getChildValues(), ["bleh"]) + for traversal in [SST.Traversal.depthFirst, SST.Traversal.breadthFirst] { + XCTAssertEqual(foo.getChildValues(traversal: traversal).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh"].sorted()) + XCTAssertEqual(bar.getChildValues(traversal: traversal).sorted(), ["baz", "blah", "bleh"].sorted()) + XCTAssertEqual(bar2.getChildValues(traversal: traversal), ["bar3"]) + XCTAssertEqual(baz.getChildValues(traversal: traversal), []) + XCTAssertEqual(blah.getChildValues(traversal: traversal), ["bleh"]) + } + + XCTAssertEqual(foo.getChildValues(traversal: .depthFirst, maxDepth: 1000).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh"].sorted()) + XCTAssertEqual(foo.getChildValues(traversal: .depthFirst, maxDepth: 3).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh"].sorted()) + XCTAssertEqual(foo.getChildValues(traversal: .depthFirst, maxDepth: 2).sorted(), ["bar", "bar2", "bar3", "baz", "blah"].sorted()) + XCTAssertEqual(foo.getChildValues(traversal: .depthFirst, maxDepth: 1).sorted(), ["bar", "bar2"].sorted()) + XCTAssertEqual(foo.getChildValues(traversal: .depthFirst, maxDepth: 0).sorted(), [].sorted()) + } + + public func testGetSelfAndChildValues() throws { + let foo = SST(value: "foo") + let bar = foo.addChild(for: "bar") + let bar2 = foo.addChild(for: "bar2") + _ = bar2.addChild(for: "bar3") + let baz = bar.addChild(for: "baz") + let blah = bar.addChild(for: "blah") + _ = blah.addChild(for: "bleh") - XCTAssertEqual(foo.getChildValues(maxDepth: 1000).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh"].sorted()) - XCTAssertEqual(foo.getChildValues(maxDepth: 3).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh"].sorted()) - XCTAssertEqual(foo.getChildValues(maxDepth: 2).sorted(), ["bar", "bar2", "bar3", "baz", "blah"].sorted()) - XCTAssertEqual(foo.getChildValues(maxDepth: 1).sorted(), ["bar", "bar2"].sorted()) - XCTAssertEqual(foo.getChildValues(maxDepth: 0).sorted(), [].sorted()) + for traversal in [SST.Traversal.depthFirst, SST.Traversal.breadthFirst] { + XCTAssertEqual(foo.getSelfAndChildValues(traversal: traversal).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh", "foo"].sorted()) + XCTAssertEqual(bar.getSelfAndChildValues(traversal: traversal).sorted(), ["bar", "baz", "blah", "bleh"].sorted()) + XCTAssertEqual(bar2.getSelfAndChildValues(traversal: traversal), ["bar2", "bar3"]) + XCTAssertEqual(baz.getSelfAndChildValues(traversal: traversal), ["baz"]) + XCTAssertEqual(blah.getSelfAndChildValues(traversal: traversal), ["blah", "bleh"]) + } + + XCTAssertEqual(foo.getSelfAndChildValues(traversal: .depthFirst, maxDepth: 1000).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "bleh", "foo"].sorted()) + XCTAssertEqual(foo.getSelfAndChildValues(traversal: .depthFirst, maxDepth: 3).sorted(), ["bar", "bar2", "bar3", "baz", "blah", "foo"].sorted()) + XCTAssertEqual(foo.getSelfAndChildValues(traversal: .depthFirst, maxDepth: 2).sorted(), ["bar", "bar2", "foo"].sorted()) + XCTAssertEqual(foo.getSelfAndChildValues(traversal: .depthFirst, maxDepth: 1).sorted(), ["foo"].sorted()) + XCTAssertEqual(foo.getSelfAndChildValues(traversal: .depthFirst, maxDepth: 0).sorted(), [].sorted()) } public func testGetAllChildValues() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let bar2 = foo.addChild(for: "bar2") _ = bar2.addChild(for: "bar3") @@ -199,7 +237,7 @@ class SimpleTreeTests: XCTestCase { } public func testGetChildValuesExclude() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let bar2 = foo.addChild(for: "bar2") _ = bar2.addChild(for: "bar3") @@ -213,7 +251,7 @@ class SimpleTreeTests: XCTestCase { } public func testGetChildValues2Exclude() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let bar2 = foo.addChild(for: "bar2") _ = bar2.addChild(for: "bar3") @@ -228,7 +266,7 @@ class SimpleTreeTests: XCTestCase { } public func testGetAllValues() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") let baz = bar.addChild(for: "baz") @@ -249,7 +287,7 @@ class SimpleTreeTests: XCTestCase { } public func testGetAllValuesExcludeRoot() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") _ = foo.addChild(for: "bar") let actual = foo.getSelfAndChildValues(excludeValues: Set(["foo"])) @@ -258,7 +296,7 @@ class SimpleTreeTests: XCTestCase { } public func testGetAllValuesExcludeChild() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let bar = foo.addChild(for: "bar") _ = bar.addChild(for: "baz") @@ -268,7 +306,7 @@ class SimpleTreeTests: XCTestCase { } public func testGetAllValuesExcludeSibling() throws { - let foo = SimpleTree(value: "foo") + let foo = SST(value: "foo") let blah = foo.addChild(for: "blah") _ = blah.addChild(for: "bleh")