├── .gitignore ├── Resources ├── tree.png └── tree~dark.png ├── Sources └── Tree │ ├── Documentation.docc │ ├── Resources │ │ ├── node.png │ │ ├── tree.png │ │ ├── node@2x.png │ │ ├── nodePrune.png │ │ ├── nodeRoot.png │ │ ├── node~dark.png │ │ ├── tree@2x.png │ │ ├── tree~dark.png │ │ ├── nodeAppend.png │ │ ├── nodeExample.png │ │ ├── nodeInsert.png │ │ ├── nodeIsLeaf.png │ │ ├── nodeIsRoot.png │ │ ├── nodeLeaves.png │ │ ├── nodeParent.png │ │ ├── nodeRemove.png │ │ ├── nodeRoot@2x.png │ │ ├── nodeAppend@2x.png │ │ ├── nodeChildren.png │ │ ├── nodeDepthFirst.png │ │ ├── nodeExample@2x.png │ │ ├── nodeInsert@2x.png │ │ ├── nodeIsLeaf@2x.png │ │ ├── nodeIsRoot@2x.png │ │ ├── nodeLeaves@2x.png │ │ ├── nodeParent@2x.png │ │ ├── nodePrune@2x.png │ │ ├── nodePruneRoot.png │ │ ├── nodePrune~dark.png │ │ ├── nodeRemove@2x.png │ │ ├── nodeRemoveRoot.png │ │ ├── nodeRoot~dark.png │ │ ├── node~dark@2x.png │ │ ├── tree~dark@2x.png │ │ ├── nodeAppend~dark.png │ │ ├── nodeBreadthFirst.png │ │ ├── nodeChildren@2x.png │ │ ├── nodeExample~dark.png │ │ ├── nodeInsert~dark.png │ │ ├── nodeIsLeaf~dark.png │ │ ├── nodeIsRoot~dark.png │ │ ├── nodeLeaves~dark.png │ │ ├── nodeParent~dark.png │ │ ├── nodePruneRoot@2x.png │ │ ├── nodeRemove~dark.png │ │ ├── nodeRoot~dark@2x.png │ │ ├── nodeAppend~dark@2x.png │ │ ├── nodeBreadthFirst@2x.png │ │ ├── nodeChildren~dark.png │ │ ├── nodeDepthFirst@2x.png │ │ ├── nodeDepthFirst~dark.png │ │ ├── nodeExample~dark@2x.png │ │ ├── nodeInsert~dark@2x.png │ │ ├── nodeIsLeaf~dark@2x.png │ │ ├── nodeIsRoot~dark@2x.png │ │ ├── nodeLeaves~dark@2x.png │ │ ├── nodeParent~dark@2x.png │ │ ├── nodePruneRoot~dark.png │ │ ├── nodePrune~dark@2x.png │ │ ├── nodeRemoveRoot@2x.png │ │ ├── nodeRemoveRoot~dark.png │ │ ├── nodeRemove~dark@2x.png │ │ ├── treeBuilderExample.png │ │ ├── nodeBreadthFirst~dark.png │ │ ├── nodeChildren~dark@2x.png │ │ ├── nodePruneRoot~dark@2x.png │ │ ├── treeBuilderExample@2x.png │ │ ├── nodeBreadthFirst~dark@2x.png │ │ ├── nodeDepthFirst~dark@2x.png │ │ ├── nodeRemoveRoot~dark@2x.png │ │ ├── treeBuilderExample~dark.png │ │ └── treeBuilderExample~dark@2x.png │ ├── Node+prune.md │ ├── Node+isLeaf.md │ ├── Node+parent.md │ ├── Node+chidren.md │ ├── Node+prune(index).md │ ├── Node+prune(identifier).md │ ├── Root.md │ ├── Branch.md │ ├── Node+appendChild(element).md │ ├── Node+isRoot.md │ ├── Node+remove(index).md │ ├── Node+remove(identifier).md │ ├── Node+insertChild(element-index).md │ ├── Node+appendChild(node).md │ ├── Node+remove.md │ ├── Node+insertChild(node-index).md │ ├── Node+leaves.md │ ├── Node+breadthFirst.md │ ├── Node+root.md │ ├── Node+depthFirst.md │ ├── Node+node(identifier).md │ ├── Node+contains(element).md │ ├── Node+node(element).md │ ├── Tree.md │ ├── TreeBuilder.md │ └── Node.md │ ├── NodeIterator.swift │ ├── TreeBuilder.swift │ └── Node.swift ├── LICENSE ├── Package.swift ├── .github └── workflows │ └── publish-documentation.yml ├── Tests └── TreeTests │ ├── Test.swift │ ├── NodeIteratorTests.swift │ ├── TreeBuilderTests.swift │ └── NodeTests.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.xcuserstate 3 | *.resolved 4 | .swiftpm 5 | -------------------------------------------------------------------------------- /Resources/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Resources/tree.png -------------------------------------------------------------------------------- /Resources/tree~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Resources/tree~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/node.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/tree.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/node@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/node@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePrune.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePrune.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRoot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRoot.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/node~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/node~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/tree@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/tree@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/tree~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/tree~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeAppend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeAppend.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeExample.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeInsert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeInsert.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsLeaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsLeaf.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsRoot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsRoot.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeLeaves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeLeaves.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeParent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeParent.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemove.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRoot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRoot@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeAppend@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeAppend@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeChildren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeChildren.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeDepthFirst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeDepthFirst.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeExample@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeExample@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeInsert@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeInsert@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsLeaf@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsLeaf@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsRoot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsRoot@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeLeaves@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeLeaves@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeParent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeParent@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePrune@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePrune@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePruneRoot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePruneRoot.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePrune~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePrune~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemove@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemove@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRoot~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRoot~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/node~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/node~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/tree~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/tree~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeAppend~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeAppend~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeChildren@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeChildren@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeExample~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeExample~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeInsert~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeInsert~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsLeaf~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsLeaf~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsRoot~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsRoot~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeLeaves~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeLeaves~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeParent~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeParent~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePruneRoot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePruneRoot@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemove~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemove~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRoot~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRoot~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeAppend~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeAppend~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeChildren~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeChildren~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeDepthFirst@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeDepthFirst@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeDepthFirst~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeDepthFirst~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeExample~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeExample~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeInsert~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeInsert~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsLeaf~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsLeaf~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeIsRoot~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeIsRoot~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeLeaves~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeLeaves~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeParent~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeParent~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePruneRoot~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePruneRoot~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePrune~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePrune~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemove~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemove~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/treeBuilderExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/treeBuilderExample.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeChildren~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeChildren~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodePruneRoot~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodePruneRoot~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/treeBuilderExample@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/treeBuilderExample@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeBreadthFirst~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeDepthFirst~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeDepthFirst~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/nodeRemoveRoot~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/treeBuilderExample~dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/treeBuilderExample~dark.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Resources/treeBuilderExample~dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattcox/Tree/HEAD/Sources/Tree/Documentation.docc/Resources/treeBuilderExample~dark@2x.png -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+prune.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/prune()`` 2 | 3 | ```swift 4 | // Prune the node from it's parent, creating a new tree. 5 | A.prune() 6 | ``` 7 | 8 | ![A pruned tree](nodePrune.png) 9 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+isLeaf.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/isLeaf`` 2 | 3 | A leaf node is a node with no child nodes. 4 | 5 | ![A tree structure indicating which nodes are leaf nodes and which aren't](nodeIsLeaf.png) 6 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+parent.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/parent`` 2 | 3 | Every node in the tree apart from the ``Node/root`` node will have a parent. 4 | 5 | ![A tree structure highlighting the parent node](nodeParent.png) 6 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+chidren.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/children`` 2 | 3 | Nodes can have zero, one, or many children. Nodes without a child are considered 4 | ``Node/leaves``. 5 | 6 | ![A tree structure highlighting the child nodes](nodeChildren.png) 7 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+prune(index).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/prune(childAtIndex:)`` 2 | 3 | ```swift 4 | // Prune the child node from it's parent, creating a new tree. 5 | let A = root.prune(childAtIndex: 0) 6 | ``` 7 | 8 | ![A pruned tree](nodePruneRoot.png) 9 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+prune(identifier).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/prune(childIdentifiedBy:)`` 2 | 3 | ```swift 4 | // Prune the child node from it's parent, creating a new tree. 5 | let A = root.prune(childIdentifiedBy: "A") 6 | ``` 7 | 8 | ![A pruned tree](nodePruneRoot.png) 9 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Root.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Root`` 2 | 3 | ```swift 4 | let root = Root("Root") { 5 | Branch("A") { 6 | "C" 7 | "D" 8 | } 9 | 10 | "B" 11 | } 12 | ``` 13 | 14 | ![The tree structure created by the example above](treeBuilderExample.png) 15 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Branch.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Branch`` 2 | 3 | ```swift 4 | let root = Root("Root") { 5 | Branch("A") { 6 | "C" 7 | "D" 8 | } 9 | 10 | "B" 11 | } 12 | ``` 13 | 14 | ![The tree structure created by the example above](treeBuilderExample.png) 15 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+appendChild(element).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/append(child:)-3dbmb`` 2 | 3 | ```swift 4 | // Add the "B" element as a child of the root node. 5 | let B = root.append(child: "B") 6 | ``` 7 | 8 | ![A tree structure demonstrating the behaviour of the append method](nodeAppend.png) 9 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+isRoot.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/isRoot`` 2 | 3 | A root node is a node without a parent node. One root always exists for each 4 | tree, and is the root ancestor of all child nodes. 5 | 6 | ![A tree structure indicating which nodes are root nodes and which aren't](nodeIsRoot.png) 7 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+remove(index).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/remove(childAtIndex:)`` 2 | 3 | ```swift 4 | // Remove the child node from it's parent, reparenting its child nodes. 5 | let A = root.remove(childAtIndex: "A") 6 | ``` 7 | 8 | ![A tree with a removed node](nodeRemoveRoot.png) 9 | 10 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+remove(identifier).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/remove(childIdentifiedBy:)`` 2 | 3 | ```swift 4 | // Remove the child node from it's parent, reparenting its child nodes. 5 | let A = root.remove(childIdentifiedBy: "A") 6 | ``` 7 | 8 | ![A tree with a removed node](nodeRemoveRoot.png) 9 | 10 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+insertChild(element-index).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/insert(child:atIndex:)-4pov2`` 2 | 3 | ```swift 4 | // Add the "C" element as a child of the root node, at index 1. 5 | let C = root.insert(child: "C", atIndex: 1) 6 | ``` 7 | 8 | ![A tree structure demonstrating the behaviour of the insert method](nodeInsert.png) 9 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+appendChild(node).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/append(child:)-5lg6n`` 2 | 3 | ```swift 4 | // Create a node. 5 | let B = Node("B") 6 | 7 | // Add the node as a child of the root node. 8 | root.append(child: B) 9 | ``` 10 | 11 | ![A tree structure demonstrating the behaviour of the append method](nodeAppend.png) 12 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+remove.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/remove()`` 2 | 3 | ```swift 4 | // Remove the node from it's parent, reparenting the child nodes. 5 | A.remove() 6 | ``` 7 | 8 | ![A tree with a removed node](nodeRemove.png) 9 | 10 | > Warning: Care should be taken when using this function on a root node, as 11 | it could result in orphaned child nodes. 12 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+insertChild(node-index).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/insert(child:atIndex:)-5en4l`` 2 | 3 | ```swift 4 | // Create a node. 5 | let C = Node("C") 6 | 7 | // Add the node as a child of the root node, at index 1. 8 | root.insert(child: C, atIndex: 1) 9 | ``` 10 | 11 | ![A tree structure demonstrating the behaviour of the insert method](nodeInsert.png) 12 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+leaves.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/leaves`` 2 | 3 | Child leaf nodes may be direct or distant descendents. 4 | 5 | A leaf node is a node that has no child nodes. 6 | 7 | ![A tree structure highlighting the leaf nodes](nodeLeaves.png) 8 | 9 | This function has `O(n)` performance over the size of the tree. It works by 10 | visiting each node, and collecting the nodes that are considered leaves. 11 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+breadthFirst.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/breadthFirst`` 2 | 3 | Breadth first iteration visits every node at each depth of the tree, before 4 | moving on to the nodes at the next depth level. It does this until all nodes 5 | in the tree have been visited. 6 | 7 | ```swift 8 | for node in root.breadthFirst { 9 | print(node.id) 10 | } 11 | ``` 12 | 13 | ![A tree structure showing the sequence nodes are visited with breadth first iteration](nodeBreadthFirst.png) 14 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+root.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/root`` 2 | 3 | A root node is a node without a parent node. One root always exists for each 4 | tree, and is the root ancestor of all ``Node/children``. 5 | 6 | ![A tree structure highlighting the root node](nodeRoot.png) 7 | 8 | This function works by walking the tree from the current node to the 9 | ``Node/parent`` node until the root node is found. When performing iteration of 10 | nodes that requires access to the root node, it can be beneficial to cache the 11 | root node to prevent continuous and costly lookup. 12 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+depthFirst.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/depthFirst`` 2 | 3 | Depth first iteration visits every node in the first branch of the tree, and 4 | then back traces to the next unvisited branch in the tree. Each branch is 5 | visited in turn until all nodes have been visited. 6 | 7 | ```swift 8 | for node in root.depthFirst { 9 | print(node.id) 10 | } 11 | ``` 12 | 13 | ![A tree structure showing the sequence nodes are visited with depth first iteration](nodeDepthFirst.png) 14 | 15 | > Note: Depth first iteration is the default iteration method when iterating the 16 | tree as a sequence. 17 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+node(identifier).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/node(identifiedBy:)`` 2 | 3 | This function has `O(n)` performance over the size of the tree. It works by 4 | visiting each node, and comparing the identifiers. 5 | 6 | This function will search only the sub-tree formed from the current node and it's 7 | children. To scan the entire tree for a specific element, call the function on 8 | the ``Tree/Node/root`` node. 9 | 10 | ```swift 11 | // Get the root node. 12 | let root = node.root 13 | 14 | // Search the sub-tree for the identifier and return it if it exists. 15 | let foundNode = root.node(identifiedBy: someIdentifier) 16 | ``` 17 | 18 | > Important: If two or more elements within the tree have the same identifier, 19 | then first node with a matching identifier will be returned. 20 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+contains(element).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/contains(element:)`` 2 | 3 | This function will test only the sub-tree formed from the current node and it's 4 | children. To scan the entire tree for a specific element, call the function on 5 | the ``Tree/Node/root`` node. 6 | 7 | ```swift 8 | // Get the root node. 9 | let root = node.root 10 | 11 | // Test if the node is in the tree. 12 | let isNodeFound = root.contains(element: someElement) 13 | ``` 14 | 15 | This function has `O(n)` performance over the size of the tree. It works by 16 | visiting each node, and comparing the identifiers. 17 | 18 | > Important: A matching element is found using the identifier associated with 19 | the element. If two or more elements have the same identifier, then first 20 | element with a matching identifier will be returned. 21 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node+node(element).md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node/node(forElement:)`` 2 | 3 | This function has `O(n)` performance over the size of the tree. It works by 4 | visiting each node, and comparing the identifiers. 5 | 6 | This function will search only the sub-tree formed from the current node and it's 7 | children. To scan the entire tree for a specific element, call the function on 8 | the ``Tree/Node/root`` node. 9 | 10 | ```swift 11 | // Get the root node. 12 | let root = node.root 13 | 14 | // Search the sub-tree for the node and return it if it exists. 15 | let foundNode = root.node(forElement: someElement) 16 | ``` 17 | 18 | > Important: A matching element is found using the identifier associated with 19 | the element. If two or more elements within the tree have the same identifier, 20 | then first node with a matching identifier will be returned. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matt Cox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Tree", 8 | platforms: [ 9 | .macOS(.v13), 10 | .iOS(.v13) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries a package produces, and make them visible to other packages. 14 | .library( 15 | name: "Tree", 16 | targets: ["Tree"] 17 | ), 18 | ], 19 | dependencies: [ 20 | // Dependencies declare other packages that this package depends on. 21 | .package(url: "https://github.com/apple/swift-collections", .upToNextMinor(from: "1.1.4")) 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 26 | .target( 27 | name: "Tree", 28 | dependencies: [ 29 | .product(name: "Collections", package: "swift-collections") 30 | ] 31 | ), 32 | .testTarget( 33 | name: "TreeTests", 34 | dependencies: [ 35 | "Tree" 36 | ] 37 | ), 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Tree.md: -------------------------------------------------------------------------------- 1 | # ``Tree`` 2 | 3 | A hierarchical tree structure constructed of interconnected nodes. 4 | 5 | ## Overview 6 | 7 | A tree is a hierarchical data structure constructed of interconnected nodes. 8 | 9 | Unlike a biological tree which grows vertically from a root at it's base and 10 | leaves at its top, the tree is conceptually upside down with a root at the top, 11 | and leaves at the bottom. 12 | 13 | ![An example of a basic tree hierarchy](tree.png) 14 | 15 | ## Topics 16 | 17 | ### Structure 18 | - ``Node`` 19 | - ``Node/children`` 20 | - ``Node/element`` 21 | - ``Node/leaves`` 22 | - ``Node/parent`` 23 | - ``Node/root`` 24 | 25 | ### Building 26 | - ``TreeBuilder`` 27 | - ``Branch`` 28 | - ``Root`` 29 | 30 | ### Editing 31 | - ``Node/append(child:)-3dbmb`` 32 | - ``Node/append(child:)-5lg6n`` 33 | - ``Node/insert(child:atIndex:)-4pov2`` 34 | - ``Node/insert(child:atIndex:)-5en4l`` 35 | - ``Node/prune()`` 36 | - ``Node/prune(childAtIndex:)`` 37 | - ``Node/prune(childIdentifiedBy:)`` 38 | - ``Node/remove()`` 39 | - ``Node/remove(childAtIndex:)`` 40 | - ``Node/remove(childIdentifiedBy:)`` 41 | 42 | ### Lookup 43 | - ``Node/contains(element:)`` 44 | - ``Node/node(forElement:)`` 45 | - ``Node/node(identifiedBy:)`` 46 | 47 | ### Properties 48 | - ``Node/isLeaf`` 49 | - ``Node/isRoot`` 50 | 51 | ### Iteration 52 | - ``Node/breadthFirst`` 53 | - ``Node/depthFirst`` 54 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/TreeBuilder.md: -------------------------------------------------------------------------------- 1 | # ``Tree/TreeBuilder`` 2 | 3 | You typically use ``TreeBuilder`` as a parameter attribute for child 4 | node-producing closure parameters, allowing those closures to provide multiple 5 | sub nodes. 6 | 7 | ```swift 8 | func createNode(_ element: T, @TreeBuilder _ contents: () -> [Node]) -> Node { 9 | let node = Node(element) 10 | for child in contents() { 11 | node.append(child: child) 12 | } 13 | return node 14 | } 15 | ``` 16 | 17 | The ``Node/init(_:_:)`` initializer on the ``Node`` can be used to building a 18 | tree with a tree builder. For example, to use this to build a simple structure 19 | containing a root node, a branch, and three leaf nodes. 20 | 21 | ```swift 22 | let root = Node("Root") { 23 | // Tree builders can contain other tree builders to create branches. 24 | Node("A") { 25 | "C" 26 | "D" 27 | } 28 | 29 | // Leaf nodes can be declared directly as a value. 30 | "B" 31 | } 32 | ``` 33 | 34 | ![The tree structure created by the example above](treeBuilderExample.png) 35 | 36 | The ``Root`` and ``Branch`` types are aliases for ``Node``, but provide a more 37 | user friendly interface for the tree builder. 38 | 39 | ```swift 40 | let root = Root("Root") { 41 | Branch("A") { 42 | "C" 43 | "D" 44 | } 45 | 46 | "B" 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /.github/workflows/publish-documentation.yml: -------------------------------------------------------------------------------- 1 | # Workflow name. 2 | name: Publish Documentation 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow one concurrent deployment 16 | concurrency: 17 | group: "pages" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | # Single deploy job since we're just deploying 22 | deploy: 23 | environment: 24 | # Must be set to this for deploying to GitHub Pages 25 | name: github-pages 26 | url: ${{ steps.deployment.outputs.page_url }} 27 | runs-on: macos-12 28 | steps: 29 | - name: Checkout 🛎️ 30 | uses: actions/checkout@v3 31 | - name: Publish Documentation 32 | run: | 33 | xcodebuild docbuild -scheme Tree \ 34 | -derivedDataPath /tmp/docbuild \ 35 | -destination 'generic/platform=iOS'; 36 | $(xcrun --find docc) process-archive \ 37 | transform-for-static-hosting /tmp/docbuild/Build/Products/Debug-iphoneos/Tree.doccarchive \ 38 | --hosting-base-path Tree \ 39 | --output-path docs; 40 | echo "" > docs/index.html; 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v1 43 | with: 44 | # Upload only docs directory 45 | path: 'docs' 46 | - name: Deploy to GitHub Pages 47 | id: deployment 48 | uses: actions/deploy-pages@v1 49 | -------------------------------------------------------------------------------- /Tests/TreeTests/Test.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Test.swift 3 | // Tree 4 | // 5 | // Created by Matt Cox on 21/08/2023. 6 | // Copyright © 2023 Matt Cox. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Tree 11 | 12 | /// A node test element. 13 | /// 14 | /// This stores the number of children, and the parent ID, so that it can be 15 | /// validated against the actual tree structure for unit testing. 16 | /// 17 | struct Test: Identifiable { 18 | /// The identifier for this node. 19 | /// 20 | var id: String 21 | 22 | /// The expected number of children for this node. 23 | /// 24 | var numberOfChildren: Int 25 | 26 | /// The id of the parent node. 27 | /// 28 | var parent: String? 29 | 30 | /// Initialize the Test element. 31 | /// 32 | /// - Parameters: 33 | /// - id: The identifier for this node. 34 | /// - numberOfChildren: The expected number of children for the node 35 | /// containing this element. 36 | /// - parent: The parent node for the node containing this element. 37 | /// 38 | init(_ id: String, numberOfChildren: Int, parent: String? = nil) { 39 | self.id = id 40 | self.parent = parent 41 | self.numberOfChildren = numberOfChildren 42 | } 43 | 44 | /// Validates the node structure, ensuring it matches the expected 45 | /// structure on the node element. 46 | /// 47 | /// This function will be called recursively, so is not suitable for 48 | /// testing very deep trees. 49 | /// 50 | /// - Parameters: 51 | /// - node: The node to validate. 52 | /// 53 | static func validate(node: Node) { 54 | XCTAssertEqual(node.id, node.element.id) 55 | XCTAssertEqual(node.parent?.id, node.element.parent) 56 | XCTAssertEqual(node.children.count, node.element.numberOfChildren) 57 | 58 | for child in node.children { 59 | Test.validate(node: child) 60 | } 61 | } 62 | } 63 | 64 | extension Node where Element == Test { 65 | func validate() { 66 | Test.validate(node: self) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/Tree/Documentation.docc/Node.md: -------------------------------------------------------------------------------- 1 | # ``Tree/Node`` 2 | 3 | A ``Node`` is the primitive building block for a ``Tree``. A tree cannot exist 4 | without at lease one node, however most trees have many nodes describing complex 5 | parent child relationships. 6 | 7 | Nodes have a ``Node/parent``, and they have ``Node/children``. A node without a 8 | parent is a ``Node/root`` node, and there is only one per-tree. Nodes without 9 | children are ``Node/leaves``. 10 | 11 | ![An example of a basic tree hierarchy, identifying root and leaf nodes](node.png) 12 | 13 | Nodes are generic types that store any identifiable value. Nodes with duplicate 14 | identifiers can exist in the tree, however for best results ensure each node 15 | has a unique identifier. 16 | 17 | > Warning: No checks are performed to ensure a valid tree structure, or 18 | guarantee a non-cyclic hierarchy. It is the responsibility of the client to 19 | ensure a node doesn't add it's own parent as a child, either directly or 20 | indirectly. 21 | 22 | ## Example 23 | 24 | In this example, a basic tree structure is created that contains strings. 25 | `String` does not conform to `Identifiable` by default, but conformance is 26 | implied. 27 | 28 | ```swift 29 | // Create a root node. This is identical to a standard node, 30 | // but has no parent. 31 | let root = Node("Root") 32 | 33 | // Create two nodes as children of the root node. 34 | let A = root.append(child: "A") 35 | let B = root.append(child: "B") 36 | 37 | // Create some leaf nodes as children of node A. 38 | let C = A.append(child: "C") 39 | let D = A.append(child: "D") 40 | ``` 41 | 42 | ![The tree structure created by the example above](nodeExample.png) 43 | 44 | Once the tree structure has been defined, its properties can be inspected and 45 | operations can be performed on the tree structure. 46 | 47 | ```swift 48 | print(root.isRoot) 49 | // "true" 50 | 51 | print(root.isLeaf) 52 | // "false" 53 | 54 | print(A.isRoot) 55 | // "false" 56 | 57 | print(A.isLeaf) 58 | // "false" 59 | 60 | print(C.isRoot) 61 | // "false" 62 | 63 | print(C.isLeaf) 64 | // "true" 65 | 66 | print(A.reduce("") { 67 | $0 + "\($1.element), " 68 | }) 69 | // "C, D, " 70 | ``` 71 | -------------------------------------------------------------------------------- /Sources/Tree/NodeIterator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeIterator.swift 3 | // Tree 4 | // 5 | // Created by Matt Cox on 14/08/2023. 6 | // Copyright © 2023 Matt Cox. All rights reserved. 7 | // 8 | 9 | import Collections 10 | import Foundation 11 | 12 | /// An iterator for iterating over all nodes in a tree. 13 | /// 14 | internal struct NodeIterator: IteratorProtocol { 15 | /// The method used for iteration. 16 | /// 17 | enum Method { 18 | /// Depth first iteration visits every node in the first branch of the 19 | /// tree, and then back traces to the next unvisited branch in the tree. 20 | /// Each branch is visited in turn until all nodes have been visited. 21 | /// 22 | case depthFirst 23 | 24 | /// Breadth first iteration visits every node at each depth of the tree, 25 | /// before moving on to the nodes at the next depth level. It does this 26 | /// until all nodes in the tree have been visited. 27 | /// 28 | case breadthFirst 29 | } 30 | 31 | private var nodesToVisit: Deque> 32 | private let method: Method 33 | 34 | /// Initialize the iterator. 35 | /// 36 | /// - Parameters: 37 | /// - node: The node to iterate from. 38 | /// - method: The method used to iterate; either depth first or breadth 39 | /// first. 40 | /// 41 | init(_ node: Node, method: Method) { 42 | self.nodesToVisit = Deque([node]) 43 | self.method = method 44 | } 45 | 46 | mutating func next() -> Node? { 47 | if nodesToVisit.isEmpty { 48 | return nil 49 | } 50 | 51 | if method == .depthFirst { 52 | let current = nodesToVisit.popFirst() 53 | if let children = current?.children, children.isEmpty == false { 54 | nodesToVisit.prepend(contentsOf: children) 55 | } 56 | return current 57 | } 58 | else { 59 | let current = nodesToVisit.popFirst() 60 | if let children = current?.children, children.isEmpty == false { 61 | nodesToVisit.append(contentsOf: children) 62 | } 63 | return current 64 | } 65 | } 66 | } 67 | 68 | extension Node: Sequence { 69 | public func makeIterator() -> AnyIterator> { 70 | depthFirst 71 | } 72 | } 73 | 74 | extension Node { 75 | /// An iterator that performs depth-first iteration of all nodes in the 76 | /// tree. 77 | /// 78 | public var depthFirst: AnyIterator> { 79 | AnyIterator(NodeIterator(self, method: .depthFirst)) 80 | } 81 | 82 | /// An iterator that performs breadth-first iteration of all nodes in the 83 | /// tree. 84 | /// 85 | public var breadthFirst: AnyIterator> { 86 | AnyIterator(NodeIterator(self, method: .breadthFirst)) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tree 2 | 3 |

4 | Swift 5 | 6 | Swift Package Manager 7 | 8 |

9 | 10 | Welcome to **Tree**, a Swift package implementing a hierarchical tree structure 11 | constructed of interconnected nodes. 12 | 13 |

14 | 15 | 16 | An example of a basic tree hierarchy 17 | 18 |

19 | 20 | ## Usage 21 | 22 | Tree's store a value associated with each node. This can be any identifiable 23 | type. The identifier is used for tracking the identity of a node within the 24 | tree. 25 | 26 | Building a tree is simple; you create a root node and add child nodes. 27 | 28 | ```swift 29 | // Create a root node. 30 | // 31 | let root = Node("root") 32 | 33 | // Create two nodes as children of the root node. 34 | // 35 | let A = root.append(child: "A") 36 | let B = root.append(child: "B") 37 | 38 | // Create some leaf nodes as children of node A. 39 | // 40 | let C = A.append(child: "C") 41 | let D = A.append(child: "D") 42 | ``` 43 | 44 | Building a tree is even easier with the declarative tree builder. 45 | 46 | ```swift 47 | let root = Root("root") { 48 | Branch("A") { 49 | "C" 50 | "D" 51 | } 52 | 53 | "B" 54 | } 55 | ``` 56 | 57 | The tree can then be enumerated of inspected for properties. 58 | 59 | ```swift 60 | print(root.isRoot) 61 | // "true" 62 | 63 | print(root.isLeaf) 64 | // "false" 65 | 66 | if let A = root.node(identifiedBy: "A") { 67 | print(A.reduce("") { 68 | $0 + "\($1.element), " 69 | }) 70 | // "C, D, " 71 | } 72 | ``` 73 | 74 | ## Documentation 75 | 76 | For more information on usage, the Tree documentation can be found at: https://mattcox.github.io/Tree/. 77 | 78 | ## Installation 79 | 80 | Tree is distributed using the [Swift Package Manager](https://swift.org/package-manager). To install it within another Swift package, add it as a dependency within your `Package.swift` manifest: 81 | 82 | ```swift 83 | let package = Package( 84 | // . . . 85 | dependencies: [ 86 | .package(url: "https://github.com/mattcox/Tree.git", branch: "main") 87 | ], 88 | // . . . 89 | ) 90 | ``` 91 | 92 | If you’d like to use Tree within an iOS, macOS, watchOS or tvOS app, then use Xcode’s `File > Add Packages...` menu command to add it to your project. 93 | 94 | Import Tree wherever you’d like to use it: 95 | ```swift 96 | import Tree 97 | ``` 98 | -------------------------------------------------------------------------------- /Sources/Tree/TreeBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeBuilder.swift 3 | // Tree 4 | // 5 | // Created by Matt Cox on 15/08/2023. 6 | // Copyright © 2023 Matt Cox. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// A typealias for ``Node``, used to provide a more friendly interface for 12 | /// ``TreeBuilder`` 13 | /// 14 | public typealias Root = Node 15 | 16 | /// A typealias for ``Node``, used to provide a more friendly interface for 17 | /// ``TreeBuilder`` 18 | /// 19 | public typealias Branch = Node 20 | 21 | extension Node { 22 | /// Initialize the tree structure starting at this node using a ``TreeBuilder``. 23 | /// 24 | /// The provided closure will be called to build the tree declaratively. 25 | /// 26 | /// - Parameters: 27 | /// - element: The element to store on this node. 28 | /// - contents: A closure used to build a tree declaratively. 29 | /// 30 | public convenience init(_ element: Element, @TreeBuilder _ contents: () -> [Node]) { 31 | self.init(element) 32 | for child in contents() { 33 | self.append(child: child) 34 | } 35 | } 36 | } 37 | 38 | /// A custom parameter attribute that constructs trees from closures. 39 | /// 40 | @resultBuilder 41 | public enum TreeBuilder { 42 | public static func buildBlock() -> [Node] { 43 | [] 44 | } 45 | } 46 | 47 | extension TreeBuilder { 48 | public static func buildPartialBlock(first: Element) -> [Node] { 49 | [Node(first)] 50 | } 51 | 52 | public static func buildPartialBlock(accumulated: [Node], next: Element) -> [Node] { 53 | accumulated + [Node(next)] 54 | } 55 | 56 | public static func buildPartialBlock(first: Node) -> [Node] { 57 | [first] 58 | } 59 | 60 | public static func buildPartialBlock(accumulated: [Node], next: Node) -> [Node] { 61 | accumulated + [next] 62 | } 63 | 64 | public static func buildPartialBlock(first: [Node]) -> [Node] { 65 | first 66 | } 67 | 68 | public static func buildPartialBlock(accumulated: [Node], next: [Node]) -> [Node] { 69 | accumulated + next 70 | } 71 | } 72 | 73 | extension TreeBuilder { 74 | public static func buildOptional(_ component: [Node]?) -> [Node] { 75 | component ?? [] 76 | } 77 | } 78 | 79 | extension TreeBuilder { 80 | public static func buildEither(first component: [Node]) -> [Node] { 81 | component 82 | } 83 | 84 | public static func buildEither(second component: [Node]) -> [Node] { 85 | component 86 | } 87 | } 88 | 89 | extension TreeBuilder { 90 | public static func buildArray(_ components: [[Node]]) -> [Node] { 91 | components.flatMap { 92 | $0 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Tests/TreeTests/NodeIteratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeIteratorTests.swift 3 | // Tree 4 | // 5 | // Created by Matt Cox on 20/08/2023. 6 | // Copyright © 2023 Matt Cox. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Tree 11 | 12 | final class NodeIteratorTests: XCTestCase { 13 | /// A standard tree for testing iteration. 14 | /// 15 | var iterationTree: Root { 16 | Root(Test("root", numberOfChildren: 5)) { 17 | Test("0", numberOfChildren: 0, parent: "root") 18 | Test("1", numberOfChildren: 0, parent: "root") 19 | 20 | Branch(Test("2", numberOfChildren: 3, parent: "root")) { 21 | Test("2.0", numberOfChildren: 0, parent: "2") 22 | Test("2.1", numberOfChildren: 0, parent: "2") 23 | Branch(Test("2.2", numberOfChildren: 1, parent: "2")) { 24 | Test("2.2.0", numberOfChildren: 0, parent: "2.2") 25 | } 26 | } 27 | 28 | Test("3", numberOfChildren: 0, parent: "root") 29 | 30 | Branch(Test("4", numberOfChildren: 2, parent: "root")) { 31 | Branch(Test("4.0", numberOfChildren: 1, parent: "4")) { 32 | Test("4.0.0", numberOfChildren: 0, parent: "4.0") 33 | } 34 | Test("4.1", numberOfChildren: 0, parent: "4") 35 | } 36 | } 37 | } 38 | 39 | /// Test node iteration. 40 | /// 41 | /// This uses depth first iteration. 42 | /// 43 | /// This is performed by simply defining a tree of moderate complexity, and 44 | /// then iterating over it, ensuring all nodes are returned in the expected 45 | /// order. 46 | /// 47 | func testIteration() { 48 | // Build a tree using the tree builder. 49 | // 50 | let root = iterationTree 51 | 52 | // Store a list of node identifiers in the order they're expected. 53 | // 54 | let expectedIdentifiers: [String] = ["root", "0", "1", "2", "2.0", "2.1", "2.2", "2.2.0", "3", "4", "4.0", "4.0.0", "4.1"] 55 | 56 | // Iterate over the nodes in the tree and collect them. 57 | // 58 | let foundIdentifiers = root.map { $0.id } 59 | 60 | // Compare the two arrays. 61 | // 62 | XCTAssertEqual(expectedIdentifiers, foundIdentifiers) 63 | } 64 | 65 | /// Test depth first node iteration. 66 | /// 67 | /// This is performed by simply defining a tree of moderate complexity, and 68 | /// then iterating over it, ensuring all nodes are returned in the expected 69 | /// order. 70 | /// 71 | func testDepthFirstIteration() { 72 | // Build a tree using the tree builder. 73 | // 74 | let root = iterationTree 75 | 76 | // Store a list of node identifiers in the order they're expected. 77 | // 78 | let expectedIdentifiers: [String] = ["root", "0", "1", "2", "2.0", "2.1", "2.2", "2.2.0", "3", "4", "4.0", "4.0.0", "4.1"] 79 | 80 | // Iterate over the nodes in the tree and collect them. 81 | // 82 | let foundIdentifiers = root.depthFirst.map { $0.id } 83 | 84 | // Compare the two arrays. 85 | // 86 | XCTAssertEqual(expectedIdentifiers, foundIdentifiers) 87 | } 88 | 89 | /// Test breadth first node iteration. 90 | /// 91 | /// This is performed by simply defining a tree of moderate complexity, and 92 | /// then iterating over it, ensuring all nodes are returned in the expected 93 | /// order. 94 | /// 95 | func testBreadthFirstIteration() { 96 | // Build a tree using the tree builder. 97 | // 98 | let root = iterationTree 99 | 100 | // Store a list of node identifiers in the order they're expected. 101 | // 102 | let expectedIdentifiers: [String] = ["root", "0", "1", "2", "3", "4", "2.0", "2.1", "2.2", "4.0", "4.1", "2.2.0", "4.0.0"] 103 | 104 | // Iterate over the nodes in the tree and collect them. 105 | // 106 | let foundIdentifiers = root.breadthFirst.map { $0.id } 107 | 108 | // Compare the two arrays. 109 | // 110 | XCTAssertEqual(expectedIdentifiers, foundIdentifiers) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Tests/TreeTests/TreeBuilderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeBuilderTests.swift 3 | // Tree 4 | // 5 | // Created by Matt Cox on 20/08/2023. 6 | // Copyright © 2023 Matt Cox. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Tree 11 | 12 | /// A series of tests for the TreeBuilder type. 13 | /// 14 | /// These tests work by building a tree using the tree builder, and then 15 | /// validating the tree structure against what we expect. The expected tree 16 | /// structure is stored on the node element, so generic tests can easily be 17 | /// added. 18 | /// 19 | final class TreeBuilderTests: XCTestCase { 20 | /// Tests the tree builder for building a basic tree containing one child 21 | /// element. 22 | /// 23 | func testBuildBlock() { 24 | let root = Root(Test("root", numberOfChildren: 1)) { 25 | Test("A", numberOfChildren: 0, parent: "root") 26 | } 27 | 28 | root.validate() 29 | } 30 | 31 | /// Tests the tree builder for building a basic tree containing multiple child 32 | /// element. 33 | /// 34 | func testBuildMultiple() { 35 | let root = Root(Test("root", numberOfChildren: 3)) { 36 | Test("A", numberOfChildren: 0, parent: "root") 37 | Test("B", numberOfChildren: 0, parent: "root") 38 | Test("C", numberOfChildren: 0, parent: "root") 39 | } 40 | 41 | root.validate() 42 | } 43 | 44 | /// Tests the tree builder for building a tree containing a single branch with 45 | /// child elements. 46 | /// 47 | func testBuildBranch() { 48 | let root = Root(Test("root", numberOfChildren: 1)) { 49 | Branch(Test("A", numberOfChildren: 2, parent: "root")) { 50 | Test("B", numberOfChildren: 0, parent: "A") 51 | Test("C", numberOfChildren: 0, parent: "A") 52 | } 53 | } 54 | 55 | root.validate() 56 | } 57 | 58 | /// Tests the tree builder for building a tree containing mixed types; elements, 59 | /// nodes and branches. 60 | /// 61 | func testBuildMixed() { 62 | let root = Root(Test("root", numberOfChildren: 4)) { 63 | Branch(Test("A", numberOfChildren: 2, parent: "root")) { 64 | Test("B", numberOfChildren: 0, parent: "A") 65 | Test("C", numberOfChildren: 0, parent: "A") 66 | } 67 | 68 | Test("D", numberOfChildren: 0, parent: "root") 69 | Test("E", numberOfChildren: 0, parent: "root") 70 | 71 | Node(Test("F", numberOfChildren: 0, parent: "root")) 72 | } 73 | 74 | root.validate() 75 | } 76 | 77 | /// Tests the tree builder with a conditional if/else statement. Both the if and 78 | /// else branch should produce a valid value, but only one should be added to 79 | /// the tree depending on which branch of the conditional is used. 80 | /// 81 | func testBuildConditionalIfElse() { 82 | var conditional = true 83 | 84 | // Test if the conditional is true. 85 | // 86 | let trueRoot = Root(Test("root", numberOfChildren: 3)) { 87 | if conditional { 88 | Test("A", numberOfChildren: 0, parent: "root") 89 | Branch(Test("B", numberOfChildren: 1, parent: "root")) { 90 | Test("C", numberOfChildren: 0, parent: "B") 91 | } 92 | Node(Test("D", numberOfChildren: 0, parent: "root")) 93 | } 94 | else { 95 | Test("E", numberOfChildren: 0, parent: "root") 96 | Branch(Test("F", numberOfChildren: 1, parent: "root")) { 97 | Test("G", numberOfChildren: 0, parent: "F") 98 | } 99 | Node(Test("H", numberOfChildren: 0, parent: "root")) 100 | } 101 | } 102 | 103 | trueRoot.validate() 104 | 105 | XCTAssertEqual(trueRoot.children[0].id, "A") 106 | XCTAssertEqual(trueRoot.children[1].id, "B") 107 | XCTAssertEqual(trueRoot.children[2].id, "D") 108 | 109 | // Test if the conditional is false. 110 | // 111 | conditional = false 112 | 113 | let falseRoot = Root(Test("root", numberOfChildren: 3)) { 114 | if conditional { 115 | Test("A", numberOfChildren: 0, parent: "root") 116 | Branch(Test("B", numberOfChildren: 1, parent: "root")) { 117 | Test("C", numberOfChildren: 0, parent: "B") 118 | } 119 | Node(Test("D", numberOfChildren: 0, parent: "root")) 120 | } 121 | else { 122 | Test("E", numberOfChildren: 0, parent: "root") 123 | Branch(Test("F", numberOfChildren: 1, parent: "root")) { 124 | Test("G", numberOfChildren: 0, parent: "F") 125 | } 126 | Node(Test("H", numberOfChildren: 0, parent: "root")) 127 | } 128 | } 129 | 130 | falseRoot.validate() 131 | 132 | XCTAssertEqual(falseRoot.children[0].id, "E") 133 | XCTAssertEqual(falseRoot.children[1].id, "F") 134 | XCTAssertEqual(falseRoot.children[2].id, "H") 135 | } 136 | 137 | /// Tests the tree builder for building an optional value. This can be used if 138 | /// the else branch for a conditional is not present. 139 | /// 140 | func testBuildOptional() { 141 | let conditional = false 142 | 143 | let root = Root(Test("root", numberOfChildren: 0)) { 144 | if conditional { 145 | Test("A", numberOfChildren: 0, parent: "root") 146 | Branch(Test("B", numberOfChildren: 1, parent: "root")) { 147 | Test("C", numberOfChildren: 0, parent: "B") 148 | } 149 | Node(Test("D", numberOfChildren: 0, parent: "root")) 150 | } 151 | } 152 | 153 | root.validate() 154 | } 155 | 156 | /// Tests the tree builder for building a tree with a loop. 157 | /// 158 | func testBuildLoop() { 159 | let root = Root(Test("root", numberOfChildren: 21)) { 160 | for i in 0..<10 { 161 | Branch(Test("branch_\(i)", numberOfChildren: 1, parent: "root")) { 162 | Node(Test("node_\(i)", numberOfChildren: 0, parent: "branch_\(i)")) 163 | } 164 | 165 | Test("element_\(i)", numberOfChildren: 0, parent: "root") 166 | } 167 | 168 | Test("child", numberOfChildren: 0, parent: "root") 169 | } 170 | 171 | root.validate() 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Sources/Tree/Node.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Node.swift 3 | // Tree 4 | // 5 | // Created by Matt Cox on 10/08/2023. 6 | // Copyright © 2023 Matt Cox. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// An identifiable node that when connected to other child nodes, forms a 12 | /// hierarchical tree structure. 13 | /// 14 | public final class Node: Identifiable { 15 | public var id: Element.ID { 16 | element.id 17 | } 18 | 19 | /// The element or value associated with this node. 20 | /// 21 | /// This value can be any type that conforms to `Identifiable`. 22 | /// 23 | public let element: Element 24 | 25 | /// The parent node for this node. 26 | /// 27 | public private(set) weak var parent: Node? 28 | 29 | /// The child nodes of this node. 30 | /// 31 | public private(set) var children: [Node] 32 | 33 | /// Initialize a new root node for the provided element. 34 | /// 35 | /// Any identifiable element can be stored on a node, however all nodes in a 36 | /// tree must store the same element type. 37 | /// 38 | /// - Parameters: 39 | /// - element: The element to store in the node. 40 | /// 41 | public init(_ element: Element) { 42 | self.element = element 43 | self.parent = nil 44 | self.children = [] 45 | } 46 | } 47 | 48 | extension Node { 49 | /// Adds a node as a child of another node. 50 | /// 51 | /// - Parameters: 52 | /// - node: The node to add as a child of the parent node. 53 | /// - parent: The parent node to append the child to. 54 | /// - index: An optional index to insert the child node. 55 | /// 56 | private static func addChild(node: Node, to parent: Node, atIndex index: Int? = nil) { 57 | node.parent = parent 58 | if let index { 59 | let insertIndex = Swift.max(index, 0) 60 | if insertIndex >= parent.children.count { 61 | parent.children.append(node) 62 | } 63 | else { 64 | parent.children.insert(node, at: insertIndex) 65 | } 66 | } 67 | else { 68 | parent.children.append(node) 69 | } 70 | } 71 | 72 | /// Inserts a node into the tree, as a new parent of the provided child 73 | /// node. The inserted node will parented to the existing childs parent. 74 | /// 75 | /// - Parameters: 76 | /// - node: The node to insert into the tree. 77 | /// - child: The existing child the new node will be a parent of. 78 | /// 79 | private static func insert(parent node: Node, into child: Node) { 80 | // Find the index of the child on it's parent. 81 | // 82 | let index = { 83 | if let parent = child.parent { 84 | return parent.children.firstIndex { 85 | $0.id == child.id 86 | } 87 | } 88 | return nil 89 | }() 90 | 91 | // Change the parent on the existing child from the old node, to the 92 | // new node. Also update the parent on this node to be the old parent. 93 | // 94 | node.parent = child.parent 95 | child.parent = node 96 | 97 | // Swap out the old child on the parent with the new node 98 | // 99 | if let index { 100 | node.parent?.children[index] = node 101 | } 102 | } 103 | 104 | /// Inserts a node into the tree, as a child of the provided parent node. 105 | /// All existing children of the parent node will be parented to this node. 106 | /// 107 | /// - Parameters: 108 | /// - node: The node to insert into the tree. 109 | /// - parent: The existing parent the new node will be a child of. 110 | /// 111 | private static func insert(child node: Node, into parent: Node) { 112 | node.children.append(contentsOf: parent.children) 113 | for child in node.children { 114 | child.parent = node 115 | } 116 | 117 | node.parent = parent 118 | parent.children = [node] 119 | } 120 | 121 | /// Removes a node from the tree. All child nodes will continue to be 122 | /// parented to the removed node. 123 | /// 124 | /// This converts this node into a root node, and an isolated tree. 125 | /// 126 | /// - Parameters: 127 | /// - node: The node to remove. 128 | /// 129 | private static func prune(node: Node) { 130 | node.parent?.children.removeAll { 131 | $0.id == node.id 132 | } 133 | node.parent = nil 134 | } 135 | 136 | /// Extracts a node from the tree. All child nodes will be reparented to 137 | /// the parent node. 138 | /// 139 | /// This function returns an array of parent nodes. If the extracted node 140 | /// has a parent, then this array will contain a single element. If it 141 | /// does not, then the array will contain the orphaned child nodes. 142 | /// 143 | /// - Parameters: 144 | /// - node: The node to extract. 145 | /// 146 | /// - Returns: The parent node, or orphaned children of this node. 147 | /// 148 | @discardableResult 149 | private static func extract(node: Node) -> [Node] { 150 | if let parent = node.parent { 151 | // Remove the node from the parent, and insert the nodes children 152 | // at the same position. 153 | // 154 | if let index = parent.children.firstIndex(of: node) { 155 | parent.children.remove(at: index) 156 | parent.children.insert(contentsOf: node.children, at: index) 157 | } 158 | else { 159 | parent.children.append(contentsOf: node.children) 160 | } 161 | 162 | // Update the parent of the children. 163 | // 164 | for child in node.children { 165 | child.parent = parent 166 | } 167 | 168 | // Isolate the removed node. 169 | // 170 | node.children = [] 171 | node.parent = nil 172 | 173 | return [parent] 174 | } 175 | else { 176 | let children = node.children 177 | for child in children { 178 | child.parent = nil 179 | } 180 | node.children = [] 181 | return children 182 | } 183 | } 184 | } 185 | 186 | extension Node { 187 | /// A boolean indicating if this node is a `leaf`. 188 | /// 189 | public var isLeaf: Bool { 190 | self.children.isEmpty 191 | } 192 | 193 | /// A boolean indicating if this node is a ``root``. 194 | /// 195 | public var isRoot: Bool { 196 | self.parent == nil 197 | } 198 | 199 | /// The root node for the tree this node belongs to. 200 | /// 201 | public var root: Node { 202 | var current = self 203 | var parent = current.parent 204 | 205 | while let nextParent = parent { 206 | current = nextParent 207 | parent = current.parent 208 | } 209 | 210 | return current 211 | } 212 | 213 | /// All leaf nodes that are ``children`` of this node. 214 | /// 215 | public var leaves: [Node] { 216 | self.filter { 217 | $0.isLeaf 218 | } 219 | } 220 | 221 | /// Test if the provided element is contained within the sub-tree formed by 222 | /// this node. 223 | /// 224 | /// - Parameters: 225 | /// - element: The element to test if it is contained within the sub-tree 226 | /// formed by this node. 227 | /// 228 | /// - Returns: A boolean indicating if the provided element is contained 229 | /// within the sub-tree formed by this node. 230 | /// 231 | public func contains(element: Element) -> Bool { 232 | self.node(identifiedBy: element.id) != nil 233 | } 234 | 235 | /// Returns the ``Node`` from sub-tree formed by this node that has the provided 236 | /// identifier. 237 | /// 238 | /// - Parameters: 239 | /// - identifier: The identifier of the node to return. 240 | /// 241 | /// - Returns: The node with the matching identifier, or nil if the node 242 | /// cannot be found. 243 | /// 244 | public func node(identifiedBy identifier: Element.ID) -> Node? { 245 | self.first { 246 | $0.id == identifier 247 | } 248 | } 249 | 250 | /// Returns the ``Node`` from sub-tree formed by this node that stores the 251 | /// provided element. 252 | /// 253 | /// - Parameters: 254 | /// - element: The element contained within the node to return. 255 | /// 256 | /// - Returns: The node storing the matching element, or nil if the node 257 | /// cannot be found. 258 | /// 259 | public func node(forElement element: Element) -> Node? { 260 | node(identifiedBy: element.id) 261 | } 262 | } 263 | 264 | extension Node { 265 | /// Append a new child element to this node. 266 | /// 267 | /// A new ``Node`` will be created for this element, and will be returned. 268 | /// 269 | /// - Parameters: 270 | /// - element: The element to add as a child of this node. 271 | /// 272 | /// - Returns: The node created to store the element. 273 | /// 274 | @discardableResult 275 | public func append(child element: Element) -> Node { 276 | let node = Node(element) 277 | Node.addChild(node: node, to: self) 278 | return node 279 | } 280 | 281 | /// Append a child node to this node. 282 | /// 283 | /// This is useful for grafting one ``Tree`` to another, however care should 284 | /// be taken to ensure the node being added or any of it's children don't 285 | /// already exist in the tree. 286 | /// 287 | /// - Parameters: 288 | /// - node: The node to add as a child of this node. 289 | /// 290 | public func append(child node: Node) { 291 | Node.addChild(node: node, to: self) 292 | } 293 | 294 | /// Insert a new child element to this node at the specified index. 295 | /// 296 | /// The newly created child ``Node`` will be returned. 297 | /// 298 | /// - Parameters: 299 | /// - element: The element to parent to this node. 300 | /// - index: The index of the element. 301 | /// 302 | /// - Returns: The node created to store the element. 303 | /// 304 | @discardableResult 305 | public func insert(child element: Element, atIndex index: Int) -> Node { 306 | let node = Node(element) 307 | Node.addChild(node: node, to: self, atIndex: index) 308 | return node 309 | } 310 | 311 | /// Insert a new child node to this node at the specified index. 312 | /// 313 | /// This is useful for grafting one ``Tree`` to another, however care should 314 | /// be taken to ensure the node being added or any of it's children don't 315 | /// already exist in the tree. 316 | /// 317 | /// - Parameters: 318 | /// - node: The node to parent to this node. 319 | /// - index: The index of the element. 320 | /// 321 | public func insert(child node: Node, atIndex index: Int) { 322 | Node.addChild(node: node, to: self, atIndex: index) 323 | } 324 | 325 | /// Removes this node and all of it's ``children`` from the parent node. 326 | /// 327 | /// This creates a new independent ``Tree`` with this node at the ``root`` 328 | /// of the new tree. 329 | /// 330 | public func prune() { 331 | Node.prune(node: self) 332 | } 333 | 334 | /// Removes a child node specified by index, and all of it's ``children`` 335 | /// from this node. 336 | /// 337 | /// This creates a new independent ``Tree`` with the pruned child at the 338 | /// ``root`` of the new tree. 339 | /// 340 | /// - Parameters: 341 | /// - index: The index of the child to prune. 342 | /// 343 | /// - Returns: The node that was pruned. 344 | /// 345 | public func prune(childAtIndex index: Int) -> Node { 346 | let child = self.children[index] 347 | Node.prune(node: child) 348 | return child 349 | } 350 | 351 | /// Removes a child node with the matching identifier, and all of it's 352 | /// ``children`` from this node. 353 | /// 354 | /// This creates a new independent ``Tree`` with the pruned child at the 355 | /// ``root`` of the new tree. 356 | /// 357 | /// - Parameters: 358 | /// - identifier: The identifier of the child to prune. 359 | /// 360 | /// - Returns: The node that was pruned. 361 | /// 362 | public func prune(childIdentifiedBy identifier: Node.ID) -> Node? { 363 | guard let child = self.children.first( 364 | where: { 365 | $0.id == identifier 366 | } 367 | ) 368 | else { 369 | return nil 370 | } 371 | 372 | Node.prune(node: child) 373 | return child 374 | } 375 | 376 | /// Removes this node and reparents all of it's ``children`` to the 377 | /// ``parent`` node. 378 | /// 379 | /// This extracts the node from the tree. 380 | /// 381 | public func remove() { 382 | Node.extract(node: self) 383 | } 384 | 385 | /// Removes a child node with the specified index, and reparents all of it's 386 | /// ``children`` to this node. 387 | /// 388 | /// This extracts the node from the ``Tree``. 389 | /// 390 | /// - Parameters: 391 | /// - index: The index of the child to remove. 392 | /// 393 | /// - Returns: The node that was removed. 394 | /// 395 | public func remove(childAtIndex index: Int) -> Node? { 396 | let child = self.children[index] 397 | Node.extract(node: child) 398 | return child 399 | } 400 | 401 | /// Removes a child node with the matching identifier, and reparents all of 402 | /// it's ``children`` to this node. 403 | /// 404 | /// This extracts the node from the ``Tree``. 405 | /// 406 | /// - Parameters: 407 | /// - identifier: The identifier of the child to remove. 408 | /// 409 | /// - Returns: The node that was removed. 410 | /// 411 | public func remove(childIdentifiedBy identifier: Node.ID) -> Node? { 412 | guard let child = self.children.first( 413 | where: { 414 | $0.id == identifier 415 | } 416 | ) 417 | else { 418 | return nil 419 | } 420 | 421 | Node.extract(node: child) 422 | return child 423 | } 424 | } 425 | 426 | extension Node: Equatable { 427 | public static func == (lhs: Node, rhs: Node) -> Bool { 428 | lhs.id == rhs.id 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /Tests/TreeTests/NodeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeTests.swift 3 | // Tree 4 | // 5 | // Created by Matt Cox on 20/08/2023. 6 | // Copyright © 2023 Matt Cox. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Tree 11 | 12 | final class NodeTests: XCTestCase { 13 | /// Test adding a child element into the tree. 14 | /// 15 | func testAddChildElement() { 16 | // Create the root node. 17 | // 18 | let root = Node(Test("root", numberOfChildren: 3, parent: nil)) 19 | 20 | // Add the children of the root node. 21 | // 22 | root.append(child: Test("A", numberOfChildren: 0, parent: "root")) 23 | root.append(child: Test("B", numberOfChildren: 0, parent: "root")) 24 | 25 | let C = root.append(child: Test("C", numberOfChildren: 2, parent: "root")) 26 | 27 | // Add two children of the C node. 28 | // 29 | C.append(child: Test("D", numberOfChildren: 0, parent: "C")) 30 | C.append(child: Test("E", numberOfChildren: 0, parent: "C")) 31 | 32 | // Validate the structure. 33 | // 34 | root.validate() 35 | } 36 | 37 | /// Test adding a child node into the tree. 38 | /// 39 | func testAddChildNode() { 40 | // Create the root node. 41 | // 42 | let root = Node(Test("root", numberOfChildren: 3, parent: nil)) 43 | 44 | // Create three children that will be parented to the root node. 45 | // 46 | let A = Node(Test("A", numberOfChildren: 0, parent: "root")) 47 | let B = Node(Test("B", numberOfChildren: 0, parent: "root")) 48 | let C = Node(Test("C", numberOfChildren: 2, parent: "root")) 49 | 50 | // Create two children that will be parented to the C node. 51 | // 52 | let D = Node(Test("D", numberOfChildren: 0, parent: "C")) 53 | let E = Node(Test("E", numberOfChildren: 0, parent: "C")) 54 | 55 | // Build the structure. 56 | // 57 | root.append(child: A) 58 | root.append(child: B) 59 | root.append(child: C) 60 | 61 | C.append(child: D) 62 | C.append(child: E) 63 | 64 | // Validate the structure. 65 | // 66 | root.validate() 67 | } 68 | 69 | /// Test adding a child element into the tree at a specific index. 70 | /// 71 | func testAddChildElementAtIndex() { 72 | // Create a basic tree structure. 73 | // 74 | let root = Root(Test("root", numberOfChildren: 3)) { 75 | Test("A", numberOfChildren: 0, parent: "root") 76 | Test("B", numberOfChildren: 0, parent: "root") 77 | Test("C", numberOfChildren: 0, parent: "root") 78 | } 79 | 80 | // Attempt to insert an element before the A node. This should be index 81 | // 0. 82 | // 83 | root.insert(child: Test("BeforeA", numberOfChildren: 0, parent: "root"), atIndex: 0) 84 | XCTAssertEqual(root.children.count, 4) 85 | XCTAssertEqual(root.children[0].id, "BeforeA") 86 | XCTAssertEqual(root.children[1].id, "A") 87 | XCTAssertEqual(root.children[2].id, "B") 88 | XCTAssertEqual(root.children[3].id, "C") 89 | 90 | // Attempted to insert an element after the A node. This should now be 91 | // index 2. 92 | // 93 | root.insert(child: Test("AfterA", numberOfChildren: 0, parent: "root"), atIndex: 2) 94 | XCTAssertEqual(root.children.count, 5) 95 | XCTAssertEqual(root.children[0].id, "BeforeA") 96 | XCTAssertEqual(root.children[1].id, "A") 97 | XCTAssertEqual(root.children[2].id, "AfterA") 98 | XCTAssertEqual(root.children[3].id, "B") 99 | XCTAssertEqual(root.children[4].id, "C") 100 | 101 | // Attempt to insert an element at a negative index. It should be added 102 | // to the start of the list of children. 103 | // 104 | root.insert(child: Test("Start", numberOfChildren: 0, parent: "root"), atIndex: Int.min) 105 | XCTAssertEqual(root.children.count, 6) 106 | XCTAssertEqual(root.children[0].id, "Start") 107 | XCTAssertEqual(root.children[1].id, "BeforeA") 108 | XCTAssertEqual(root.children[2].id, "A") 109 | XCTAssertEqual(root.children[3].id, "AfterA") 110 | XCTAssertEqual(root.children[4].id, "B") 111 | XCTAssertEqual(root.children[5].id, "C") 112 | 113 | // Attempt to insert an element at a out of bounds index. It should be 114 | // added to the end of the list of children. 115 | // 116 | root.insert(child: Test("End", numberOfChildren: 0, parent: "root"), atIndex: Int.max) 117 | XCTAssertEqual(root.children.count, 7) 118 | XCTAssertEqual(root.children[0].id, "Start") 119 | XCTAssertEqual(root.children[1].id, "BeforeA") 120 | XCTAssertEqual(root.children[2].id, "A") 121 | XCTAssertEqual(root.children[3].id, "AfterA") 122 | XCTAssertEqual(root.children[4].id, "B") 123 | XCTAssertEqual(root.children[5].id, "C") 124 | XCTAssertEqual(root.children[6].id, "End") 125 | } 126 | 127 | /// Test inserting a node into the tree at a specific index. 128 | /// 129 | func testAddChildNodeAtIndex() { 130 | // Create a basic tree structure. 131 | // 132 | let root = Root(Test("root", numberOfChildren: 3)) { 133 | Test("A", numberOfChildren: 0, parent: "root") 134 | Test("B", numberOfChildren: 0, parent: "root") 135 | Test("C", numberOfChildren: 0, parent: "root") 136 | } 137 | 138 | // Attempt to insert an element before the A node. This should be index 139 | // 0. 140 | // 141 | root.insert(child: Node(Test("BeforeA", numberOfChildren: 0, parent: "root")), atIndex: 0) 142 | XCTAssertEqual(root.children.count, 4) 143 | XCTAssertEqual(root.children[0].id, "BeforeA") 144 | XCTAssertEqual(root.children[1].id, "A") 145 | XCTAssertEqual(root.children[2].id, "B") 146 | XCTAssertEqual(root.children[3].id, "C") 147 | 148 | // Attempted to insert an element after the A node. This should now be 149 | // index 2. 150 | // 151 | root.insert(child: Node(Test("AfterA", numberOfChildren: 0, parent: "root")), atIndex: 2) 152 | XCTAssertEqual(root.children.count, 5) 153 | XCTAssertEqual(root.children[0].id, "BeforeA") 154 | XCTAssertEqual(root.children[1].id, "A") 155 | XCTAssertEqual(root.children[2].id, "AfterA") 156 | XCTAssertEqual(root.children[3].id, "B") 157 | XCTAssertEqual(root.children[4].id, "C") 158 | 159 | // Attempt to insert an element at a negative index. It should be added 160 | // to the start of the list of children. 161 | // 162 | root.insert(child: Node(Test("Start", numberOfChildren: 0, parent: "root")), atIndex: Int.min) 163 | XCTAssertEqual(root.children.count, 6) 164 | XCTAssertEqual(root.children[0].id, "Start") 165 | XCTAssertEqual(root.children[1].id, "BeforeA") 166 | XCTAssertEqual(root.children[2].id, "A") 167 | XCTAssertEqual(root.children[3].id, "AfterA") 168 | XCTAssertEqual(root.children[4].id, "B") 169 | XCTAssertEqual(root.children[5].id, "C") 170 | 171 | // Attempt to insert an element at a out of bounds index. It should be 172 | // added to the end of the list of children. 173 | // 174 | root.insert(child: Node(Test("End", numberOfChildren: 0, parent: "root")), atIndex: Int.max) 175 | XCTAssertEqual(root.children.count, 7) 176 | XCTAssertEqual(root.children[0].id, "Start") 177 | XCTAssertEqual(root.children[1].id, "BeforeA") 178 | XCTAssertEqual(root.children[2].id, "A") 179 | XCTAssertEqual(root.children[3].id, "AfterA") 180 | XCTAssertEqual(root.children[4].id, "B") 181 | XCTAssertEqual(root.children[5].id, "C") 182 | XCTAssertEqual(root.children[6].id, "End") 183 | } 184 | 185 | /// Tests the children getter for the node. 186 | /// 187 | func testChildren() { 188 | // Create a basic tree structure containing some child nodes. 189 | // 190 | let root = Root(Test("root", numberOfChildren: 5)) { 191 | Test("A", numberOfChildren: 0, parent: "root") 192 | Test("B", numberOfChildren: 0, parent: "root") 193 | Test("C", numberOfChildren: 0, parent: "root") 194 | Test("D", numberOfChildren: 0, parent: "root") 195 | Test("E", numberOfChildren: 0, parent: "root") 196 | } 197 | 198 | // Test the children to see they are in the order that was expected. 199 | // 200 | XCTAssertEqual(root.children.count, 5) 201 | XCTAssertEqual(root.children[0].id, "A") 202 | XCTAssertEqual(root.children[1].id, "B") 203 | XCTAssertEqual(root.children[2].id, "C") 204 | XCTAssertEqual(root.children[3].id, "D") 205 | XCTAssertEqual(root.children[4].id, "E") 206 | } 207 | 208 | /// Tests the contains method for the node. 209 | /// 210 | func testContains() { 211 | // Create a basic tree structure containing a basic multi-level 212 | // structure. 213 | // 214 | let root = Root(Test("root", numberOfChildren: 5)) { 215 | Test("A", numberOfChildren: 0, parent: "root") 216 | Test("B", numberOfChildren: 0, parent: "root") 217 | Branch(Test("C", numberOfChildren: 3, parent: "root")) { 218 | Test("F", numberOfChildren: 0, parent: "C") 219 | Test("G", numberOfChildren: 0, parent: "C") 220 | Branch(Test("H", numberOfChildren: 3, parent: "C")) { 221 | Test("I", numberOfChildren: 0, parent: "H") 222 | Test("J", numberOfChildren: 0, parent: "H") 223 | Test("K", numberOfChildren: 0, parent: "H") 224 | } 225 | } 226 | Test("D", numberOfChildren: 0, parent: "root") 227 | Test("E", numberOfChildren: 0, parent: "root") 228 | } 229 | 230 | // Try and find the root node from the root. It should be found, as this 231 | // is the root node. 232 | // 233 | XCTAssertTrue(root.contains(element: Test("root", numberOfChildren: 5))) 234 | 235 | // Try and find the D node from the root. It should be found, as it's a 236 | // child of the root node. 237 | // 238 | XCTAssertTrue(root.contains(element: Test("D", numberOfChildren: 0, parent: "root"))) 239 | 240 | // Try and find the K node from the root. It should be found, as it's a 241 | // child of the root node. 242 | // 243 | XCTAssertTrue(root.contains(element: Test("K", numberOfChildren: 0, parent: "H"))) 244 | 245 | // Try and find a node that doesn't exist. 246 | // 247 | XCTAssertFalse(root.contains(element: Test("NotHere", numberOfChildren: 0, parent: "Whatever"))) 248 | 249 | // Get the H node by walking the tree, and then try and lookup the 250 | // A. It should not be found, as it's not a child of the H node. 251 | // 252 | let H: Node? = { 253 | guard let C = root.children.first(where: { 254 | $0.id == "C" 255 | }) 256 | else { 257 | return nil 258 | } 259 | 260 | return C.children.first(where: { 261 | $0.id == "H" 262 | }) 263 | }() 264 | 265 | XCTAssertNotNil(H) 266 | XCTAssertFalse((H?.contains(element: Test("A", numberOfChildren: 0, parent: "root"))) ?? true) 267 | } 268 | 269 | /// Test the isLeaf getter on the node. 270 | /// 271 | func testIsLeaf() { 272 | let expectedLeaves: Set = ["A", "B", "D", "E", "F", "G", "I", "J", "K"] 273 | 274 | // Create a basic tree structure containing a basic multi-level 275 | // structure. 276 | // 277 | let root = Root(Test("root", numberOfChildren: 5)) { 278 | Test("A", numberOfChildren: 0, parent: "root") 279 | Test("B", numberOfChildren: 0, parent: "root") 280 | Branch(Test("C", numberOfChildren: 3, parent: "root")) { 281 | Test("F", numberOfChildren: 0, parent: "C") 282 | Test("G", numberOfChildren: 0, parent: "C") 283 | Branch(Test("H", numberOfChildren: 3, parent: "C")) { 284 | Test("I", numberOfChildren: 0, parent: "H") 285 | Test("J", numberOfChildren: 0, parent: "H") 286 | Test("K", numberOfChildren: 0, parent: "H") 287 | } 288 | } 289 | Test("D", numberOfChildren: 0, parent: "root") 290 | Test("E", numberOfChildren: 0, parent: "root") 291 | } 292 | 293 | // Enumerate over all the nodes and compare against the set of expected 294 | // leaves. 295 | // 296 | for node in root { 297 | if expectedLeaves.contains(node.id) { 298 | XCTAssertTrue(node.isLeaf) 299 | } 300 | else { 301 | XCTAssertFalse(node.isLeaf) 302 | } 303 | } 304 | } 305 | 306 | /// Test the isLeaf getter on the node. 307 | /// 308 | func testIsRoot() { 309 | // Create a basic tree structure. 310 | // 311 | let root = Root(Test("root", numberOfChildren: 5)) { 312 | Test("A", numberOfChildren: 0, parent: "root") 313 | Test("B", numberOfChildren: 0, parent: "root") 314 | Branch(Test("C", numberOfChildren: 3, parent: "root")) { 315 | Test("F", numberOfChildren: 0, parent: "C") 316 | Test("G", numberOfChildren: 0, parent: "C") 317 | } 318 | Test("D", numberOfChildren: 0, parent: "root") 319 | Test("E", numberOfChildren: 0, parent: "root") 320 | } 321 | 322 | // Enumerate over all the nodes and test if only the node labeled "root" 323 | // thinks it's a root. 324 | // 325 | for node in root { 326 | if node.id == "root" { 327 | XCTAssertTrue(node.isRoot) 328 | } 329 | else { 330 | XCTAssertFalse(node.isRoot) 331 | } 332 | } 333 | } 334 | 335 | /// Test the leaves getter on the node. 336 | /// 337 | func testLeaves() { 338 | let expectedLeaves: Set = ["A", "B", "D", "E", "F", "G", "I", "J", "K"] 339 | 340 | // Create a basic tree structure containing a basic multi-level 341 | // structure. 342 | // 343 | let root = Root(Test("root", numberOfChildren: 5)) { 344 | Test("A", numberOfChildren: 0, parent: "root") 345 | Test("B", numberOfChildren: 0, parent: "root") 346 | Branch(Test("C", numberOfChildren: 3, parent: "root")) { 347 | Test("F", numberOfChildren: 0, parent: "C") 348 | Test("G", numberOfChildren: 0, parent: "C") 349 | Branch(Test("H", numberOfChildren: 3, parent: "C")) { 350 | Test("I", numberOfChildren: 0, parent: "H") 351 | Test("J", numberOfChildren: 0, parent: "H") 352 | Test("K", numberOfChildren: 0, parent: "H") 353 | } 354 | } 355 | Test("D", numberOfChildren: 0, parent: "root") 356 | Test("E", numberOfChildren: 0, parent: "root") 357 | } 358 | 359 | // Get the leaves and compare against the list of expected leaves. 360 | // 361 | let foundLeaves = root.leaves 362 | XCTAssertEqual(expectedLeaves.count, foundLeaves.count) 363 | 364 | for foundLeaf in foundLeaves { 365 | XCTAssertTrue(expectedLeaves.contains(foundLeaf.id)) 366 | } 367 | } 368 | 369 | /// Tests the node(element:) method for the node. 370 | /// 371 | func testNodeElement() { 372 | // Create a basic tree structure containing a basic multi-level 373 | // structure. 374 | // 375 | let root = Root(Test("root", numberOfChildren: 5)) { 376 | Test("A", numberOfChildren: 0, parent: "root") 377 | Test("B", numberOfChildren: 0, parent: "root") 378 | Branch(Test("C", numberOfChildren: 3, parent: "root")) { 379 | Test("F", numberOfChildren: 0, parent: "C") 380 | Test("G", numberOfChildren: 0, parent: "C") 381 | Branch(Test("H", numberOfChildren: 3, parent: "C")) { 382 | Test("I", numberOfChildren: 0, parent: "H") 383 | Test("J", numberOfChildren: 0, parent: "H") 384 | Test("K", numberOfChildren: 0, parent: "H") 385 | } 386 | } 387 | Test("D", numberOfChildren: 0, parent: "root") 388 | Test("E", numberOfChildren: 0, parent: "root") 389 | } 390 | 391 | // Try and get the root node from the root. It should be found, as this 392 | // is the root node. 393 | // 394 | XCTAssertNotNil(root.node(forElement: Test("root", numberOfChildren: 5))) 395 | 396 | // Try and find the D node from the root. It should be found, as it's a 397 | // child of the root node. 398 | // 399 | XCTAssertNotNil(root.node(forElement: Test("D", numberOfChildren: 0, parent: "root"))) 400 | 401 | // Try and find the K node from the root. It should be found, as it's a 402 | // child of the root node. 403 | // 404 | XCTAssertNotNil(root.node(forElement: Test("K", numberOfChildren: 0, parent: "H"))) 405 | 406 | // Try and find a node that doesn't exist. 407 | // 408 | XCTAssertNil(root.node(forElement: Test("NotHere", numberOfChildren: 0, parent: "Whatever"))) 409 | 410 | // Get the H node by walking the tree, and then try and lookup the 411 | // A. It should not be found, as it's not a child of the H node. 412 | // 413 | let H: Node? = { 414 | guard let C = root.children.first(where: { 415 | $0.id == "C" 416 | }) 417 | else { 418 | return nil 419 | } 420 | 421 | return C.children.first(where: { 422 | $0.id == "H" 423 | }) 424 | }() 425 | 426 | if let H { 427 | XCTAssertNil(H.node(forElement: Test("A", numberOfChildren: 0, parent: "root"))) 428 | } 429 | else { 430 | XCTFail() 431 | } 432 | } 433 | 434 | /// Tests the node(identifier:) method for the node. 435 | /// 436 | func testNodeIdentifier() { 437 | // Create a basic tree structure containing a basic multi-level 438 | // structure. 439 | // 440 | let root = Root(Test("root", numberOfChildren: 5)) { 441 | Test("A", numberOfChildren: 0, parent: "root") 442 | Test("B", numberOfChildren: 0, parent: "root") 443 | Branch(Test("C", numberOfChildren: 3, parent: "root")) { 444 | Test("F", numberOfChildren: 0, parent: "C") 445 | Test("G", numberOfChildren: 0, parent: "C") 446 | Branch(Test("H", numberOfChildren: 3, parent: "C")) { 447 | Test("I", numberOfChildren: 0, parent: "H") 448 | Test("J", numberOfChildren: 0, parent: "H") 449 | Test("K", numberOfChildren: 0, parent: "H") 450 | } 451 | } 452 | Test("D", numberOfChildren: 0, parent: "root") 453 | Test("E", numberOfChildren: 0, parent: "root") 454 | } 455 | 456 | // Try and get the root node from the root. It should be found, as this 457 | // is the root node. 458 | // 459 | XCTAssertNotNil(root.node(identifiedBy: "root")) 460 | 461 | // Try and find the D node from the root. It should be found, as it's a 462 | // child of the root node. 463 | // 464 | XCTAssertNotNil(root.node(identifiedBy: "D")) 465 | 466 | // Try and find the K node from the root. It should be found, as it's a 467 | // child of the root node. 468 | // 469 | XCTAssertNotNil(root.node(identifiedBy: "K")) 470 | 471 | // Try and find a node that doesn't exist. 472 | // 473 | XCTAssertNil(root.node(identifiedBy: "NotHere")) 474 | 475 | // Get the H node by walking the tree, and then try and lookup the 476 | // A. It should not be found, as it's not a child of the H node. 477 | // 478 | let H: Node? = { 479 | guard let C = root.children.first(where: { 480 | $0.id == "C" 481 | }) 482 | else { 483 | return nil 484 | } 485 | 486 | return C.children.first(where: { 487 | $0.id == "H" 488 | }) 489 | }() 490 | 491 | if let H { 492 | XCTAssertNil(H.node(identifiedBy: "A")) 493 | } 494 | else { 495 | XCTFail() 496 | } 497 | } 498 | 499 | /// Test the parent getter on the node. 500 | /// 501 | func testParent() { 502 | // Build a basic tree with some parenting information. 503 | // 504 | let root = Node(Test("root", numberOfChildren: 1, parent: nil)) 505 | let A = Node(Test("A", numberOfChildren: 1, parent: "root")) 506 | let B = Node(Test("B", numberOfChildren: 1, parent: "A")) 507 | let C = Node(Test("C", numberOfChildren: 1, parent: "B")) 508 | 509 | root.append(child: A) 510 | A.append(child: B) 511 | B.append(child: C) 512 | 513 | // Test the parents. 514 | // 515 | XCTAssertEqual(C.parent?.id, C.element.parent) 516 | XCTAssertEqual(B.parent?.id, B.element.parent) 517 | XCTAssertEqual(A.parent?.id, A.element.parent) 518 | XCTAssertEqual(root.parent?.id, root.element.parent) 519 | } 520 | 521 | // Test the prune method on the node. 522 | // 523 | func testPrune() { 524 | // Build a basic tree with three levels of hierarchy. 525 | // 526 | let root = Node(Test("root", numberOfChildren: 2, parent: nil)) 527 | 528 | let A = Node(Test("A", numberOfChildren: 2, parent: "root")) 529 | let B = Node(Test("B", numberOfChildren: 0, parent: "root")) 530 | 531 | let C = Node(Test("C", numberOfChildren: 0, parent: "A")) 532 | let D = Node(Test("D", numberOfChildren: 0, parent: "A")) 533 | 534 | root.append(child: A) 535 | root.append(child: B) 536 | 537 | A.append(child: C) 538 | A.append(child: D) 539 | 540 | // Prune the A node. 541 | // 542 | A.prune() 543 | 544 | // Check the root of A, C and D. It should be A. 545 | // 546 | XCTAssertEqual(A.root.id, A.id) 547 | XCTAssertEqual(C.root.id, A.id) 548 | XCTAssertEqual(D.root.id, A.id) 549 | 550 | // Check the number of children of root - it should be 1. 551 | // 552 | XCTAssertEqual(root.children.count, 1) 553 | } 554 | 555 | // Test the prune(childIdentifiedBy:) method on the node. 556 | // 557 | func testPruneChildIdentifiedBy() { 558 | // Build a basic tree with three levels of hierarchy. 559 | // 560 | let root = Root(Test("root", numberOfChildren: 2, parent: nil)) { 561 | Branch(Test("A", numberOfChildren: 2, parent: "root")) { 562 | Test("C", numberOfChildren: 0, parent: "A") 563 | Test("D", numberOfChildren: 0, parent: "A") 564 | } 565 | 566 | Test("B", numberOfChildren: 0, parent: "root") 567 | } 568 | 569 | // Prune the A node from the root. 570 | // 571 | guard let A = root.prune(childIdentifiedBy: "A") else { 572 | XCTFail() 573 | return 574 | } 575 | 576 | // Check the root of A and it's children. It should be A. 577 | // 578 | XCTAssertEqual(A.root.id, A.id) 579 | for child in A.children { 580 | XCTAssertEqual(child.root.id, A.id) 581 | } 582 | 583 | // Check the number of children of root - it should be 1. 584 | // 585 | XCTAssertEqual(root.children.count, 1) 586 | } 587 | 588 | // Test the prune(childAtIndex:) method on the node. 589 | // 590 | func testPruneChildAtIndex() { 591 | // Build a basic tree with three levels of hierarchy. 592 | // 593 | let root = Root(Test("root", numberOfChildren: 2, parent: nil)) { 594 | Branch(Test("A", numberOfChildren: 2, parent: "root")) { 595 | Test("C", numberOfChildren: 0, parent: "A") 596 | Test("D", numberOfChildren: 0, parent: "A") 597 | } 598 | 599 | Test("B", numberOfChildren: 0, parent: "root") 600 | } 601 | 602 | // Prune the A node from the root. 603 | // 604 | let pruned = root.prune(childAtIndex: 0) 605 | XCTAssertEqual(pruned.id, "A") 606 | 607 | // Check the root of A and it's children. It should be A. 608 | // 609 | XCTAssertEqual(pruned.root.id, pruned.id) 610 | for child in pruned.children { 611 | XCTAssertEqual(child.root.id, pruned.id) 612 | } 613 | 614 | // Check the number of children of root - it should be 1. 615 | // 616 | XCTAssertEqual(root.children.count, 1) 617 | } 618 | 619 | // Test the remove method on the node. 620 | // 621 | func testRemove() { 622 | // Build a basic tree with three levels of hierarchy. 623 | // 624 | let root = Node(Test("root", numberOfChildren: 2, parent: nil)) 625 | 626 | let A = Node(Test("A", numberOfChildren: 2, parent: "root")) 627 | let B = Node(Test("B", numberOfChildren: 0, parent: "root")) 628 | 629 | let C = Node(Test("C", numberOfChildren: 0, parent: "A")) 630 | let D = Node(Test("D", numberOfChildren: 0, parent: "A")) 631 | 632 | root.append(child: A) 633 | root.append(child: B) 634 | 635 | A.append(child: C) 636 | A.append(child: D) 637 | 638 | // Remove the A node. This should make an independent tree for A, and 639 | // reparent A children to A's old parent, at the same position as A. 640 | // 641 | A.remove() 642 | 643 | // Confirm that A is an independent node without any children or without 644 | // a root node. 645 | // 646 | XCTAssertNil(A.parent) 647 | XCTAssertTrue(A.children.isEmpty) 648 | 649 | // Check the children of root - it should be C, D and B in that order. 650 | // 651 | XCTAssertEqual(root.children.count, 3) 652 | XCTAssertEqual(root.children[0].id, C.id) 653 | XCTAssertEqual(root.children[1].id, D.id) 654 | XCTAssertEqual(root.children[2].id, B.id) 655 | 656 | // Check the parent of nodes C and D, it should be the root. 657 | // 658 | XCTAssertEqual(C.parent?.id, root.id) 659 | XCTAssertEqual(D.parent?.id, root.id) 660 | } 661 | 662 | // Test the remove(childIdentifiedBy:) method on the node. 663 | // 664 | func testRemoveChildIdentifiedBy() { 665 | // Build a basic tree with three levels of hierarchy. 666 | // 667 | let root = Node(Test("root", numberOfChildren: 2, parent: nil)) 668 | 669 | let A = Node(Test("A", numberOfChildren: 2, parent: "root")) 670 | let B = Node(Test("B", numberOfChildren: 0, parent: "root")) 671 | 672 | let C = Node(Test("C", numberOfChildren: 0, parent: "A")) 673 | let D = Node(Test("D", numberOfChildren: 0, parent: "A")) 674 | 675 | root.append(child: A) 676 | root.append(child: B) 677 | 678 | A.append(child: C) 679 | A.append(child: D) 680 | 681 | // Remove the A node, specifying it by identifier. This should make an 682 | // independent tree for A, and reparent A children to A's old parent, 683 | // at the same position as A. 684 | // 685 | _ = root.remove(childIdentifiedBy: A.id) 686 | 687 | // Confirm that A is an independent node without any children or without 688 | // a root node. 689 | // 690 | XCTAssertNil(A.parent) 691 | XCTAssertTrue(A.children.isEmpty) 692 | 693 | // Check the children of root - it should be C, D and B in that order. 694 | // 695 | XCTAssertEqual(root.children.count, 3) 696 | XCTAssertEqual(root.children[0].id, C.id) 697 | XCTAssertEqual(root.children[1].id, D.id) 698 | XCTAssertEqual(root.children[2].id, B.id) 699 | 700 | // Check the parent of nodes C and D, it should be the root. 701 | // 702 | XCTAssertEqual(C.parent?.id, root.id) 703 | XCTAssertEqual(D.parent?.id, root.id) 704 | } 705 | 706 | // Test the remove(childAtIndex:) method on the node. 707 | // 708 | func testRemoveChildAtIndex() { 709 | // Build a basic tree with three levels of hierarchy. 710 | // 711 | let root = Node(Test("root", numberOfChildren: 2, parent: nil)) 712 | 713 | let A = Node(Test("A", numberOfChildren: 2, parent: "root")) 714 | let B = Node(Test("B", numberOfChildren: 0, parent: "root")) 715 | 716 | let C = Node(Test("C", numberOfChildren: 0, parent: "A")) 717 | let D = Node(Test("D", numberOfChildren: 0, parent: "A")) 718 | 719 | root.append(child: A) 720 | root.append(child: B) 721 | 722 | A.append(child: C) 723 | A.append(child: D) 724 | 725 | // Remove the A node, specifying it by index. This should make an 726 | // independent tree for A, and reparent A children to A's old parent, 727 | // at the same position as A. 728 | // 729 | let removedNode = root.remove(childAtIndex: 0) 730 | XCTAssertEqual(removedNode?.id, A.id) 731 | 732 | // Confirm that A is an independent node without any children or without 733 | // a root node. 734 | // 735 | XCTAssertNil(A.parent) 736 | XCTAssertTrue(A.children.isEmpty) 737 | 738 | // Check the children of root - it should be C, D and B in that order. 739 | // 740 | XCTAssertEqual(root.children.count, 3) 741 | XCTAssertEqual(root.children[0].id, C.id) 742 | XCTAssertEqual(root.children[1].id, D.id) 743 | XCTAssertEqual(root.children[2].id, B.id) 744 | 745 | // Check the parent of nodes C and D, it should be the root. 746 | // 747 | XCTAssertEqual(C.parent?.id, root.id) 748 | XCTAssertEqual(D.parent?.id, root.id) 749 | } 750 | 751 | /// Test the root getter on the node. 752 | /// 753 | func testRoot() { 754 | // Build a basic tree with multiple levels of hierarchy. 755 | // 756 | let root = Node(Test("root", numberOfChildren: 3, parent: nil)) 757 | 758 | let A = Node(Test("A", numberOfChildren: 2, parent: "root")) 759 | let B = Node(Test("B", numberOfChildren: 0, parent: "root")) 760 | let C = Node(Test("C", numberOfChildren: 0, parent: "root")) 761 | 762 | let D = Node(Test("D", numberOfChildren: 0, parent: "A")) 763 | let E = Node(Test("E", numberOfChildren: 1, parent: "A")) 764 | 765 | let F = Node(Test("F", numberOfChildren: 0, parent: "E")) 766 | 767 | root.append(child: A) 768 | root.append(child: B) 769 | root.append(child: C) 770 | 771 | A.append(child: D) 772 | A.append(child: E) 773 | 774 | E.append(child: F) 775 | 776 | // Given the F node, attempt to find the root of the tree. 777 | // 778 | XCTAssertEqual(F.root.id, root.id) 779 | } 780 | } 781 | --------------------------------------------------------------------------------