From 80fb30574cb33852c46243807255593c56ca7ab1 Mon Sep 17 00:00:00 2001 From: James Bean Date: Sun, 2 Sep 2018 00:03:01 -0400 Subject: [PATCH] Add BinarySearchTree (#160) --- Sources/DataStructures/BinarySearchTree.swift | 154 ++++++++++++++++++ .../BinarySearchTreeTests.swift | 17 ++ .../DataStructuresTests/XCTestManifests.swift | 7 + 3 files changed, 178 insertions(+) create mode 100644 Sources/DataStructures/BinarySearchTree.swift create mode 100644 Tests/DataStructuresTests/BinarySearchTreeTests.swift diff --git a/Sources/DataStructures/BinarySearchTree.swift b/Sources/DataStructures/BinarySearchTree.swift new file mode 100644 index 0000000..beee8c7 --- /dev/null +++ b/Sources/DataStructures/BinarySearchTree.swift @@ -0,0 +1,154 @@ +// +// BinarySearchTree.swift +// DataStructures +// +// Created by James Bean on 9/1/18. +// + +public enum BinarySearchTree { + + // MARK: - Cases + + case empty + case leaf(Value) + indirect case node(BinarySearchTree, Value, BinarySearchTree) +} + +extension BinarySearchTree { + + public init (_ sequence: S) where S: Sequence, S.Element == Value { + var tree: BinarySearchTree = .empty + sequence.forEach { tree = tree.inserting($0) } + self = tree + } +} + +extension BinarySearchTree { + + // MARK: - Computed Properties + + /// - Returns: The amount of nodes contained herein. + public var count: Int { + switch self { + case .empty: + return 0 + case .leaf: + return 1 + case .node(let left, _, let right): + return left.count + 1 + right.count + } + } + + /// - Returns: The height of this `BinarySearchTree`. + public var height: Int { + switch self { + case .empty: + return 0 + case .leaf: + return 1 + case .node(let left, _, let right): + return 1 + max(left.height, right.height) + } + } + + /// - Returns: The values of the nodes contained herein in `inOrder` (sorted) order. + /// + /// - Complexity: O(*n*) + public var inOrder: [Value] { + func traverse(_ node: BinarySearchTree, into result: [Value]) -> [Value] { + switch node { + case .empty: + return result + case .leaf(let value): + return result + [value] + case .node(let left, let value, let right): + return left.inOrder + [value] + right.inOrder + } + } + return traverse(self, into: []) + } + + /// - Returns: The left-most descendent. + public var minDescendent: BinarySearchTree { + var node = self + var prev = node + while case let .node(next, _, _) = node { + prev = node + node = next + } + if case .leaf = node { + return node + } + return prev + } + + /// - Returns: The right-most descendent. + public var maxDescendent: BinarySearchTree { + var node = self + var prev = node + while case let .node(_, _, next) = node { + prev = node + node = next + } + if case .leaf = node { + return node + } + return prev + } +} + +extension BinarySearchTree { + + // MARK: - Instance Methods + + /// - Returns: A `BinarySearchTree` with the given `newValue` inserted in the appropriate place. + public func inserting(_ newValue: Value) -> BinarySearchTree { + switch self { + case .empty: + return .leaf(newValue) + case .leaf(let value): + if newValue < value { + return .node(.leaf(newValue), value, .empty) + } else { + return .node(.empty, value, .leaf(newValue)) + } + case .node(let left, let value, let right): + if newValue < value { + return .node(left.inserting(newValue), value, right) + } else { + return .node(left, value, right.inserting(newValue)) + } + } + } + + /// - Returns: `true` if this `BinarySearchTree` contains the given `value`. Otherwise, `false`. + public func contains(_ value: Value) -> Bool { + return search(for: value) != nil + } + + /// - Returns: The `BinarySearchTree` which contains the given `target`, if it exists. + /// Otherwise, `nil`. + public func search(for target: Value) -> BinarySearchTree? { + switch self { + case .empty: + return nil + case .leaf(let value): + return target == value ? self : nil + case .node(let left, let value, let right): + if target < value { + return left.search(for: target) + } else if value < target { + return right.search(for: target) + } else { + return self + } + } + } +} + +extension BinarySearchTree: ExpressibleByArrayLiteral { + + public init(arrayLiteral elements: Value...) { + self.init(elements) + } +} diff --git a/Tests/DataStructuresTests/BinarySearchTreeTests.swift b/Tests/DataStructuresTests/BinarySearchTreeTests.swift new file mode 100644 index 0000000..f018c30 --- /dev/null +++ b/Tests/DataStructuresTests/BinarySearchTreeTests.swift @@ -0,0 +1,17 @@ +// +// BinarySearchTreeTests.swift +// DataStructuresTests +// +// Created by James Bean on 9/1/18. +// + +import XCTest +import DataStructures + +class BinarySearchTreeTests: XCTestCase { + + func testInitSequence() { + let bst: BinarySearchTree = [5,1,4,3,2,6,7] + XCTAssertEqual(bst.inOrder, [1,2,3,4,5,6,7]) + } +} diff --git a/Tests/DataStructuresTests/XCTestManifests.swift b/Tests/DataStructuresTests/XCTestManifests.swift index 0809410..e448dd2 100644 --- a/Tests/DataStructuresTests/XCTestManifests.swift +++ b/Tests/DataStructuresTests/XCTestManifests.swift @@ -44,6 +44,12 @@ extension BinaryHeapTests { ] } +extension BinarySearchTreeTests { + static let __allTests = [ + ("testInitSequence", testInitSequence), + ] +} + extension CircularArrayTests { static let __allTests = [ ("testCollection", testCollection), @@ -458,6 +464,7 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(ArrayExtensionsTests.__allTests), testCase(BimapTests.__allTests), testCase(BinaryHeapTests.__allTests), + testCase(BinarySearchTreeTests.__allTests), testCase(CircularArrayTests.__allTests), testCase(ContiguousSegmentCollectionTests.__allTests), testCase(DictionaryProtocolsTests.__allTests),