diff --git a/A-Star/AStar.swift b/A-Star/AStar.swift new file mode 100644 index 000000000..41a9fac6c --- /dev/null +++ b/A-Star/AStar.swift @@ -0,0 +1,153 @@ +// Written by Alejandro Isaza. + +import Foundation + +public protocol Graph { + associatedtype Vertex: Hashable + associatedtype Edge: WeightedEdge where Edge.Vertex == Vertex + + /// Lists all edges going out from a vertex. + func edgesOutgoing(from vertex: Vertex) -> [Edge] +} + +public protocol WeightedEdge { + associatedtype Vertex + + /// The edge's cost. + var cost: Double { get } + + /// The target vertex. + var target: Vertex { get } +} + +public final class AStar { + /// The graph to search on. + public let graph: G + + /// The heuristic cost function that estimates the cost between two vertices. + /// + /// - Note: The heuristic function needs to always return a value that is lower-than or equal to the actual + /// cost for the resulting path of the A* search to be optimal. + public let heuristic: (G.Vertex, G.Vertex) -> Double + + /// Open list of nodes to expand. + private var open: HashedHeap> + + /// Closed list of vertices already expanded. + private var closed = Set() + + /// Actual vertex cost for vertices we already encountered (refered to as `g` on the literature). + private var costs = Dictionary() + + /// Store the previous node for each expanded node to recreate the path. + private var parents = Dictionary() + + /// Initializes `AStar` with a graph and a heuristic cost function. + public init(graph: G, heuristic: @escaping (G.Vertex, G.Vertex) -> Double) { + self.graph = graph + self.heuristic = heuristic + open = HashedHeap(sort: <) + } + + /// Finds an optimal path between `source` and `target`. + /// + /// - Precondition: both `source` and `target` belong to `graph`. + public func path(start: G.Vertex, target: G.Vertex) -> [G.Vertex] { + open.insert(Node(vertex: start, cost: 0, estimate: heuristic(start, target))) + while !open.isEmpty { + guard let node = open.remove() else { + break + } + costs[node.vertex] = node.cost + + if (node.vertex == target) { + let path = buildPath(start: start, target: target) + cleanup() + return path + } + + if !closed.contains(node.vertex) { + expand(node: node, target: target) + closed.insert(node.vertex) + } + } + + // No path found + return [] + } + + private func expand(node: Node, target: G.Vertex) { + let edges = graph.edgesOutgoing(from: node.vertex) + for edge in edges { + let g = cost(node.vertex) + edge.cost + if g < cost(edge.target) { + open.insert(Node(vertex: edge.target, cost: g, estimate: heuristic(edge.target, target))) + parents[edge.target] = node.vertex + } + } + } + + private func cost(_ vertex: G.Edge.Vertex) -> Double { + if let c = costs[vertex] { + return c + } + + let node = Node(vertex: vertex, cost: Double.greatestFiniteMagnitude, estimate: 0) + if let index = open.index(of: node) { + return open[index].cost + } + + return Double.greatestFiniteMagnitude + } + + private func buildPath(start: G.Vertex, target: G.Vertex) -> [G.Vertex] { + var path = Array() + path.append(target) + + var current = target + while current != start { + guard let parent = parents[current] else { + return [] // no path found + } + current = parent + path.append(current) + } + + return path.reversed() + } + + private func cleanup() { + open.removeAll() + closed.removeAll() + parents.removeAll() + } +} + +private struct Node: Hashable, Comparable { + /// The graph vertex. + var vertex: V + + /// The actual cost between the start vertex and this vertex. + var cost: Double + + /// Estimated (heuristic) cost betweent this vertex and the target vertex. + var estimate: Double + + public init(vertex: V, cost: Double, estimate: Double) { + self.vertex = vertex + self.cost = cost + self.estimate = estimate + } + + static func < (lhs: Node, rhs: Node) -> Bool { + return lhs.cost + lhs.estimate < rhs.cost + rhs.estimate + } + + static func == (lhs: Node, rhs: Node) -> Bool { + return lhs.vertex == rhs.vertex + } + + var hashValue: Int { + return vertex.hashValue + } +} diff --git a/A-Star/Images/graph.dot b/A-Star/Images/graph.dot new file mode 100644 index 000000000..941d66232 --- /dev/null +++ b/A-Star/Images/graph.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "h = 3" ] } + { rank = same; B [ label = "h = 2" ]; C [ label = "h = 2" ]; D [ label = "h = 2" ] } + { rank = same; E [ label = "h = 1" ]; F [ label = "h = 1" ]; G [ label = "h = 1" ] } + { H [ label = "h = 0", style = filled, color = green ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/graph.png b/A-Star/Images/graph.png new file mode 100644 index 000000000..c0a4b1cc8 Binary files /dev/null and b/A-Star/Images/graph.png differ diff --git a/A-Star/Images/step1.dot b/A-Star/Images/step1.dot new file mode 100644 index 000000000..5785aea4c --- /dev/null +++ b/A-Star/Images/step1.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "g = 0\nh = 3", style = filled, color = deepskyblue1 ] } + { rank = same; B [ label = "g = 1\nh = 2", style = filled, color = lightgrey ]; C [ label = "g = 1\nh = 2", style = filled, color = lightgrey ]; D [ label = "g = 1\nh = 2", style = filled, color = lightgrey ] } + { rank = same; E [ label = "g = \?\nh = 1" ]; F [ label = "g = \?\nh = 1" ]; G [ label = "g = \?\nh = 1" ] } + { H [ label = "g = \?\nh = 0" ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/step1.png b/A-Star/Images/step1.png new file mode 100644 index 000000000..a983033a2 Binary files /dev/null and b/A-Star/Images/step1.png differ diff --git a/A-Star/Images/step2.dot b/A-Star/Images/step2.dot new file mode 100644 index 000000000..0c8a9d53d --- /dev/null +++ b/A-Star/Images/step2.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "g = 0\nh = 3", style = filled, color = lightblue ] } + { rank = same; B [ label = "g = 1\nh = 2", style = filled, color = deepskyblue1 ]; C [ label = "g = 1\nh = 2", style = filled, color = lightgrey ]; D [ label = "g = 1\nh = 2", style = filled, color = lightgrey ] } + { rank = same; E [ label = "g = 2\nh = 1", style = filled, color = lightgrey ]; F [ label = "g = \?\nh = 1" ]; G [ label = "g = \?\nh = 1" ] } + { H [ label = "g = \?\nh = 0" ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/step2.png b/A-Star/Images/step2.png new file mode 100644 index 000000000..10b4300f4 Binary files /dev/null and b/A-Star/Images/step2.png differ diff --git a/A-Star/Images/step3.dot b/A-Star/Images/step3.dot new file mode 100644 index 000000000..32a75891d --- /dev/null +++ b/A-Star/Images/step3.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "g = 0\nh = 3", style = filled, color = lightblue ] } + { rank = same; B [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; C [ label = "g = 1\nh = 2", style = filled, color = deepskyblue1 ]; D [ label = "g = 1\nh = 2", style = filled, color = lightgrey ] } + { rank = same; E [ label = "g = 2\nh = 1", style = filled, color = lightgrey ]; F [ label = "g = \?\nh = 1" ]; G [ label = "g = \?\nh = 1" ] } + { H [ label = "g = \?\nh = 0" ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/step3.png b/A-Star/Images/step3.png new file mode 100644 index 000000000..e195e7e8e Binary files /dev/null and b/A-Star/Images/step3.png differ diff --git a/A-Star/Images/step4.dot b/A-Star/Images/step4.dot new file mode 100644 index 000000000..16db76796 --- /dev/null +++ b/A-Star/Images/step4.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "g = 0\nh = 3", style = filled, color = lightblue ] } + { rank = same; B [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; C [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; D [ label = "g = 1\nh = 2", style = filled, color = deepskyblue1 ] } + { rank = same; E [ label = "g = 2\nh = 1", style = filled, color = lightgrey ]; F [ label = "g = \?\nh = 1" ]; G [ label = "g = 2\nh = 1", style = filled, color = lightgrey ] } + { H [ label = "g = \?\nh = 0" ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/step4.png b/A-Star/Images/step4.png new file mode 100644 index 000000000..c07f34c80 Binary files /dev/null and b/A-Star/Images/step4.png differ diff --git a/A-Star/Images/step5.dot b/A-Star/Images/step5.dot new file mode 100644 index 000000000..2986b6a90 --- /dev/null +++ b/A-Star/Images/step5.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "g = 0\nh = 3", style = filled, color = lightblue ] } + { rank = same; B [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; C [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; D [ label = "g = 1\nh = 2", style = filled, color = lightblue ] } + { rank = same; E [ label = "g = 2\nh = 1", style = filled, color = deepskyblue1 ]; F [ label = "g = 3\nh = 1", style = filled, color = lightgrey ]; G [ label = "g = 2\nh = 1", style = filled, color = lightgrey ] } + { H [ label = "g = \?\nh = 0" ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/step5.png b/A-Star/Images/step5.png new file mode 100644 index 000000000..40a7008da Binary files /dev/null and b/A-Star/Images/step5.png differ diff --git a/A-Star/Images/step6.dot b/A-Star/Images/step6.dot new file mode 100644 index 000000000..b5e3179b6 --- /dev/null +++ b/A-Star/Images/step6.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "g = 0\nh = 3", style = filled, color = lightblue ] } + { rank = same; B [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; C [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; D [ label = "g = 1\nh = 2", style = filled, color = lightblue ] } + { rank = same; E [ label = "g = 2\nh = 1", style = filled, color = lightblue ]; F [ label = "g = 3\nh = 1", style = filled, color = lightgrey ]; G [ label = "g = 2\nh = 1", style = filled, color = deepskyblue1 ] } + { H [ label = "g = 3\nh = 0", style = filled, color = lightgrey ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/step6.png b/A-Star/Images/step6.png new file mode 100644 index 000000000..9e7baef26 Binary files /dev/null and b/A-Star/Images/step6.png differ diff --git a/A-Star/Images/step7.dot b/A-Star/Images/step7.dot new file mode 100644 index 000000000..c5b26b6b3 --- /dev/null +++ b/A-Star/Images/step7.dot @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + { A [ label = "g = 0\nh = 3", style = filled, color = lightblue ] } + { rank = same; B [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; C [ label = "g = 1\nh = 2", style = filled, color = lightblue ]; D [ label = "g = 1\nh = 2", style = filled, color = lightblue ] } + { rank = same; E [ label = "g = 2\nh = 1", style = filled, color = lightblue ]; F [ label = "g = 3\nh = 1", style = filled, color = lightgrey ]; G [ label = "g = 2\nh = 1", style = filled, color = lightblue ] } + { H [ label = "g = 3\nh = 0", style = filled, color = deepskyblue1 ] } + A -> { B C D } + B -> E + E -> F + D -> G + G -> H +} diff --git a/A-Star/Images/step7.png b/A-Star/Images/step7.png new file mode 100644 index 000000000..0eedd638f Binary files /dev/null and b/A-Star/Images/step7.png differ diff --git a/A-Star/README.md b/A-Star/README.md new file mode 100644 index 000000000..b373d25fc --- /dev/null +++ b/A-Star/README.md @@ -0,0 +1,43 @@ +# A* + +A* (pronounced "ay star") is a heuristic best-first search algorithm. A* minimizes node expansions, therefore minimizing the search time, by using a heuristic function. The heuristic function gives an estimate of the distance between two vertices. For instance if you are searching for a path between two points in a city, you can estimate the actual street distance with the straight-line distance. + +A* works by expanding the most promising nodes first, according to the heuristic function. In the city example it would choose streets which go in the general direction of the target first and, only if those are dead ends, backtrack and try other streets. This speeds up search in most sitations. + +A* is optimal (it always find the shortest path) if the heuristic function is admissible. A heuristic function is admissible if it never overestimates the cost of reaching the goal. In the extreme case of the heuristic function always retuning `0` A* acts exactly the same as [Dijkstra's Algorithm](../Dijkstra). The closer the heuristic function is to the actual distance the faster the search. + +## Example + +Let's run through an example on this simple directed graph. We are going to assume that all edges have a cost of 1 and the heuristic function is going to be the column starting at goal and going back: + +![Graph](Images/graph.png) + +On the first step we expand the root node on the left (blue). We set the cost `g` to zero and add all neighbors to the open list (grey). + +![Step 1](Images/step1.png) + +We put the first node in the closed list (light blue) so that we don't try expanding it again if there were to be loops in the graph. Next we take the node on the open list with the smallest value of `g + h` where `g` is the current cost (0) plus the edge cost (1). Since all nodes in the open list have the same value we choose the top one. + +![Step 2](Images/step2.png) + +We repeat the process and pick the next node from the open list. In this case there are no new nodes to add to the open list. + +![Step 3](Images/step3.png) + +We expand the next node. One more node on the open list, but nothing exciting yet. + +![Step 4](Images/step4.png) + +Sicne the top and the bottom nodes have the same value we choose the top one. This is not a great choice but we could do better if we had a better heuristic function. + +![Step 5](Images/step5.png) + +Now we expand the bottom node because it has a smaller value than the middle node (2 + 1 < 3 + 1). + +![Step 6](Images/step6.png) + +And we finally reach the goal! We never even expanded that middle node. We didn't have to because its value is 4, which is equal to the total lenght of our solution and therefore guaranteed to not be part of the optimal solution. + +![Step 7](Images/step7.png) + +The final step is to backtrack from the goal node to buld the optimal path. diff --git a/A-Star/Tests/AStarTests.swift b/A-Star/Tests/AStarTests.swift new file mode 100755 index 000000000..87f674cd5 --- /dev/null +++ b/A-Star/Tests/AStarTests.swift @@ -0,0 +1,57 @@ +import Foundation +import XCTest + +struct GridGraph: Graph { + struct Vertex: Hashable { + var x: Int + var y: Int + + static func == (lhs: Vertex, rhs: Vertex) -> Bool { + return lhs.x == rhs.x && lhs.y == rhs.y + } + + public var hashValue: Int { + return x.hashValue ^ y.hashValue + } + } + + struct Edge: WeightedEdge { + var cost: Double + var target: Vertex + } + + func edgesOutgoing(from vertex: Vertex) -> [Edge] { + return [ + Edge(cost: 1, target: Vertex(x: vertex.x - 1, y: vertex.y)), + Edge(cost: 1, target: Vertex(x: vertex.x + 1, y: vertex.y)), + Edge(cost: 1, target: Vertex(x: vertex.x, y: vertex.y - 1)), + Edge(cost: 1, target: Vertex(x: vertex.x, y: vertex.y + 1)), + ] + } +} + +class AStarTests: XCTestCase { + func testSameStartAndEnd() { + let graph = GridGraph() + let astar = AStar(graph: graph, heuristic: manhattanDistance) + let path = astar.path(start: GridGraph.Vertex(x: 0, y: 0), target: GridGraph.Vertex(x: 0, y: 0)) + XCTAssertEqual(path.count, 1) + XCTAssertEqual(path[0].x, 0) + XCTAssertEqual(path[0].y, 0) + } + + func testDiagonal() { + let graph = GridGraph() + let astar = AStar(graph: graph, heuristic: manhattanDistance) + let path = astar.path(start: GridGraph.Vertex(x: 0, y: 0), target: GridGraph.Vertex(x: 10, y: 10)) + XCTAssertEqual(path.count, 21) + XCTAssertEqual(path[0].x, 0) + XCTAssertEqual(path[0].y, 0) + XCTAssertEqual(path[20].x, 10) + XCTAssertEqual(path[20].y, 10) + } + + func manhattanDistance(_ s: GridGraph.Vertex, _ t: GridGraph.Vertex) -> Double { + return Double(abs(s.x - t.x) + abs(s.y - t.y)) + } +} diff --git a/A-Star/Tests/AStarTests.xcodeproj/project.pbxproj b/A-Star/Tests/AStarTests.xcodeproj/project.pbxproj new file mode 100644 index 000000000..cacd1e5ae --- /dev/null +++ b/A-Star/Tests/AStarTests.xcodeproj/project.pbxproj @@ -0,0 +1,291 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 611D099C1F8978AB00C7092B /* AStarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611D099B1F8978AB00C7092B /* AStarTests.swift */; }; + 611D099E1F8978BC00C7092B /* AStar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611D099D1F8978BB00C7092B /* AStar.swift */; }; + 611D09A01F89795100C7092B /* HashedHeap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 611D099F1F89795100C7092B /* HashedHeap.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 611D099B1F8978AB00C7092B /* AStarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AStarTests.swift; sourceTree = ""; }; + 611D099D1F8978BB00C7092B /* AStar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AStar.swift; path = ../AStar.swift; sourceTree = ""; }; + 611D099F1F89795100C7092B /* HashedHeap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HashedHeap.swift; path = "../../Hashed Heap/HashedHeap.swift"; sourceTree = ""; }; + 7B2BBC801C779D720067B71D /* AStarTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AStarTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B2BBC941C779E7B0067B71D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7B2BBC7D1C779D720067B71D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 7B2BBC681C779D710067B71D = { + isa = PBXGroup; + children = ( + 7B2BBC831C779D720067B71D /* Tests */, + 7B2BBC721C779D710067B71D /* Products */, + ); + sourceTree = ""; + }; + 7B2BBC721C779D710067B71D /* Products */ = { + isa = PBXGroup; + children = ( + 7B2BBC801C779D720067B71D /* AStarTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 7B2BBC831C779D720067B71D /* Tests */ = { + isa = PBXGroup; + children = ( + 611D099F1F89795100C7092B /* HashedHeap.swift */, + 611D099D1F8978BB00C7092B /* AStar.swift */, + 611D099B1F8978AB00C7092B /* AStarTests.swift */, + 7B2BBC941C779E7B0067B71D /* Info.plist */, + ); + name = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7B2BBC7F1C779D720067B71D /* AStarTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "AStarTests" */; + buildPhases = ( + 7B2BBC7C1C779D720067B71D /* Sources */, + 7B2BBC7D1C779D720067B71D /* Frameworks */, + 7B2BBC7E1C779D720067B71D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AStarTests; + productName = TestsTests; + productReference = 7B2BBC801C779D720067B71D /* AStarTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7B2BBC691C779D710067B71D /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0720; + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = "Swift Algorithm Club"; + TargetAttributes = { + 7B2BBC7F1C779D720067B71D = { + CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0900; + }; + }; + }; + buildConfigurationList = 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "AStarTests" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7B2BBC681C779D710067B71D; + productRefGroup = 7B2BBC721C779D710067B71D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7B2BBC7F1C779D720067B71D /* AStarTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7B2BBC7E1C779D720067B71D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7B2BBC7C1C779D720067B71D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 611D099C1F8978AB00C7092B /* AStarTests.swift in Sources */, + 611D099E1F8978BC00C7092B /* AStar.swift in Sources */, + 611D09A01F89795100C7092B /* HashedHeap.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7B2BBC871C779D720067B71D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + }; + name = Debug; + }; + 7B2BBC881C779D720067B71D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + }; + name = Release; + }; + 7B2BBC8D1C779D720067B71D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 7B2BBC8E1C779D720067B71D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "AStarTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7B2BBC871C779D720067B71D /* Debug */, + 7B2BBC881C779D720067B71D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "AStarTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7B2BBC8D1C779D720067B71D /* Debug */, + 7B2BBC8E1C779D720067B71D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7B2BBC691C779D710067B71D /* Project object */; +} diff --git a/A-Star/Tests/AStarTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/A-Star/Tests/AStarTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..6c0ea8493 --- /dev/null +++ b/A-Star/Tests/AStarTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/A-Star/Tests/AStarTests.xcodeproj/xcshareddata/xcschemes/AStarTests.xcscheme b/A-Star/Tests/AStarTests.xcodeproj/xcshareddata/xcschemes/AStarTests.xcscheme new file mode 100644 index 000000000..5473b4c0c --- /dev/null +++ b/A-Star/Tests/AStarTests.xcodeproj/xcshareddata/xcschemes/AStarTests.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/A-Star/Tests/Info.plist b/A-Star/Tests/Info.plist new file mode 100644 index 000000000..ba72822e8 --- /dev/null +++ b/A-Star/Tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/B-Tree/BTree.playground/Sources/BTree.swift b/B-Tree/BTree.playground/Sources/BTree.swift index 2693eb308..2ed0b2593 100644 --- a/B-Tree/BTree.playground/Sources/BTree.swift +++ b/B-Tree/BTree.playground/Sources/BTree.swift @@ -86,7 +86,7 @@ extension BTreeNode { } } -// MARK: BTreeNode extension: Travelsals +// MARK: BTreeNode extension: Traversals extension BTreeNode { @@ -443,7 +443,7 @@ public class BTree { } } -// MARK: BTree extension: Travelsals +// MARK: BTree extension: Traversals extension BTree { /** diff --git a/Binary Search Tree/README.markdown b/Binary Search Tree/README.markdown index 0b382f204..57d1f4bff 100644 --- a/Binary Search Tree/README.markdown +++ b/Binary Search Tree/README.markdown @@ -557,7 +557,7 @@ As a result, doing `tree.search(100)` gives nil. You can check whether a tree is a valid binary search tree with the following method: ```swift - public func isBST(minValue minValue: T, maxValue: T) -> Bool { + public func isBST(minValue: T, maxValue: T) -> Bool { if value < minValue || value > maxValue { return false } let leftBST = left?.isBST(minValue: minValue, maxValue: value) ?? true let rightBST = right?.isBST(minValue: value, maxValue: maxValue) ?? true diff --git a/Bloom Filter/BloomFilter.playground/Contents.swift b/Bloom Filter/BloomFilter.playground/Contents.swift index 7a3719d57..8ed808a06 100644 --- a/Bloom Filter/BloomFilter.playground/Contents.swift +++ b/Bloom Filter/BloomFilter.playground/Contents.swift @@ -79,4 +79,4 @@ bloom.insert("Bloom Filterz") print(bloom.array) bloom.query("Bloom Filterz") // true -bloom.query("Hello WORLD") // true +bloom.query("Hello WORLD") // false or true: It may return true due to a false positive, but it's not guaranteed. diff --git a/Count Occurrences/README.markdown b/Count Occurrences/README.markdown index 297bc4b0f..8e935ea58 100644 --- a/Count Occurrences/README.markdown +++ b/Count Occurrences/README.markdown @@ -25,7 +25,7 @@ In code this looks as follows: func countOccurrences(of key: T, in array: [T]) -> Int { var leftBoundary: Int { var low = 0 - var high = a.count + var high = array.count while low < high { let midIndex = low + (high - low)/2 if a[midIndex] < key { @@ -39,7 +39,7 @@ func countOccurrences(of key: T, in array: [T]) -> Int { var rightBoundary: Int { var low = 0 - var high = a.count + var high = array.count while low < high { let midIndex = low + (high - low)/2 if a[midIndex] > key { @@ -62,7 +62,7 @@ To test this algorithm, copy the code to a playground and then do: ```swift let a = [ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ] -countOccurrencesOfKey(3, inArray: a) // returns 4 +countOccurrences(of: 3, in: a) // returns 4 ``` > **Remember:** If you use your own array, make sure it is sorted first! diff --git a/CounterClockWise/CounterClockWise.playground/Contents.swift b/CounterClockWise/CounterClockWise.playground/Contents.swift new file mode 100644 index 000000000..ffa91ecbb --- /dev/null +++ b/CounterClockWise/CounterClockWise.playground/Contents.swift @@ -0,0 +1,76 @@ +/* + CounterClockWise(CCW) Algorithm + The user cross-multiplies corresponding coordinates to find the area encompassing the polygon, + and subtracts it from the surrounding polygon to find the area of the polygon within. + This code is based on the "Shoelace formula" by Carl Friedrich Gauss + https://en.wikipedia.org/wiki/Shoelace_formula + */ + +import Foundation + +// MARK : Point struct for defining 2-D coordinate(x,y) +public struct Point{ + // Coordinate(x,y) + var x: Int + var y: Int + + public init(x: Int ,y: Int){ + self.x = x + self.y = y + } +} + +// MARK : Function that determine the area of a simple polygon whose vertices are described +// by their Cartesian coordinates in the plane. +func ccw(points: [Point]) -> Int{ + let polygon = points.count + var orientation = 0 + + // Take the first x-coordinate and multiply it by the second y-value, + // then take the second x-coordinate and multiply it by the third y-value, + // and repeat as many times until it is done for all wanted points. + for i in 0.. 0 : CounterClockWise + } +} + +// A few simple tests + + +// Triangle +var p1 = Point(x: 5, y: 8) +var p2 = Point(x: 9, y: 1) +var p3 = Point(x: 3, y: 6) + +print(ccw(points: [p1,p2,p3])) // -1 means ClockWise + +// Quadrilateral +var p4 = Point(x: 5, y: 8) +var p5 = Point(x: 2, y: 3) +var p6 = Point(x: 6, y: 1) +var p7 = Point(x: 9, y: 3) + +print(ccw(points: [p4,p5,p6,p7])) // 1 means CounterClockWise + +// Pentagon +var p8 = Point(x: 5, y: 11) +var p9 = Point(x: 3, y: 4) +var p10 = Point(x: 5, y: 6) +var p11 = Point(x: 9, y: 5) +var p12 = Point(x: 12, y: 8) + +print(ccw(points: [p8,p9,p10,p11,p12])) // 1 means CounterClockWise diff --git a/Fizz Buzz/FizzBuzz.playground/contents.xcplayground b/CounterClockWise/CounterClockWise.playground/contents.xcplayground similarity index 59% rename from Fizz Buzz/FizzBuzz.playground/contents.xcplayground rename to CounterClockWise/CounterClockWise.playground/contents.xcplayground index 5da2641c9..9f5f2f40c 100644 --- a/Fizz Buzz/FizzBuzz.playground/contents.xcplayground +++ b/CounterClockWise/CounterClockWise.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/contents.xcworkspacedata b/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/contents.xcworkspacedata rename to CounterClockWise/CounterClockWise.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to CounterClockWise/CounterClockWise.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/CounterClockWise/CounterClockWise.swift b/CounterClockWise/CounterClockWise.swift new file mode 100644 index 000000000..07c422ea5 --- /dev/null +++ b/CounterClockWise/CounterClockWise.swift @@ -0,0 +1,50 @@ +/* + CounterClockWise(CCW) Algorithm + The user cross-multiplies corresponding coordinates to find the area encompassing the polygon, + and subtracts it from the surrounding polygon to find the area of the polygon within. + This code is based on the "Shoelace formula" by Carl Friedrich Gauss + https://en.wikipedia.org/wiki/Shoelace_formula + */ + + +import Foundation + +// MARK : Point struct for defining 2-D coordinate(x,y) +public struct Point{ + // Coordinate(x,y) + var x: Int + var y: Int + + public init(x: Int ,y: Int){ + self.x = x + self.y = y + } +} + +// MARK : Function that determine the area of a simple polygon whose vertices are described +// by their Cartesian coordinates in the plane. +func ccw(points: [Point]) -> Int{ + let polygon = points.count + var orientation = 0 + + // Take the first x-coordinate and multiply it by the second y-value, + // then take the second x-coordinate and multiply it by the third y-value, + // and repeat as many times until it is done for all wanted points. + for i in 0.. 0 : CounterClockWise + } +} diff --git a/CounterClockWise/Images/Pentagon_img.png b/CounterClockWise/Images/Pentagon_img.png new file mode 100644 index 000000000..44d858303 Binary files /dev/null and b/CounterClockWise/Images/Pentagon_img.png differ diff --git a/CounterClockWise/Images/Quadrilateral_img.jpg b/CounterClockWise/Images/Quadrilateral_img.jpg new file mode 100644 index 000000000..b48d7ee5a Binary files /dev/null and b/CounterClockWise/Images/Quadrilateral_img.jpg differ diff --git a/CounterClockWise/Images/Shoelace.png b/CounterClockWise/Images/Shoelace.png new file mode 100644 index 000000000..2691ec129 Binary files /dev/null and b/CounterClockWise/Images/Shoelace.png differ diff --git a/CounterClockWise/Images/Triangle_img.jpg b/CounterClockWise/Images/Triangle_img.jpg new file mode 100644 index 000000000..8a010ca15 Binary files /dev/null and b/CounterClockWise/Images/Triangle_img.jpg differ diff --git a/CounterClockWise/Images/pentagon.png b/CounterClockWise/Images/pentagon.png new file mode 100644 index 000000000..063c77900 Binary files /dev/null and b/CounterClockWise/Images/pentagon.png differ diff --git a/CounterClockWise/Images/quadrilateral.png b/CounterClockWise/Images/quadrilateral.png new file mode 100644 index 000000000..139d09778 Binary files /dev/null and b/CounterClockWise/Images/quadrilateral.png differ diff --git a/CounterClockWise/Images/triangle.png b/CounterClockWise/Images/triangle.png new file mode 100644 index 000000000..67e8b453f Binary files /dev/null and b/CounterClockWise/Images/triangle.png differ diff --git a/CounterClockWise/README.md b/CounterClockWise/README.md new file mode 100644 index 000000000..2d15eba53 --- /dev/null +++ b/CounterClockWise/README.md @@ -0,0 +1,118 @@ +# CounterClockWise + +Goal : Determine what direction to take when multiple points are given. + +CounterClockWise(CCW) is based on [Shoelace formula](https://en.wikipedia.org/wiki/Shoelace_formula#Examples) by Carl Friedrich Gauss. + + + +1. Take the first x-coordinate and multiply it by the second y-value, then take the second x-coordinate and multiply it by the third y-value, and repeat as many times until it is done for all wanted points. +2. If the points are labeled sequentially in the counterclockwise direction, then the sum of the above determinants is positive and the absolute value signs can be omitted if they are labeled in the clockwise direction, the sum of the determinants will be negative. This is because the formula can be viewed as a special case of [Green's theorem](https://en.wikipedia.org/wiki/Green%27s_theorem). + +![Shoelace](./Images/Shoelace.png) + + + +Here's an implementation in Swift that should be easy to understand: + +```swift +func ccw(points: [Point]) -> Int{ + let polygon = points.count + var orientation = 0 + + for i in 0.. 0 : CounterClockWise + } +} +``` + +Put this code in a playground and test it like so: + +```swift +var p1 = Point(x: 5, y: 8) +var p2 = Point(x: 9, y: 1) +var p3 = Point(x: 3, y: 6) + +print(ccw(points: [p1,p2,p3])) // -1 means ClockWise +``` + +Here's how it works. When given an `[Photo]`, `ccw(points:)` calculates the direction of the given points according to the Shoelaces formula. + + + +`orientation` is less than 0, the direction is clockwise. + +`orientation` is equal to 0, the direction is parallel. + +`orientation` is greater than 0, the direction is counterclockwise. + + + +## An example + +**In Triangle** + +```swift +var p1 = Point(x: 5, y: 8) +var p2 = Point(x: 9, y: 1) +var p3 = Point(x: 3, y: 6) + +print(ccw(points: [p1,p2,p3])) // -1 means ClockWise +``` + +![triangle](./Images/Triangle_img.jpg) + +![triangleExpression](./Images/triangle.png) + + + +**In Quadrilateral** + +```swift +var p4 = Point(x: 5, y: 8) +var p5 = Point(x: 2, y: 3) +var p6 = Point(x: 6, y: 1) +var p7 = Point(x: 9, y: 3) + +print(ccw(points: [p4,p5,p6,p7])) // 1 means CounterClockWise +``` + +![Quadrilateral](./Images/Quadrilateral_img.jpg) + +![triangleExpression](./Images/quadrilateral.png) + + + +**In Pentagon** + +```swift +var p8 = Point(x: 5, y: 11) +var p9 = Point(x: 3, y: 4) +var p10 = Point(x: 5, y: 6) +var p11 = Point(x: 9, y: 5) +var p12 = Point(x: 12, y: 8) + +print(ccw(points: [p8,p9,p10,p11,p12])) // 1 means CounterClockWise +``` + +![triangle](./Images/Pentagon_img.png) + +![triangleExpression](./Images/pentagon.png) + + + +You probably won't need to use the CCW in any real-world problems, but it's cool to play around with geometry algorithm. The formula was described by Meister (1724-1788) in 1769 and by Gauss in 1795. It can be verified by dividing the polygon into triangles, and can be considered to be a special case of Green's theorem. + + + +*Written for Swift Algorithm Club by TaeJoong Yoon* diff --git a/Counting Sort/CountingSort.playground/Contents.swift b/Counting Sort/CountingSort.playground/Contents.swift index dc375512a..99178f85d 100644 --- a/Counting Sort/CountingSort.playground/Contents.swift +++ b/Counting Sort/CountingSort.playground/Contents.swift @@ -29,12 +29,17 @@ func countingSort(array: [Int]) throws -> [Int] { // Step 3 // Place the element in the final array as per the number of elements before it + // Loop through the array in reverse to keep the stability of the new array + // i.e. 7, is at index 3 and 6, thus in sortedArray the position of 7 at index 3 should be before 7 at index 6 var sortedArray = [Int](repeating: 0, count: array.count) - for element in array { + for index in stride(from: array.count - 1, through: 0, by: -1) { + let element = array[index] countArray[element] -= 1 sortedArray[countArray[element]] = element } + return sortedArray } try countingSort(array: [10, 9, 8, 7, 1, 2, 7, 3]) + diff --git a/Counting Sort/CountingSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Counting Sort/CountingSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Counting Sort/CountingSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Counting Sort/CountingSort.swift b/Counting Sort/CountingSort.swift index 725a634a4..68dcf5c0d 100644 --- a/Counting Sort/CountingSort.swift +++ b/Counting Sort/CountingSort.swift @@ -29,8 +29,11 @@ func countingSort(_ array: [Int])-> [Int] { // Step 3 // Place the element in the final array as per the number of elements before it + // Loop through the array in reverse to keep the stability of the new sorted array + // (For Example: 7 is at index 3 and 6, thus in sortedArray the position of 7 at index 3 should be before 7 at index 6 var sortedArray = [Int](repeating: 0, count: array.count) - for element in array { + for index in stride(from: array.count - 1, through: 0, by: -1) { + let element = array[index] countArray[element] -= 1 sortedArray[countArray[element]] = element } diff --git a/Counting Sort/README.markdown b/Counting Sort/README.markdown index 959becc8c..93d6635bd 100644 --- a/Counting Sort/README.markdown +++ b/Counting Sort/README.markdown @@ -50,7 +50,9 @@ The code for step 2 is: ### Step 3: -This is the last step in the algorithm. Each element in the original array is placed at the position defined by the output of step 2. For example, the number 10 would be placed at an index of 7 in the output array. Also, as you place the elements you need to reduce the count by 1 as those many elements are reduced from the array. +This is the last step in the algorithm. Each element in the original array is placed at the position defined by the output of step 2. For example, the number 10 would be placed at an index of 7 in the output array. Also, as you place the elements you need to reduce the count by 1 as those many elements are reduced from the array. +We also have to loop through the array in reverse to keep the stability of the new sorted array. +For Example: 7 is at index 3 and 6, thus in sortedArray the position of 7 at index 3 should be before 7 at index 6. The final output would be: @@ -63,7 +65,8 @@ Here is the code for this final step: ```swift var sortedArray = [Int](repeating: 0, count: array.count) - for element in array { + for index in stride(from: array.count - 1, through: 0, by: -1) { + let element = array[index] countArray[element] -= 1 sortedArray[countArray[element]] = element } diff --git a/Counting Sort/Tests/Tests.xcodeproj/project.pbxproj b/Counting Sort/Tests/Tests.xcodeproj/project.pbxproj index 5cbb0d4aa..aaa6c1b6b 100644 --- a/Counting Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Counting Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -226,7 +227,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -238,7 +239,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Counting Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Counting Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Counting Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Fizz Buzz/FizzBuzz.playground/Contents.swift b/Fizz Buzz/FizzBuzz.playground/Contents.swift index ae966bbcc..2ab13b362 100644 --- a/Fizz Buzz/FizzBuzz.playground/Contents.swift +++ b/Fizz Buzz/FizzBuzz.playground/Contents.swift @@ -1,23 +1,23 @@ -// last checked with Xcode 10.0 (10A255) +// Last checked with Xcode Version 11.4.1 (11E503a) func fizzBuzz(_ numberOfTurns: Int) { - for i in 1...numberOfTurns { - var result = "" - - if i % 3 == 0 { - result += "Fizz" + guard numberOfTurns >= 1 else { + print("Number of turns must be >= 1") + return } - - if i % 5 == 0 { - result += (result.isEmpty ? "" : " ") + "Buzz" + + for i in 1...numberOfTurns { + switch (i.isMultiple(of: 3), i.isMultiple(of: 5)) { + case (false, false): + print("\(i)") + case (true, false): + print("Fizz") + case (false, true): + print("Buzz") + case (true, true): + print("Fizz Buzz") + } } - - if result.isEmpty { - result += "\(i)" - } - - print(result) - } } -fizzBuzz(100) +fizzBuzz(15) diff --git a/Fizz Buzz/FizzBuzz.swift b/Fizz Buzz/FizzBuzz.swift index b6bb410e7..bf753e648 100644 --- a/Fizz Buzz/FizzBuzz.swift +++ b/Fizz Buzz/FizzBuzz.swift @@ -1,19 +1,21 @@ -func fizzBuzz(_ numberOfTurns: Int) { - for i in 1...numberOfTurns { - var result = "" - - if i % 3 == 0 { - result += "Fizz" - } +// Last checked with Xcode Version 11.4.1 (11E503a) - if i % 5 == 0 { - result += (result.isEmpty ? "" : " ") + "Buzz" +func fizzBuzz(_ numberOfTurns: Int) { + guard numberOfTurns >= 1 else { + print("Number of turns must be >= 1") + return } - - if result.isEmpty { - result += "\(i)" + + for i in 1...numberOfTurns { + switch (i.isMultiple(of: 3), i.isMultiple(of: 5)) { + case (false, false): + print("\(i)") + case (true, false): + print("Fizz") + case (false, true): + print("Buzz") + case (true, true): + print("Fizz Buzz") + } } - - print(result) - } -} +} \ No newline at end of file diff --git a/Fizz Buzz/README.markdown b/Fizz Buzz/README.markdown index 8c250da27..be516bd46 100644 --- a/Fizz Buzz/README.markdown +++ b/Fizz Buzz/README.markdown @@ -16,46 +16,48 @@ The modulus operator `%` is the key to solving fizz buzz. The modulus operator returns the remainder after an integer division. Here is an example of the modulus operator: -| Division | Division Result | Modulus | Modulus Result | -| ------------- | -------------------------- | --------------- | ---------------:| -| 1 / 3 | 0 with a remainder of 3 | 1 % 3 | 1 | -| 5 / 3 | 1 with a remainder of 2 | 5 % 3 | 2 | -| 16 / 3 | 5 with a remainder of 1 | 16 % 3 | 1 | +| Division | Division Result | Modulus | Modulus Result| +| ----------- | --------------------- | ------------- | :-----------: | +| 1 / 3 | 0 with a remainder of 3 | 1 % 3 | 1 | +| 5 / 3 | 1 with a remainder of 2 | 5 % 3 | 2 | +| 16 / 3 | 5 with a remainder of 1 | 16 % 3 | 1 | A common approach to determine if a number is even or odd is to use the modulus operator: -| Modulus | Result | Swift Code | Swift Code Result | Comment | -| ------------- | ---------------:| ------------------------------- | -----------------:| --------------------------------------------- | -| 6 % 2 | 0 | `let isEven = (number % 2 == 0)` | `true` | If a number is divisible by 2 it is *even* | -| 5 % 2 | 1 | `let isOdd = (number % 2 != 0)` | `true` | If a number is not divisible by 2 it is *odd* | +| Modulus | Result | Swift Code | Swift Code
Result | Comment | +| -------- | :-----:| -------------------------------- | :----------------:| --------------------------------------------- | +| 6 % 2 | 0 | `let isEven = (number % 2 == 0)` | `true` | If a number is divisible by 2 it is *even* | +| 5 % 2 | 1 | `let isOdd = (number % 2 != 0)` | `true` | If a number is not divisible by 2 it is *odd* | + +Alternatively, Swift's built in function .isMultiple(of:) can be used, i.e. 6.isMultiple(of: 2) will return true, 5.isMultiple(of: 2) will return false ## Solving fizz buzz -Now we can use the modulus operator `%` to solve fizz buzz. +Now we can use the modulus operator `%` or .isMultiple(of:) method to solve fizz buzz. Finding numbers divisible by three: -| Modulus | Modulus Result | Swift Code | Swift Code Result | -| ------- | --------------:| ------------- |------------------:| -| 1 % 3 | 1 | `1 % 3 == 0` | `false` | -| 2 % 3 | 2 | `2 % 3 == 0` | `false` | -| 3 % 3 | 0 | `3 % 3 == 0` | `true` | -| 4 % 3 | 1 | `4 % 3 == 0` | `false` | +| Modulus | Modulus
Result | Swift Code
using Modulo | Swift Code
using .isMultiple(of:) | Swift Code
Result | +| ------- | :---------------: | -------------------------- | ------------------------------------ | ------------------- | +|1 % 3 | 1 | `1 % 3 == 0` | `1.isMultiple(of: 3)` | `false` | +|2 % 3 | 2 | `2 % 3 == 0` | `2.isMultiple(of: 3)` | `false` | +|3 % 3 | 0 | `3 % 3 == 0` | `3.isMultiple(of: 3)` | `true` | +|4 % 3 | 1 | `4 % 3 == 0` | `4.isMultiple(of: 3)` | `false` | Finding numbers divisible by five: -| Modulus | Modulus Result | Swift Code | Swift Code Result | -| ------- | --------------:| ------------- |------------------:| -| 1 % 5 | 1 | `1 % 5 == 0` | `false` | -| 2 % 5 | 2 | `2 % 5 == 0` | `false` | -| 3 % 5 | 3 | `3 % 5 == 0` | `false` | -| 4 % 5 | 4 | `4 % 5 == 0` | `false` | -| 5 % 5 | 0 | `5 % 5 == 0` | `true` | -| 6 % 5 | 1 | `6 % 5 == 0` | `false` | +| Modulus | Modulus
Result | Swift Code
using Modulo | Swift Code
using .isMultiple(of:) | Swift Code
Result | +| ------- | :---------------: | -------------------------- | ------------------------------------ | -------------------- | +| 1 % 5 | 1 | `1 % 5 == 0` | `1.isMultiple(of: 5)` | `false` | +| 2 % 5 | 2 | `2 % 5 == 0` | `2.isMultiple(of: 5)` | `false` | +| 3 % 5 | 3 | `3 % 5 == 0` | `3.isMultiple(of: 5)` | `false` | +| 4 % 5 | 4 | `4 % 5 == 0` | `4.isMultiple(of: 5)` | `false` | +| 5 % 5 | 0 | `5 % 5 == 0` | `5.isMultiple(of: 5)` | `true` | +| 6 % 5 | 1 | `6 % 5 == 0` | `6.isMultiple(of: 5)` | `false` | ## The code -Here is a simple implementation in Swift: +Here is a simple implementation in Swift using Modulus approach ```swift func fizzBuzz(_ numberOfTurns: Int) { @@ -79,7 +81,31 @@ func fizzBuzz(_ numberOfTurns: Int) { } ``` -Put this code in a playground and test it like so: +Here is simple implementation in Swift using .isMultiple(of:) and switch statement + +```swift +func fizzBuzz(_ numberOfTurns: Int) { + guard numberOfTurns >= 1 else { + print("Number of turns must be >= 1") + return + } + + for i in 1...numberOfTurns { + switch (i.isMultiple(of: 3), i.isMultiple(of: 5)) { + case (false, false): + print("\(i)") + case (true, false): + print("Fizz") + case (false, true): + print("Buzz") + case (true, true): + print("Fizz Buzz") + } + } +} +``` + +Put either code in a playground and test it like so: ```swift fizzBuzz(15) @@ -87,10 +113,25 @@ fizzBuzz(15) This will output: - 1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, Fizz Buzz +1 +2 +Fizz +4 +Buzz +Fizz +7 +8 +Fizz +Buzz +11 +Fizz +13 +14 +Fizz Buzz ## See also [Fizz buzz on Wikipedia](https://en.wikipedia.org/wiki/Fizz_buzz) -*Written by [Chris Pilcher](https://github.com/chris-pilcher)* +*Originally written by [Chris Pilcher](https://github.com/chris-pilcher)*
+*Updated by [Lance Rettberg](https://github.com/l-rettberg)* diff --git a/Graph/Graph.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Graph/Graph.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Graph/Graph.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Graph/Graph.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Graph/Graph.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Graph/Graph.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Graph/Graph/Edge.swift b/Graph/Graph/Edge.swift index ac5ed6a25..9ddd4e23d 100644 --- a/Graph/Graph/Edge.swift +++ b/Graph/Graph/Edge.swift @@ -29,13 +29,14 @@ extension Edge: CustomStringConvertible { extension Edge: Hashable { - public var hashValue: Int { - var string = "\(from.description)\(to.description)" + public func hash(into hasher: inout Hasher) { + hasher.combine(from) + hasher.combine(to) if weight != nil { - string.append("\(weight!)") + hasher.combine(weight) } - return string.hashValue } + } public func == (lhs: Edge, rhs: Edge) -> Bool { diff --git a/Graph/Graph/Vertex.swift b/Graph/Graph/Vertex.swift index 758a367bb..48645f0fa 100644 --- a/Graph/Graph/Vertex.swift +++ b/Graph/Graph/Vertex.swift @@ -24,8 +24,9 @@ extension Vertex: CustomStringConvertible { extension Vertex: Hashable { - public var hashValue: Int { - return "\(data)\(index)".hashValue + public func hasher(into hasher: inout Hasher) { + hasher.combine(data) + hasher.combine(index) } } diff --git a/Hashed Heap/HashedHeap.swift b/Hashed Heap/HashedHeap.swift index be7d9ab78..106cdacb0 100644 --- a/Hashed Heap/HashedHeap.swift +++ b/Hashed Heap/HashedHeap.swift @@ -57,7 +57,12 @@ public struct HashedHeap { public var count: Int { return elements.count } - + + /// Accesses an element by its index. + public subscript(index: Int) -> T { + return elements[index] + } + /// Returns the index of the given element. /// /// This is the operation that a hashed heap optimizes in compassion with a normal heap. In a normal heap this @@ -141,6 +146,12 @@ public struct HashedHeap { } return removeLast() } + + /// Removes all elements from the heap. + public mutating func removeAll() { + elements.removeAll() + indices.removeAll() + } /// Removes the last element from the heap. /// diff --git a/HaversineDistance/HaversineDistance.playground/Contents.swift b/HaversineDistance/HaversineDistance.playground/Contents.swift index 3d8d60f54..ed89b4462 100644 --- a/HaversineDistance/HaversineDistance.playground/Contents.swift +++ b/HaversineDistance/HaversineDistance.playground/Contents.swift @@ -1,6 +1,6 @@ import UIKit -func haversineDinstance(la1: Double, lo1: Double, la2: Double, lo2: Double, radius: Double = 6367444.7) -> Double { +func haversineDistance(la1: Double, lo1: Double, la2: Double, lo2: Double, radius: Double = 6367444.7) -> Double { let haversin = { (angle: Double) -> Double in return (1 - cos(angle))/2 @@ -27,4 +27,4 @@ let amsterdam = (52.3702, 4.8952) let newYork = (40.7128, -74.0059) // Google says it's 5857 km so our result is only off by 2km which could be due to all kinds of things, not sure how google calculates the distance or which latitude and longitude google uses to calculate the distance. -haversineDinstance(la1: amsterdam.0, lo1: amsterdam.1, la2: newYork.0, lo2: newYork.1) +haversineDistance(la1: amsterdam.0, lo1: amsterdam.1, la2: newYork.0, lo2: newYork.1) diff --git a/Heap Sort/README.markdown b/Heap Sort/README.markdown index a45dad6f9..5bec58626 100644 --- a/Heap Sort/README.markdown +++ b/Heap Sort/README.markdown @@ -42,7 +42,7 @@ As you can see, the largest items are making their way to the back. We repeat th > **Note:** This process is very similar to [selection sort](../Selection%20Sort/), which repeatedly looks for the minimum item in the remainder of the array. Extracting the minimum or maximum value is what heaps are good at. -Performance of heap sort is **O(n lg n)** in best, worst, and average case. Because we modify the array directly, heap sort can be performed in-place. But it is not a stable sort: the relative order of identical elements is not preserved. +Performance of heap sort is **O(n log n)** in best, worst, and average case. Because we modify the array directly, heap sort can be performed in-place. But it is not a stable sort: the relative order of identical elements is not preserved. Here's how you can implement heap sort in Swift: diff --git a/Heap/Heap.swift b/Heap/Heap.swift index cf39fb8a5..7560bc7b9 100755 --- a/Heap/Heap.swift +++ b/Heap/Heap.swift @@ -212,7 +212,7 @@ extension Heap where T: Equatable { return nodes.index(where: { $0 == node }) } - /** Removes the first occurrence of a node from the heap. Performance: O(n log n). */ + /** Removes the first occurrence of a node from the heap. Performance: O(n). */ @discardableResult public mutating func remove(node: T) -> T? { if let index = index(of: node) { return remove(at: index) diff --git a/Heap/README.markdown b/Heap/README.markdown index 6c65fe9c8..fee881129 100755 --- a/Heap/README.markdown +++ b/Heap/README.markdown @@ -82,7 +82,7 @@ array[parent(i)] >= array[i] Verify that this heap property holds for the array from the example heap. -As you can see, these equations allow us to find the parent or child index for any node without the need for pointers. It is complicated than just dereferencing a pointer, but that is the tradeoff: we save memory space but pay with extra computations. Fortunately, the computations are fast and only take **O(1)** time. +As you can see, these equations allow us to find the parent or child index for any node without the need for pointers. It is more complicated than just dereferencing a pointer, but that is the tradeoff: we save memory space but pay with extra computations. Fortunately, the computations are fast and only take **O(1)** time. It is important to understand this relationship between array index and position in the tree. Here is a larger heap which has 15 nodes divided over four levels: @@ -164,7 +164,7 @@ All of the above take time **O(log n)** because shifting up or down is expensive - `buildHeap(array)`: Converts an (unsorted) array into a heap by repeatedly calling `insert()`. If you are smart about this, it can be done in **O(n)** time. -- [Heap sort](../Heap%20Sort/). Since the heap is an array, we can use its unique properties to sort the array from low to high. Time: **O(n lg n).** +- [Heap sort](../Heap%20Sort/). Since the heap is an array, we can use its unique properties to sort the array from low to high. Time: **O(n log n).** The heap also has a `peek()` function that returns the maximum (max-heap) or minimum (min-heap) element, without removing it from the heap. Time: **O(1)**. diff --git a/Insertion Sort/InsertionSort.playground/Contents.swift b/Insertion Sort/InsertionSort.playground/Contents.swift index 184fc36e4..e1d3a312e 100644 --- a/Insertion Sort/InsertionSort.playground/Contents.swift +++ b/Insertion Sort/InsertionSort.playground/Contents.swift @@ -9,17 +9,17 @@ func insertionSort(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { guard array.count > 1 else { return array } - var a = array - for x in 1.. 0 && isOrderedBefore(temp, a[y - 1]) { - a[y] = a[y - 1] - y -= 1 + var sortedArray = array + for index in 1.. 0 && isOrderedBefore(temp, sortedArray[currentIndex - 1]) { + sortedArray[currentIndex] = sortedArray[currentIndex - 1] + currentIndex -= 1 } - a[y] = temp + sortedArray[currentIndex] = temp } - return a + return sortedArray } /// Performs the Insertion sort algorithm to a given array @@ -27,17 +27,17 @@ func insertionSort(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { /// - Parameter array: the array to be sorted, conatining elements that conform to the Comparable protocol /// - Returns: a sorted array containing the same elements func insertionSort(_ array: [T]) -> [T] { - var a = array - for x in 1.. 0 && temp < a[y - 1] { - a[y] = a[y - 1] - y -= 1 + var sortedArray = array + for index in 1.. 0 && temp < sortedArray[currentIndex - 1] { + sortedArray[currentIndex] = sortedArray[currentIndex - 1] + currentIndex -= 1 } - a[y] = temp + sortedArray[currentIndex] = temp } - return a + return sortedArray } let list = [ 10, -1, 3, 9, 2, 27, 8, 5, 1, 3, 0, 26 ] diff --git a/Insertion Sort/InsertionSort.playground/contents.xcplayground b/Insertion Sort/InsertionSort.playground/contents.xcplayground index 9f9eecc96..06828af92 100644 --- a/Insertion Sort/InsertionSort.playground/contents.xcplayground +++ b/Insertion Sort/InsertionSort.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Insertion Sort/InsertionSort.swift b/Insertion Sort/InsertionSort.swift index 2ad607ac8..5f0b6c2b4 100644 --- a/Insertion Sort/InsertionSort.swift +++ b/Insertion Sort/InsertionSort.swift @@ -5,19 +5,19 @@ /// - isOrderedBefore: returns true if the elements provided are in the corect order /// - Returns: a sorted array containing the same elements func insertionSort(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { - guard array.count > 1 else { return array } - - var a = array - for x in 1.. 0 && isOrderedBefore(temp, a[y - 1]) { - a[y] = a[y - 1] - y -= 1 + guard array.count > 1 else { return array } + /// - sortedArray: copy the array to save stability + var sortedArray = array + for index in 1.. 0, isOrderedBefore(temp, sortedArray[currentIndex - 1]) { + sortedArray[currentIndex] = sortedArray[currentIndex - 1] + currentIndex -= 1 + } + sortedArray[currentIndex] = temp } - a[y] = temp - } - return a + return sortedArray } /// Performs the Insertion sort algorithm to a given array @@ -27,15 +27,14 @@ func insertionSort(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { func insertionSort(_ array: [T]) -> [T] { guard array.count > 1 else { return array } - var a = array - for x in 1.. 0 && temp < a[y - 1] { - a[y] = a[y - 1] - y -= 1 + var sortedArray = array + for var index in 1.. 0, temp < sortedArray[index - 1] { + sortedArray[index] = sortedArray[index - 1] + index -= 1 } - a[y] = temp + sortedArray[index] = temp } - return a + return sortedArray } diff --git a/Insertion Sort/README.markdown b/Insertion Sort/README.markdown index 4bad29aa1..f7b933b92 100644 --- a/Insertion Sort/README.markdown +++ b/Insertion Sort/README.markdown @@ -91,15 +91,15 @@ Here is an implementation of insertion sort in Swift: ```swift func insertionSort(_ array: [Int]) -> [Int] { - var a = array // 1 - for x in 1.. 0 && a[y] < a[y - 1] { // 3 - a.swapAt(y - 1, y) - y -= 1 + var sortedArray = array // 1 + for index in 1.. 0 && sortedArray[currentIndex] < sortedArray[currentIndex - 1] { // 3 + sortedArray.swapAt(currentIndex - 1, currentIndex) + currentIndex -= 1 } } - return a + return sortedArray } @@ -114,11 +114,11 @@ insertionSort(list) Here is how the code works. -1. Make a copy of the array. This is necessary because we cannot modify the contents of the `array` parameter directly. Like Swift's own `sort()`, the `insertionSort()` function will return a sorted *copy* of the original array. +1. Make a copy of the array. This is necessary because we cannot modify the contents of the `array` parameter directly. Like Swift's own `sorted()`, the `insertionSort()` function will return a sorted *copy* of the original array. -2. There are two loops inside this function. The outer loop looks at each of the elements in the array in turn; this is what picks the top-most number from the pile. The variable `x` is the index of where the sorted portion ends and the pile begins (the position of the `|` bar). Remember, at any given moment the beginning of the array -- from index 0 up to `x` -- is always sorted. The rest, from index `x` until the last element, is the unsorted pile. +2. There are two loops inside this function. The outer loop looks at each of the elements in the array in turn; this is what picks the top-most number from the pile. The variable `currentIndex` is the index of where the sorted portion ends and the pile begins (the position of the `|` bar). Remember, at any given moment the beginning of the array -- from index 0 up to `currentIndex` -- is always sorted. The rest, from index `currentIndex` until the last element, is the unsorted pile. -3. The inner loop looks at the element at position `x`. This is the number at the top of the pile, and it may be smaller than any of the previous elements. The inner loop steps backwards through the sorted array; every time it finds a previous number that is larger, it swaps them. When the inner loop completes, the beginning of the array is sorted again, and the sorted portion has grown by one element. +3. The inner loop looks at the element at position `currentIndex`. This is the number at the top of the pile, and it may be smaller than any of the previous elements. The inner loop steps backwards through the sorted array; every time it finds a previous number that is larger, it swaps them. When the inner loop completes, the beginning of the array is sorted again, and the sorted portion has grown by one element. > **Note:** The outer loop starts at index 1, not 0. Moving the very first element from the pile to the sorted portion doesn't actually change anything, so we might as well skip it. @@ -154,17 +154,17 @@ In code that looks like this: ```swift func insertionSort(_ array: [Int]) -> [Int] { - var a = array - for x in 1.. 0 && temp < a[y - 1] { - a[y] = a[y - 1] // 1 - y -= 1 + var sortedArray = array + for index in 1.. 0 && temp < sortedArray[currentIndex - 1] { + sortedArray[currentIndex] = sortedArray[currentIndex - 1] // 1 + currentIndex -= 1 } - a[y] = temp // 2 + sortedArray[currentIndex] = temp // 2 } - return a + return sortedArray } ``` diff --git a/Introsort/README.markdown b/Introsort/README.markdown index 376703cf7..280950928 100644 --- a/Introsort/README.markdown +++ b/Introsort/README.markdown @@ -2,7 +2,9 @@ Goal: Sort an array from low to high (or high to low). -IntroSort is the algorithm used by swift to sort a collection. Introsort is an hybrid algorithm invented by David Musser in 1993 with the purpose of giving a generic sorting algorithm for the C++ standard library. The classic implementation of introsort expect a recursive Quicksort with fallback to Heapsort in case the recursion depth level reached a certain max. The maximum depends on the number of elements in the collection and it is usually 2 * log(n). The reason behind this “fallback” is that if Quicksort was not able to get the solution after 2 * log(n) recursions for a branch, probably it hit its worst case and it is degrading to complexity O( n^2 ). To optimise even further this algorithm, the swift implementation introduce an extra step in each recursion where the partition is sorted using InsertionSort if the count of the partition is less than 20. +IntroSort is the algorithm used by Swift to sort a collection. Introsort is an hybrid algorithm invented by David Musser in 1993 with the purpose of giving a generic sorting algorithm for the C++ standard library. + +The classic implementation of introsort uses a recursive Quicksort with a fallback to Heapsort in the case where the recursion depth level reached a certain maximum value. The maximum depends on the number of elements in the collection and it is usually 2 * log(n). The reason behind this “fallback” is that if Quicksort was not able to get the solution after 2 * log(n) recursions for a branch, probably it hit its worst case and it is degrading to complexity O( n^2 ). To optimise even further this algorithm, the Swift implementation introduce an extra step in each recursion where the partition is sorted using InsertionSort if the count of the partition is less than 20. The number 20 is an empiric number obtained observing the behaviour of InsertionSort with lists of this size. diff --git a/Knuth-Morris-Pratt/KnuthMorrisPratt.swift b/Knuth-Morris-Pratt/KnuthMorrisPratt.swift index 81bc65278..be0cd961d 100644 --- a/Knuth-Morris-Pratt/KnuthMorrisPratt.swift +++ b/Knuth-Morris-Pratt/KnuthMorrisPratt.swift @@ -12,8 +12,8 @@ extension String { func indexesOf(ptnr: String) -> [Int]? { - let text = Array(self.characters) - let pattern = Array(ptnr.characters) + let text = Array(self) + let pattern = Array(ptnr) let textLength: Int = text.count let patternLength: Int = pattern.count diff --git a/Linked List/README.markdown b/Linked List/README.markdown index e81e00b9f..ea399ee12 100644 --- a/Linked List/README.markdown +++ b/Linked List/README.markdown @@ -471,6 +471,7 @@ This will print the list like so: How about reversing a list, so that the head becomes the tail and vice versa? There is a very fast algorithm for that: +Iterative Approach: ```swift public func reverse() { var node = head @@ -482,6 +483,18 @@ How about reversing a list, so that the head becomes the tail and vice versa? Th } } ``` +Recursive Approach: +```swift + public func reverse(node: head) { + if !head || !head.next { + return head + } + let temp = reverse(head.next) + head.next.next = head + head.next = nil + return temp + } +``` This loops through the entire list and simply swaps the `next` and `previous` pointers of each node. It also moves the `head` pointer to the very last element. (If you had a tail pointer you'd also need to update it.) You end up with something like this: diff --git a/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift b/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift index c8d413e7f..0e78b7043 100644 --- a/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift +++ b/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift @@ -1,4 +1,4 @@ -// last checked with Xcode 9.0b4 +// last checked with Xcode 11.4 #if swift(>=4.0) print("Hello, Swift 4!") #endif @@ -7,10 +7,10 @@ extension String { public func longestCommonSubsequence(_ other: String) -> String { func lcsLength(_ other: String) -> [[Int]] { - var matrix = [[Int]](repeating: [Int](repeating: 0, count: other.characters.count+1), count: self.characters.count+1) + var matrix = [[Int]](repeating: [Int](repeating: 0, count: other.count+1), count: self.count+1) - for (i, selfChar) in self.characters.enumerated() { - for (j, otherChar) in other.characters.enumerated() { + for (i, selfChar) in self.enumerated() { + for (j, otherChar) in other.enumerated() { if otherChar == selfChar { matrix[i+1][j+1] = matrix[i][j] + 1 } else { @@ -22,8 +22,8 @@ extension String { } func backtrack(_ matrix: [[Int]]) -> String { - var i = self.characters.count - var j = other.characters.count + var i = self.count + var j = other.count var charInSequence = self.endIndex var lcs = String() @@ -41,7 +41,7 @@ extension String { lcs.append(self[charInSequence]) } } - return String(lcs.characters.reversed()) + return String(lcs.reversed()) } return backtrack(lcsLength(other)) diff --git a/Quicksort/Quicksort.playground/Contents.swift b/Quicksort/Quicksort.playground/Contents.swift index 94d9f8e45..1bbf7b967 100644 --- a/Quicksort/Quicksort.playground/Contents.swift +++ b/Quicksort/Quicksort.playground/Contents.swift @@ -1,10 +1,5 @@ //: Playground - noun: a place where people can play -// last checked with Xcode 9.0b4 -#if swift(>=4.0) -print("Hello, Swift 4!") -#endif - import Foundation // *** Simple but inefficient version of quicksort *** @@ -42,12 +37,12 @@ func partitionLomuto(_ a: inout [T], low: Int, high: Int) -> Int var i = low for j in low..(_ a: inout [T], low: Int, high: Int) -> Int var i = low for j in low..=4.0) - print("Hello, Swift 4!") - #endif - } - func testQuicksort() { checkSortAlgorithm(quicksort) } diff --git a/Quicksort/Tests/SortingTestHelpers.swift b/Quicksort/Tests/SortingTestHelpers.swift index d699ec1e4..0c47cde4e 100644 --- a/Quicksort/Tests/SortingTestHelpers.swift +++ b/Quicksort/Tests/SortingTestHelpers.swift @@ -1,4 +1,3 @@ -import Foundation import XCTest func randomArray(_ size: Int) -> [Int] { diff --git a/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj b/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj index c1b14e727..923195121 100644 --- a/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj +++ b/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj @@ -86,7 +86,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { @@ -149,12 +149,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -187,7 +189,7 @@ SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -203,12 +205,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -234,7 +238,7 @@ SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Quicksort/Tests/Tests-Quicksort.xcodeproj/xcshareddata/xcschemes/Tests-Quicksort.xcscheme b/Quicksort/Tests/Tests-Quicksort.xcodeproj/xcshareddata/xcschemes/Tests-Quicksort.xcscheme index 5b67c6f70..ec81527d1 100644 --- a/Quicksort/Tests/Tests-Quicksort.xcodeproj/xcshareddata/xcschemes/Tests-Quicksort.xcscheme +++ b/Quicksort/Tests/Tests-Quicksort.xcodeproj/xcshareddata/xcschemes/Tests-Quicksort.xcscheme @@ -1,6 +1,6 @@ : Equatable { - public typealias RBNode = RBTreeNode - - fileprivate var color: RBTreeColor = .black - fileprivate var key: T? - var leftChild: RBNode? - var rightChild: RBNode? - fileprivate weak var parent: RBNode? - - public init(key: T?, leftChild: RBNode?, rightChild: RBNode?, parent: RBNode?) { - self.key = key - self.leftChild = leftChild - self.rightChild = rightChild - self.parent = parent - - self.leftChild?.parent = self - self.rightChild?.parent = self - } - - public convenience init(key: T?) { - self.init(key: key, leftChild: RBNode(), rightChild: RBNode(), parent: RBNode()) - } - - // For initialising the nullLeaf - public convenience init() { - self.init(key: nil, leftChild: nil, rightChild: nil, parent: nil) - self.color = .black - } + public typealias RBNode = RBTreeNode + + fileprivate var color: RBTreeColor = .black + fileprivate var key: T? + var leftChild: RBNode? + var rightChild: RBNode? + fileprivate weak var parent: RBNode? + + public init(key: T?, leftChild: RBNode?, rightChild: RBNode?, parent: RBNode?) { + self.key = key + self.leftChild = leftChild + self.rightChild = rightChild + self.parent = parent + + self.leftChild?.parent = self + self.rightChild?.parent = self + } + + public convenience init(key: T?) { + self.init(key: key, leftChild: RBNode(), rightChild: RBNode(), parent: RBNode()) + } + + // For initialising the nullLeaf + public convenience init() { + self.init(key: nil, leftChild: nil, rightChild: nil, parent: nil) + self.color = .black + } + + var isRoot: Bool { + return parent == nil + } + + var isLeaf: Bool { + return rightChild == nil && leftChild == nil + } + + var isNullLeaf: Bool { + return key == nil && isLeaf && color == .black + } + + var isLeftChild: Bool { + return parent?.leftChild === self + } + + var isRightChild: Bool { + return parent?.rightChild === self + } + + var grandparent: RBNode? { + return parent?.parent + } + + var sibling: RBNode? { + if isLeftChild { + return parent?.rightChild + } else { + return parent?.leftChild + } + } + + var uncle: RBNode? { + return parent?.sibling + } - public func getKey() -> T? { - return key - } - - var isRoot: Bool { - return parent == nil - } - - var isLeaf: Bool { - return rightChild == nil && leftChild == nil - } - - var isNullLeaf: Bool { - return key == nil && isLeaf && color == .black - } - - var isLeftChild: Bool { - return parent?.leftChild === self - } - - var isRightChild: Bool { - return parent?.rightChild === self - } - - var grandparent: RBNode? { - return parent?.parent - } - - var sibling: RBNode? { - if isLeftChild { - return parent?.rightChild - } else { - return parent?.leftChild + public func getKey() -> T? { + return key } - } - - var uncle: RBNode? { - return parent?.sibling - } } // MARK: - RedBlackTree public class RedBlackTree { - public typealias RBNode = RBTreeNode - - fileprivate(set) var root: RBNode - fileprivate(set) var size = 0 - fileprivate let nullLeaf = RBNode() - - public init() { - root = nullLeaf - } + public typealias RBNode = RBTreeNode + + fileprivate(set) var root: RBNode + fileprivate(set) var size = 0 + fileprivate let nullLeaf = RBNode() + fileprivate let allowDuplicateNode: Bool + + public init(_ allowDuplicateNode: Bool = false) { + root = nullLeaf + self.allowDuplicateNode = allowDuplicateNode + } +} + +// MARK: - Size + +extension RedBlackTree { + public func count() -> Int { + return size + } + + public func isEmpty() -> Bool { + return size == 0 + } + + public func allElements() -> [T] { + var nodes: [T] = [] + + getAllElements(node: root, nodes: &nodes) + + return nodes + } + + private func getAllElements(node: RBTreeNode, nodes: inout [T]) { + guard !node.isNullLeaf else { + return + } + + if let left = node.leftChild { + getAllElements(node: left, nodes: &nodes) + } + + if let key = node.key { + nodes.append(key) + } + + if let right = node.rightChild { + getAllElements(node: right, nodes: &nodes) + } + } } // MARK: - Equatable protocol extension RBTreeNode { - static public func == (lhs: RBTreeNode, rhs: RBTreeNode) -> Bool { - return lhs.key == rhs.key - } + static public func == (lhs: RBTreeNode, rhs: RBTreeNode) -> Bool { + return lhs.key == rhs.key + } } // MARK: - Finding a nodes successor and predecessor extension RBTreeNode { - /* - * Returns the inorder predecessor node of a node - * The predecessor is a node with the next smaller key value of the current node - */ - public func getPredecessor() -> RBNode? { - // if node has left child: predecessor is min of this left tree - if let leftChild = leftChild, !leftChild.isNullLeaf { - return leftChild.maximum() - } - // else go upward while node is left child - var currentNode = self - var parent = currentNode.parent - while currentNode.isLeftChild { - if let parent = parent { - currentNode = parent - } - parent = currentNode.parent - } - return parent - } - /* * Returns the inorder successor node of a node * The successor is a node with the next larger key value of the current node @@ -170,606 +189,632 @@ extension RBTreeNode { } return parent } + + /* + * Returns the inorder predecessor node of a node + * The predecessor is a node with the next smaller key value of the current node + */ + public func getPredecessor() -> RBNode? { + // if node has left child: predecessor is min of this left tree + if let leftChild = leftChild, !leftChild.isNullLeaf { + return leftChild.maximum() + } + // else go upward while node is left child + var currentNode = self + var parent = currentNode.parent + while currentNode.isLeftChild { + if let parent = parent { + currentNode = parent + } + parent = currentNode.parent + } + return parent + } } // MARK: - Searching extension RBTreeNode { - /* - * Returns the node with the minimum key of the current subtree - */ - public func minimum() -> RBNode? { - if let leftChild = leftChild { - if !leftChild.isNullLeaf { - return leftChild.minimum() - } - return self + /* + * Returns the node with the minimum key of the current subtree + */ + public func minimum() -> RBNode? { + if let leftChild = leftChild { + if !leftChild.isNullLeaf { + return leftChild.minimum() + } + return self + } + return self } - return self - } - - /* - * Returns the node with the maximum key of the current subtree - */ - public func maximum() -> RBNode? { - if let rightChild = rightChild { - if !rightChild.isNullLeaf { - return rightChild.maximum() - } - return self + + /* + * Returns the node with the maximum key of the current subtree + */ + public func maximum() -> RBNode? { + if let rightChild = rightChild { + if !rightChild.isNullLeaf { + return rightChild.maximum() + } + return self + } + return self } - return self - } } extension RedBlackTree { - /* - * Returns the node with the given key |input| if existing - */ - public func search(input: T) -> RBNode? { - return search(key: input, node: root) - } - - /* - * Returns the node with given |key| in subtree of |node| - */ - fileprivate func search(key: T, node: RBNode?) -> RBNode? { - // If node nil -> key not found - guard let node = node else { - return nil - } - // If node is nullLeaf == semantically same as if nil - if !node.isNullLeaf { - if let nodeKey = node.key { - // Node found - if key == nodeKey { - return node - } else if key < nodeKey { - return search(key: key, node: node.leftChild) - } else { - return search(key: key, node: node.rightChild) + /* + * Returns the node with the given key |input| if existing + */ + public func search(input: T) -> RBNode? { + return search(key: input, node: root) + } + + /* + * Returns the node with given |key| in subtree of |node| + */ + fileprivate func search(key: T, node: RBNode?) -> RBNode? { + // If node nil -> key not found + guard let node = node else { + return nil } - } + // If node is nullLeaf == semantically same as if nil + if !node.isNullLeaf { + if let nodeKey = node.key { + // Node found + if key == nodeKey { + return node + } else if key < nodeKey { + return search(key: key, node: node.leftChild) + } else { + return search(key: key, node: node.rightChild) + } + } + } + return nil } - return nil - } } // MARK: - Finding maximum and minimum value extension RedBlackTree { - /* - * Returns the minimum key value of the whole tree - */ - public func minValue() -> T? { - guard let minNode = root.minimum() else { - return nil + /* + * Returns the minimum key value of the whole tree + */ + public func minValue() -> T? { + guard let minNode = root.minimum() else { + return nil + } + return minNode.key } - return minNode.key - } - - /* - * Returns the maximum key value of the whole tree - */ - public func maxValue() -> T? { - guard let maxNode = root.maximum() else { - return nil + + /* + * Returns the maximum key value of the whole tree + */ + public func maxValue() -> T? { + guard let maxNode = root.maximum() else { + return nil + } + return maxNode.key } - return maxNode.key - } } // MARK: - Inserting new nodes extension RedBlackTree { - /* - * Insert a node with key |key| into the tree - * 1. Perform normal insert operation as in a binary search tree - * 2. Fix red-black properties - * Runntime: O(log n) - */ - public func insert(key: T) { - if root.isNullLeaf { - root = RBNode(key: key) - } else { - insert(input: RBNode(key: key), node: root) + /* + * Insert a node with key |key| into the tree + * 1. Perform normal insert operation as in a binary search tree + * 2. Fix red-black properties + * Runntime: O(log n) + */ + public func insert(key: T) { + // If key must be unique and find the key already existed, quit + if search(input: key) != nil && !allowDuplicateNode { + return + } + + if root.isNullLeaf { + root = RBNode(key: key) + } else { + insert(input: RBNode(key: key), node: root) + } + + size += 1 } - size += 1 - } - - /* - * Nearly identical insert operation as in a binary search tree - * Differences: All nil pointers are replaced by the nullLeaf, we color the inserted node red, - * after inserting we call insertFixup to maintain the red-black properties - */ - private func insert(input: RBNode, node: RBNode) { - guard let inputKey = input.key, let nodeKey = node.key else { - return - } - if inputKey < nodeKey { - guard let child = node.leftChild else { - addAsLeftChild(child: input, parent: node) - return - } - if child.isNullLeaf { - addAsLeftChild(child: input, parent: node) - } else { - insert(input: input, node: child) - } - } else { - guard let child = node.rightChild else { - addAsRightChild(child: input, parent: node) - return - } - if child.isNullLeaf { - addAsRightChild(child: input, parent: node) - } else { - insert(input: input, node: child) - } - } - } - - private func addAsLeftChild(child: RBNode, parent: RBNode) { - parent.leftChild = child - child.parent = parent - child.color = .red - insertFixup(node: child) - } - - private func addAsRightChild(child: RBNode, parent: RBNode) { - parent.rightChild = child - child.parent = parent - child.color = .red - insertFixup(node: child) - } - - /* - * Fixes possible violations of the red-black property after insertion - * Only violation of red-black properties occurs at inserted node |z| and z.parent - * We have 3 distinct cases: case 1, 2a and 2b - * - case 1: may repeat, but only h/2 steps, where h is the height of the tree - * - case 2a -> case 2b -> red-black tree - * - case 2b -> red-black tree - */ - private func insertFixup(node z: RBNode) { - if !z.isNullLeaf { - guard let parentZ = z.parent else { - return - } - // If both |z| and his parent are red -> violation of red-black property -> need to fix it - if parentZ.color == .red { - guard let uncle = z.uncle else { - return - } - // Case 1: Uncle red -> recolor + move z - if uncle.color == .red { - parentZ.color = .black - uncle.color = .black - if let grandparentZ = parentZ.parent { - grandparentZ.color = .red - // Move z to grandparent and check again - insertFixup(node: grandparentZ) - } - } - // Case 2: Uncle black - else { - var zNew = z - // Case 2.a: z right child -> rotate - if parentZ.isLeftChild && z.isRightChild { - zNew = parentZ - leftRotate(node: zNew) - } else if parentZ.isRightChild && z.isLeftChild { - zNew = parentZ - rightRotate(node: zNew) - } - // Case 2.b: z left child -> recolor + rotate - zNew.parent?.color = .black - if let grandparentZnew = zNew.grandparent { - grandparentZnew.color = .red - if z.isLeftChild { - rightRotate(node: grandparentZnew) + /* + * Nearly identical insert operation as in a binary search tree + * Differences: All nil pointers are replaced by the nullLeaf, we color the inserted node red, + * after inserting we call insertFixup to maintain the red-black properties + */ + private func insert(input: RBNode, node: RBNode) { + guard let inputKey = input.key, let nodeKey = node.key else { + return + } + if inputKey < nodeKey { + guard let child = node.leftChild else { + addAsLeftChild(child: input, parent: node) + return + } + if child.isNullLeaf { + addAsLeftChild(child: input, parent: node) + } else { + insert(input: input, node: child) + } + } else { + guard let child = node.rightChild else { + addAsRightChild(child: input, parent: node) + return + } + if child.isNullLeaf { + addAsRightChild(child: input, parent: node) } else { - leftRotate(node: grandparentZnew) + insert(input: input, node: child) } - // We have a valid red-black-tree - } } - } } - root.color = .black - } + + private func addAsLeftChild(child: RBNode, parent: RBNode) { + parent.leftChild = child + child.parent = parent + child.color = .red + insertFixup(node: child) + } + + private func addAsRightChild(child: RBNode, parent: RBNode) { + parent.rightChild = child + child.parent = parent + child.color = .red + insertFixup(node: child) + } + + /* + * Fixes possible violations of the red-black property after insertion + * Only violation of red-black properties occurs at inserted node |z| and z.parent + * We have 3 distinct cases: case 1, 2a and 2b + * - case 1: may repeat, but only h/2 steps, where h is the height of the tree + * - case 2a -> case 2b -> red-black tree + * - case 2b -> red-black tree + */ + private func insertFixup(node z: RBNode) { + if !z.isNullLeaf { + guard let parentZ = z.parent else { + return + } + // If both |z| and his parent are red -> violation of red-black property -> need to fix it + if parentZ.color == .red { + guard let uncle = z.uncle else { + return + } + // Case 1: Uncle red -> recolor + move z + if uncle.color == .red { + parentZ.color = .black + uncle.color = .black + if let grandparentZ = parentZ.parent { + grandparentZ.color = .red + // Move z to grandparent and check again + insertFixup(node: grandparentZ) + } + } + // Case 2: Uncle black + else { + var zNew = z + // Case 2.a: z right child -> rotate + if parentZ.isLeftChild && z.isRightChild { + zNew = parentZ + leftRotate(node: zNew) + } else if parentZ.isRightChild && z.isLeftChild { + zNew = parentZ + rightRotate(node: zNew) + } + // Case 2.b: z left child -> recolor + rotate + zNew.parent?.color = .black + if let grandparentZnew = zNew.grandparent { + grandparentZnew.color = .red + if z.isLeftChild { + rightRotate(node: grandparentZnew) + } else { + leftRotate(node: grandparentZnew) + } + // We have a valid red-black-tree + } + } + } + } + root.color = .black + } } // MARK: - Deleting a node extension RedBlackTree { - /* - * Delete a node with key |key| from the tree - * 1. Perform standard delete operation as in a binary search tree - * 2. Fix red-black properties - * Runntime: O(log n) - */ - public func delete(key: T) { - if size == 1 { - root = nullLeaf - size -= 1 - } else if let node = search(key: key, node: root) { - if !node.isNullLeaf { - delete(node: node) - size -= 1 - } - } - } - - /* - * Nearly identical delete operation as in a binary search tree - * Differences: All nil pointers are replaced by the nullLeaf, - * after deleting we call insertFixup to maintain the red-black properties if the delted node was - * black (as if it was red -> no violation of red-black properties) - */ - private func delete(node z: RBNode) { - var nodeY = RBNode() - var nodeX = RBNode() - if let leftChild = z.leftChild, let rightChild = z.rightChild { - if leftChild.isNullLeaf || rightChild.isNullLeaf { - nodeY = z - } else { - if let successor = z.getSuccessor() { - nodeY = successor + /* + * Delete a node with key |key| from the tree + * 1. Perform standard delete operation as in a binary search tree + * 2. Fix red-black properties + * Runntime: O(log n) + */ + public func delete(key: T) { + if size == 1 { + root = nullLeaf + size -= 1 + } else if let node = search(key: key, node: root) { + if !node.isNullLeaf { + delete(node: node) + size -= 1 + } } - } - } - if let leftChild = nodeY.leftChild { - if !leftChild.isNullLeaf { - nodeX = leftChild - } else if let rightChild = nodeY.rightChild { - nodeX = rightChild - } } - nodeX.parent = nodeY.parent - if let parentY = nodeY.parent { - // Should never be the case, as parent of root = nil - if parentY.isNullLeaf { - root = nodeX - } else { - if nodeY.isLeftChild { - parentY.leftChild = nodeX + + /* + * Nearly identical delete operation as in a binary search tree + * Differences: All nil pointers are replaced by the nullLeaf, + * after deleting we call insertFixup to maintain the red-black properties if the delted node was + * black (as if it was red -> no violation of red-black properties) + */ + private func delete(node z: RBNode) { + var nodeY = RBNode() + var nodeX = RBNode() + if let leftChild = z.leftChild, let rightChild = z.rightChild { + if leftChild.isNullLeaf || rightChild.isNullLeaf { + nodeY = z + } else { + if let successor = z.getSuccessor() { + nodeY = successor + } + } + } + if let leftChild = nodeY.leftChild { + if !leftChild.isNullLeaf { + nodeX = leftChild + } else if let rightChild = nodeY.rightChild { + nodeX = rightChild + } + } + nodeX.parent = nodeY.parent + if let parentY = nodeY.parent { + // Should never be the case, as parent of root = nil + if parentY.isNullLeaf { + root = nodeX + } else { + if nodeY.isLeftChild { + parentY.leftChild = nodeX + } else { + parentY.rightChild = nodeX + } + } } else { - parentY.rightChild = nodeX + root = nodeX } - } - } else { - root = nodeX - } - if nodeY != z { - z.key = nodeY.key - } - // If sliced out node was red -> nothing to do as red-black-property holds - // If it was black -> fix red-black-property - if nodeY.color == .black { - deleteFixup(node: nodeX) - } - } - - /* - * Fixes possible violations of the red-black property after deletion - * We have w distinct cases: only case 2 may repeat, but only h many steps, where h is the height - * of the tree - * - case 1 -> case 2 -> red-black tree - * case 1 -> case 3 -> case 4 -> red-black tree - * case 1 -> case 4 -> red-black tree - * - case 3 -> case 4 -> red-black tree - * - case 4 -> red-black tree - */ - private func deleteFixup(node x: RBNode) { - var xTmp = x - if !x.isRoot && x.color == .black { - guard var sibling = x.sibling else { - return - } - // Case 1: Sibling of x is red - if sibling.color == .red { - // Recolor - sibling.color = .black - if let parentX = x.parent { - parentX.color = .red - // Rotation - if x.isLeftChild { - leftRotate(node: parentX) - } else { - rightRotate(node: parentX) - } - // Update sibling - if let sibl = x.sibling { - sibling = sibl - } + if nodeY != z { + z.key = nodeY.key } - } - // Case 2: Sibling is black with two black children - if sibling.leftChild?.color == .black && sibling.rightChild?.color == .black { - // Recolor - sibling.color = .red - // Move fake black unit upwards - if let parentX = x.parent { - deleteFixup(node: parentX) - } - // We have a valid red-black-tree - } else { - // Case 3: a. Sibling black with one black child to the right - if x.isLeftChild && sibling.rightChild?.color == .black { - // Recolor - sibling.leftChild?.color = .black - sibling.color = .red - // Rotate - rightRotate(node: sibling) - // Update sibling of x - if let sibl = x.sibling { - sibling = sibl - } - } - // Still case 3: b. One black child to the left - else if x.isRightChild && sibling.leftChild?.color == .black { - // Recolor - sibling.rightChild?.color = .black - sibling.color = .red - // Rotate - leftRotate(node: sibling) - // Update sibling of x - if let sibl = x.sibling { - sibling = sibl - } - } - // Case 4: Sibling is black with red right child - // Recolor - if let parentX = x.parent { - sibling.color = parentX.color - parentX.color = .black - // a. x left and sibling with red right child - if x.isLeftChild { - sibling.rightChild?.color = .black - // Rotate - leftRotate(node: parentX) - } - // b. x right and sibling with red left child - else { - sibling.leftChild?.color = .black - //Rotate - rightRotate(node: parentX) - } - // We have a valid red-black-tree - xTmp = root + // If sliced out node was red -> nothing to do as red-black-property holds + // If it was black -> fix red-black-property + if nodeY.color == .black { + deleteFixup(node: nodeX) } - } } - xTmp.color = .black - } + + /* + * Fixes possible violations of the red-black property after deletion + * We have w distinct cases: only case 2 may repeat, but only h many steps, where h is the height + * of the tree + * - case 1 -> case 2 -> red-black tree + * case 1 -> case 3 -> case 4 -> red-black tree + * case 1 -> case 4 -> red-black tree + * - case 3 -> case 4 -> red-black tree + * - case 4 -> red-black tree + */ + private func deleteFixup(node x: RBNode) { + var xTmp = x + if !x.isRoot && x.color == .black { + guard var sibling = x.sibling else { + return + } + // Case 1: Sibling of x is red + if sibling.color == .red { + // Recolor + sibling.color = .black + if let parentX = x.parent { + parentX.color = .red + // Rotation + if x.isLeftChild { + leftRotate(node: parentX) + } else { + rightRotate(node: parentX) + } + // Update sibling + if let sibl = x.sibling { + sibling = sibl + } + } + } + // Case 2: Sibling is black with two black children + if sibling.leftChild?.color == .black && sibling.rightChild?.color == .black { + // Recolor + sibling.color = .red + // Move fake black unit upwards + if let parentX = x.parent { + deleteFixup(node: parentX) + } + // We have a valid red-black-tree + } else { + // Case 3: a. Sibling black with one black child to the right + if x.isLeftChild && sibling.rightChild?.color == .black { + // Recolor + sibling.leftChild?.color = .black + sibling.color = .red + // Rotate + rightRotate(node: sibling) + // Update sibling of x + if let sibl = x.sibling { + sibling = sibl + } + } + // Still case 3: b. One black child to the left + else if x.isRightChild && sibling.leftChild?.color == .black { + // Recolor + sibling.rightChild?.color = .black + sibling.color = .red + // Rotate + leftRotate(node: sibling) + // Update sibling of x + if let sibl = x.sibling { + sibling = sibl + } + } + // Case 4: Sibling is black with red right child + // Recolor + if let parentX = x.parent { + sibling.color = parentX.color + parentX.color = .black + // a. x left and sibling with red right child + if x.isLeftChild { + sibling.rightChild?.color = .black + // Rotate + leftRotate(node: parentX) + } + // b. x right and sibling with red left child + else { + sibling.leftChild?.color = .black + //Rotate + rightRotate(node: parentX) + } + // We have a valid red-black-tree + xTmp = root + } + } + } + xTmp.color = .black + } } // MARK: - Rotation extension RedBlackTree { - /* - * Left rotation around node x - * Assumes that x.rightChild y is not a nullLeaf, rotates around the link from x to y, - * makes y the new root of the subtree with x as y's left child and y's left child as x's right - * child, where n = a node, [n] = a subtree - * | | - * x y - * / \ ~> / \ - * [A] y x [C] - * / \ / \ - * [B] [C] [A] [B] - */ - fileprivate func leftRotate(node x: RBNode) { - rotate(node: x, direction: .left) - } - - /* - * Right rotation around node y - * Assumes that y.leftChild x is not a nullLeaf, rotates around the link from y to x, - * makes x the new root of the subtree with y as x's right child and x's right child as y's left - * child, where n = a node, [n] = a subtree - * | | - * x y - * / \ <~ / \ - * [A] y x [C] - * / \ / \ - * [B] [C] [A] [B] - */ - fileprivate func rightRotate(node x: RBNode) { - rotate(node: x, direction: .right) - } - - /* - * Rotation around a node x - * Is a local operation preserving the binary-search-tree property that only exchanges pointers. - * Runntime: O(1) - */ - private func rotate(node x: RBNode, direction: RotationDirection) { - var nodeY: RBNode? = RBNode() - - // Set |nodeY| and turn |nodeY|'s left/right subtree into |x|'s right/left subtree - switch direction { - case .left: - nodeY = x.rightChild - x.rightChild = nodeY?.leftChild - x.rightChild?.parent = x - case .right: - nodeY = x.leftChild - x.leftChild = nodeY?.rightChild - x.leftChild?.parent = x - } - - // Link |x|'s parent to nodeY - nodeY?.parent = x.parent - if x.isRoot { - if let node = nodeY { - root = node - } - } else if x.isLeftChild { - x.parent?.leftChild = nodeY - } else if x.isRightChild { - x.parent?.rightChild = nodeY + /* + * Left rotation around node x + * Assumes that x.rightChild y is not a nullLeaf, rotates around the link from x to y, + * makes y the new root of the subtree with x as y's left child and y's left child as x's right + * child, where n = a node, [n] = a subtree + * | | + * x y + * / \ ~> / \ + * [A] y x [C] + * / \ / \ + * [B] [C] [A] [B] + */ + fileprivate func leftRotate(node x: RBNode) { + rotate(node: x, direction: .left) } - // Put |x| on |nodeY|'s left - switch direction { - case .left: - nodeY?.leftChild = x - case .right: - nodeY?.rightChild = x + /* + * Right rotation around node y + * Assumes that y.leftChild x is not a nullLeaf, rotates around the link from y to x, + * makes x the new root of the subtree with y as x's right child and x's right child as y's left + * child, where n = a node, [n] = a subtree + * | | + * x y + * / \ <~ / \ + * [A] y x [C] + * / \ / \ + * [B] [C] [A] [B] + */ + fileprivate func rightRotate(node x: RBNode) { + rotate(node: x, direction: .right) + } + + /* + * Rotation around a node x + * Is a local operation preserving the binary-search-tree property that only exchanges pointers. + * Runntime: O(1) + */ + private func rotate(node x: RBNode, direction: RotationDirection) { + var nodeY: RBNode? = RBNode() + + // Set |nodeY| and turn |nodeY|'s left/right subtree into |x|'s right/left subtree + switch direction { + case .left: + nodeY = x.rightChild + x.rightChild = nodeY?.leftChild + x.rightChild?.parent = x + case .right: + nodeY = x.leftChild + x.leftChild = nodeY?.rightChild + x.leftChild?.parent = x + } + + // Link |x|'s parent to nodeY + nodeY?.parent = x.parent + if x.isRoot { + if let node = nodeY { + root = node + } + } else if x.isLeftChild { + x.parent?.leftChild = nodeY + } else if x.isRightChild { + x.parent?.rightChild = nodeY + } + + // Put |x| on |nodeY|'s left + switch direction { + case .left: + nodeY?.leftChild = x + case .right: + nodeY?.rightChild = x + } + x.parent = nodeY } - x.parent = nodeY - } } // MARK: - Verify extension RedBlackTree { - /* - * Verifies that the existing tree fulfills all red-black properties - * Returns true if the tree is a valid red-black tree, false otherwise - */ - public func verify() -> Bool { - if root.isNullLeaf { - print("The tree is empty") - return true + /* + * Verifies that the existing tree fulfills all red-black properties + * Returns true if the tree is a valid red-black tree, false otherwise + */ + public func verify() -> Bool { + if root.isNullLeaf { + print("The tree is empty") + return true + } + return property2() && property4() && property5() } - return property2() && property4() && property5() - } - - // Property 1: Every node is either red or black -> fullfilled through setting node.color of type - // RBTreeColor - - // Property 2: The root is black - private func property2() -> Bool { - if root.color == .red { - print("Property-Error: Root is red") - return false + + // Property 1: Every node is either red or black -> fullfilled through setting node.color of type + // RBTreeColor + + // Property 2: The root is black + private func property2() -> Bool { + if root.color == .red { + print("Property-Error: Root is red") + return false + } + return true } - return true - } - - // Property 3: Every nullLeaf is black -> fullfilled through initialising nullLeafs with color = black - - // Property 4: If a node is red, then both its children are black - private func property4() -> Bool { - return property4(node: root) - } - - private func property4(node: RBNode) -> Bool { - if node.isNullLeaf { - return true + + // Property 3: Every nullLeaf is black -> fullfilled through initialising nullLeafs with color = black + + // Property 4: If a node is red, then both its children are black + private func property4() -> Bool { + return property4(node: root) } - if let leftChild = node.leftChild, let rightChild = node.rightChild { - if node.color == .red { - if !leftChild.isNullLeaf && leftChild.color == .red { - print("Property-Error: Red node with key \(String(describing: node.key)) has red left child") - return false + + private func property4(node: RBNode) -> Bool { + if node.isNullLeaf { + return true } - if !rightChild.isNullLeaf && rightChild.color == .red { - print("Property-Error: Red node with key \(String(describing: node.key)) has red right child") - return false + if let leftChild = node.leftChild, let rightChild = node.rightChild { + if node.color == .red { + if !leftChild.isNullLeaf && leftChild.color == .red { + print("Property-Error: Red node with key \(String(describing: node.key)) has red left child") + return false + } + if !rightChild.isNullLeaf && rightChild.color == .red { + print("Property-Error: Red node with key \(String(describing: node.key)) has red right child") + return false + } + } + return property4(node: leftChild) && property4(node: rightChild) } - } - return property4(node: leftChild) && property4(node: rightChild) + return false } - return false - } - - // Property 5: For each node, all paths from the node to descendant leaves contain the same number - // of black nodes (same blackheight) - private func property5() -> Bool { - if property5(node: root) == -1 { - return false - } else { - return true - } - } - - private func property5(node: RBNode) -> Int { - if node.isNullLeaf { - return 0 - } - guard let leftChild = node.leftChild, let rightChild = node.rightChild else { - return -1 + + // Property 5: For each node, all paths from the node to descendant leaves contain the same number + // of black nodes (same blackheight) + private func property5() -> Bool { + if property5(node: root) == -1 { + return false + } else { + return true + } } - let left = property5(node: leftChild) - let right = property5(node: rightChild) - if left == -1 || right == -1 { - return -1 - } else if left == right { - let addedHeight = node.color == .black ? 1 : 0 - return left + addedHeight - } else { - print("Property-Error: Black height violated at node with key \(String(describing: node.key))") - return -1 + private func property5(node: RBNode) -> Int { + if node.isNullLeaf { + return 0 + } + guard let leftChild = node.leftChild, let rightChild = node.rightChild else { + return -1 + } + let left = property5(node: leftChild) + let right = property5(node: rightChild) + + if left == -1 || right == -1 { + return -1 + } else if left == right { + let addedHeight = node.color == .black ? 1 : 0 + return left + addedHeight + } else { + print("Property-Error: Black height violated at node with key \(String(describing: node.key))") + return -1 + } } - } } // MARK: - Debugging extension RBTreeNode: CustomDebugStringConvertible { - public var debugDescription: String { - var s = "" - if isNullLeaf { - s = "nullLeaf" - } else { - if let key = key { - s = "key: \(key)" - } else { - s = "key: nil" - } - if let parent = parent { - s += ", parent: \(String(describing: parent.key))" - } - if let left = leftChild { - s += ", left = [" + left.debugDescription + "]" - } - if let right = rightChild { - s += ", right = [" + right.debugDescription + "]" - } - s += ", color = \(color)" + public var debugDescription: String { + var s = "" + if isNullLeaf { + s = "nullLeaf" + } else { + if let key = key { + s = "key: \(key)" + } else { + s = "key: nil" + } + if let parent = parent { + s += ", parent: \(String(describing: parent.key))" + } + if let left = leftChild { + s += ", left = [" + left.debugDescription + "]" + } + if let right = rightChild { + s += ", right = [" + right.debugDescription + "]" + } + s += ", color = \(color)" + } + return s } - return s - } } extension RedBlackTree: CustomDebugStringConvertible { - public var debugDescription: String { - return root.debugDescription - } + public var debugDescription: String { + return root.debugDescription + } } extension RBTreeNode: CustomStringConvertible { - public var description: String { - var s = "" - if isNullLeaf { - s += "nullLeaf" - } else { - if let left = leftChild { - s += "(\(left.description)) <- " - } - if let key = key { - s += "\(key)" - } else { - s += "nil" - } - s += ", \(color)" - if let right = rightChild { - s += " -> (\(right.description))" - } + public var description: String { + var s = "" + if isNullLeaf { + s += "nullLeaf" + } else { + if let left = leftChild { + s += "(\(left.description)) <- " + } + if let key = key { + s += "\(key)" + } else { + s += "nil" + } + s += ", \(color)" + if let right = rightChild { + s += " -> (\(right.description))" + } + } + return s } - return s - } } extension RedBlackTree: CustomStringConvertible { - public var description: String { - if root.isNullLeaf { - return "[]" - } else { - return root.description + public var description: String { + if root.isNullLeaf { + return "[]" + } else { + return root.description + } } - } } diff --git a/Red-Black Tree/RedBlackTree.swift b/Red-Black Tree/RedBlackTree.swift new file mode 100644 index 000000000..fc0411b42 --- /dev/null +++ b/Red-Black Tree/RedBlackTree.swift @@ -0,0 +1,795 @@ +//Copyright (c) 2016 Matthijs Hollemans and contributors +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in +//all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +//THE SOFTWARE. + +import Foundation + +private enum RBTreeColor { + case red + case black +} + +private enum RotationDirection { + case left + case right +} + +// MARK: - RBNode + +public class RBTreeNode: Equatable { + public typealias RBNode = RBTreeNode + + fileprivate var color: RBTreeColor = .black + fileprivate var key: T? + var leftChild: RBNode? + var rightChild: RBNode? + fileprivate weak var parent: RBNode? + + public init(key: T?, leftChild: RBNode?, rightChild: RBNode?, parent: RBNode?) { + self.key = key + self.leftChild = leftChild + self.rightChild = rightChild + self.parent = parent + + self.leftChild?.parent = self + self.rightChild?.parent = self + } + + public convenience init(key: T?) { + self.init(key: key, leftChild: RBNode(), rightChild: RBNode(), parent: RBNode()) + } + + // For initialising the nullLeaf + public convenience init() { + self.init(key: nil, leftChild: nil, rightChild: nil, parent: nil) + self.color = .black + } + + var isRoot: Bool { + return parent == nil + } + + var isLeaf: Bool { + return rightChild == nil && leftChild == nil + } + + var isNullLeaf: Bool { + return key == nil && isLeaf && color == .black + } + + var isLeftChild: Bool { + return parent?.leftChild === self + } + + var isRightChild: Bool { + return parent?.rightChild === self + } + + var grandparent: RBNode? { + return parent?.parent + } + + var sibling: RBNode? { + if isLeftChild { + return parent?.rightChild + } else { + return parent?.leftChild + } + } + + var uncle: RBNode? { + return parent?.sibling + } +} + +// MARK: - RedBlackTree + +public class RedBlackTree { + public typealias RBNode = RBTreeNode + + fileprivate(set) var root: RBNode + fileprivate(set) var size = 0 + fileprivate let nullLeaf = RBNode() + fileprivate let allowDuplicateNode: Bool + + public init(_ allowDuplicateNode: Bool = false) { + root = nullLeaf + self.allowDuplicateNode = allowDuplicateNode + } +} + +// MARK: - Size + +extension RedBlackTree { + public func count() -> Int { + return size + } + + public func isEmpty() -> Bool { + return size == 0 + } + + public func allElements() -> [T] { + var nodes: [T] = [] + + getAllElements(node: root, nodes: &nodes) + + return nodes + } + + private func getAllElements(node: RBTreeNode, nodes: inout [T]) { + guard !node.isNullLeaf else { + return + } + + if let left = node.leftChild { + getAllElements(node: left, nodes: &nodes) + } + + if let key = node.key { + nodes.append(key) + } + + if let right = node.rightChild { + getAllElements(node: right, nodes: &nodes) + } + } +} + +// MARK: - Equatable protocol + +extension RBTreeNode { + static public func == (lhs: RBTreeNode, rhs: RBTreeNode) -> Bool { + return lhs.key == rhs.key + } +} + +// MARK: - Finding a nodes successor + +extension RBTreeNode { + /* + * Returns the inorder successor node of a node + * The successor is a node with the next larger key value of the current node + */ + public func getSuccessor() -> RBNode? { + // If node has right child: successor min of this right tree + if let rightChild = self.rightChild { + if !rightChild.isNullLeaf { + return rightChild.minimum() + } + } + // Else go upward until node left child + var currentNode = self + var parent = currentNode.parent + while currentNode.isRightChild { + if let parent = parent { + currentNode = parent + } + parent = currentNode.parent + } + return parent + } +} + +// MARK: - Searching + +extension RBTreeNode { + /* + * Returns the node with the minimum key of the current subtree + */ + public func minimum() -> RBNode? { + if let leftChild = leftChild { + if !leftChild.isNullLeaf { + return leftChild.minimum() + } + return self + } + return self + } + + /* + * Returns the node with the maximum key of the current subtree + */ + public func maximum() -> RBNode? { + if let rightChild = rightChild { + if !rightChild.isNullLeaf { + return rightChild.maximum() + } + return self + } + return self + } +} + +extension RedBlackTree { + /* + * Returns the node with the given key |input| if existing + */ + public func search(input: T) -> RBNode? { + return search(key: input, node: root) + } + + /* + * Returns the node with given |key| in subtree of |node| + */ + fileprivate func search(key: T, node: RBNode?) -> RBNode? { + // If node nil -> key not found + guard let node = node else { + return nil + } + // If node is nullLeaf == semantically same as if nil + if !node.isNullLeaf { + if let nodeKey = node.key { + // Node found + if key == nodeKey { + return node + } else if key < nodeKey { + return search(key: key, node: node.leftChild) + } else { + return search(key: key, node: node.rightChild) + } + } + } + return nil + } +} + +// MARK: - Finding maximum and minimum value + +extension RedBlackTree { + /* + * Returns the minimum key value of the whole tree + */ + public func minValue() -> T? { + guard let minNode = root.minimum() else { + return nil + } + return minNode.key + } + + /* + * Returns the maximum key value of the whole tree + */ + public func maxValue() -> T? { + guard let maxNode = root.maximum() else { + return nil + } + return maxNode.key + } +} + +// MARK: - Inserting new nodes + +extension RedBlackTree { + /* + * Insert a node with key |key| into the tree + * 1. Perform normal insert operation as in a binary search tree + * 2. Fix red-black properties + * Runntime: O(log n) + */ + public func insert(key: T) { + // If key must be unique and find the key already existed, quit + if search(input: key) != nil && !allowDuplicateNode { + return + } + + if root.isNullLeaf { + root = RBNode(key: key) + } else { + insert(input: RBNode(key: key), node: root) + } + + size += 1 + } + + /* + * Nearly identical insert operation as in a binary search tree + * Differences: All nil pointers are replaced by the nullLeaf, we color the inserted node red, + * after inserting we call insertFixup to maintain the red-black properties + */ + private func insert(input: RBNode, node: RBNode) { + guard let inputKey = input.key, let nodeKey = node.key else { + return + } + if inputKey < nodeKey { + guard let child = node.leftChild else { + addAsLeftChild(child: input, parent: node) + return + } + if child.isNullLeaf { + addAsLeftChild(child: input, parent: node) + } else { + insert(input: input, node: child) + } + } else { + guard let child = node.rightChild else { + addAsRightChild(child: input, parent: node) + return + } + if child.isNullLeaf { + addAsRightChild(child: input, parent: node) + } else { + insert(input: input, node: child) + } + } + } + + private func addAsLeftChild(child: RBNode, parent: RBNode) { + parent.leftChild = child + child.parent = parent + child.color = .red + insertFixup(node: child) + } + + private func addAsRightChild(child: RBNode, parent: RBNode) { + parent.rightChild = child + child.parent = parent + child.color = .red + insertFixup(node: child) + } + + /* + * Fixes possible violations of the red-black property after insertion + * Only violation of red-black properties occurs at inserted node |z| and z.parent + * We have 3 distinct cases: case 1, 2a and 2b + * - case 1: may repeat, but only h/2 steps, where h is the height of the tree + * - case 2a -> case 2b -> red-black tree + * - case 2b -> red-black tree + */ + private func insertFixup(node z: RBNode) { + if !z.isNullLeaf { + guard let parentZ = z.parent else { + return + } + // If both |z| and his parent are red -> violation of red-black property -> need to fix it + if parentZ.color == .red { + guard let uncle = z.uncle else { + return + } + // Case 1: Uncle red -> recolor + move z + if uncle.color == .red { + parentZ.color = .black + uncle.color = .black + if let grandparentZ = parentZ.parent { + grandparentZ.color = .red + // Move z to grandparent and check again + insertFixup(node: grandparentZ) + } + } + // Case 2: Uncle black + else { + var zNew = z + // Case 2.a: z right child -> rotate + if parentZ.isLeftChild && z.isRightChild { + zNew = parentZ + leftRotate(node: zNew) + } else if parentZ.isRightChild && z.isLeftChild { + zNew = parentZ + rightRotate(node: zNew) + } + // Case 2.b: z left child -> recolor + rotate + zNew.parent?.color = .black + if let grandparentZnew = zNew.grandparent { + grandparentZnew.color = .red + if z.isLeftChild { + rightRotate(node: grandparentZnew) + } else { + leftRotate(node: grandparentZnew) + } + // We have a valid red-black-tree + } + } + } + } + root.color = .black + } +} + +// MARK: - Deleting a node +extension RedBlackTree { + /* + * Delete a node with key |key| from the tree + * 1. Perform standard delete operation as in a binary search tree + * 2. Fix red-black properties + * Runntime: O(log n) + */ + public func delete(key: T) { + if size == 1 { + root = nullLeaf + size -= 1 + } else if let node = search(key: key, node: root) { + if !node.isNullLeaf { + delete(node: node) + size -= 1 + } + } + } + + /* + * Nearly identical delete operation as in a binary search tree + * Differences: All nil pointers are replaced by the nullLeaf, + * after deleting we call insertFixup to maintain the red-black properties if the delted node was + * black (as if it was red -> no violation of red-black properties) + */ + private func delete(node z: RBNode) { + var nodeY = RBNode() + var nodeX = RBNode() + if let leftChild = z.leftChild, let rightChild = z.rightChild { + if leftChild.isNullLeaf || rightChild.isNullLeaf { + nodeY = z + } else { + if let successor = z.getSuccessor() { + nodeY = successor + } + } + } + if let leftChild = nodeY.leftChild { + if !leftChild.isNullLeaf { + nodeX = leftChild + } else if let rightChild = nodeY.rightChild { + nodeX = rightChild + } + } + nodeX.parent = nodeY.parent + if let parentY = nodeY.parent { + // Should never be the case, as parent of root = nil + if parentY.isNullLeaf { + root = nodeX + } else { + if nodeY.isLeftChild { + parentY.leftChild = nodeX + } else { + parentY.rightChild = nodeX + } + } + } else { + root = nodeX + } + if nodeY != z { + z.key = nodeY.key + } + // If sliced out node was red -> nothing to do as red-black-property holds + // If it was black -> fix red-black-property + if nodeY.color == .black { + deleteFixup(node: nodeX) + } + } + + /* + * Fixes possible violations of the red-black property after deletion + * We have w distinct cases: only case 2 may repeat, but only h many steps, where h is the height + * of the tree + * - case 1 -> case 2 -> red-black tree + * case 1 -> case 3 -> case 4 -> red-black tree + * case 1 -> case 4 -> red-black tree + * - case 3 -> case 4 -> red-black tree + * - case 4 -> red-black tree + */ + private func deleteFixup(node x: RBNode) { + var xTmp = x + if !x.isRoot && x.color == .black { + guard var sibling = x.sibling else { + return + } + // Case 1: Sibling of x is red + if sibling.color == .red { + // Recolor + sibling.color = .black + if let parentX = x.parent { + parentX.color = .red + // Rotation + if x.isLeftChild { + leftRotate(node: parentX) + } else { + rightRotate(node: parentX) + } + // Update sibling + if let sibl = x.sibling { + sibling = sibl + } + } + } + // Case 2: Sibling is black with two black children + if sibling.leftChild?.color == .black && sibling.rightChild?.color == .black { + // Recolor + sibling.color = .red + // Move fake black unit upwards + if let parentX = x.parent { + deleteFixup(node: parentX) + } + // We have a valid red-black-tree + } else { + // Case 3: a. Sibling black with one black child to the right + if x.isLeftChild && sibling.rightChild?.color == .black { + // Recolor + sibling.leftChild?.color = .black + sibling.color = .red + // Rotate + rightRotate(node: sibling) + // Update sibling of x + if let sibl = x.sibling { + sibling = sibl + } + } + // Still case 3: b. One black child to the left + else if x.isRightChild && sibling.leftChild?.color == .black { + // Recolor + sibling.rightChild?.color = .black + sibling.color = .red + // Rotate + leftRotate(node: sibling) + // Update sibling of x + if let sibl = x.sibling { + sibling = sibl + } + } + // Case 4: Sibling is black with red right child + // Recolor + if let parentX = x.parent { + sibling.color = parentX.color + parentX.color = .black + // a. x left and sibling with red right child + if x.isLeftChild { + sibling.rightChild?.color = .black + // Rotate + leftRotate(node: parentX) + } + // b. x right and sibling with red left child + else { + sibling.leftChild?.color = .black + //Rotate + rightRotate(node: parentX) + } + // We have a valid red-black-tree + xTmp = root + } + } + } + xTmp.color = .black + } +} + +// MARK: - Rotation +extension RedBlackTree { + /* + * Left rotation around node x + * Assumes that x.rightChild y is not a nullLeaf, rotates around the link from x to y, + * makes y the new root of the subtree with x as y's left child and y's left child as x's right + * child, where n = a node, [n] = a subtree + * | | + * x y + * / \ ~> / \ + * [A] y x [C] + * / \ / \ + * [B] [C] [A] [B] + */ + fileprivate func leftRotate(node x: RBNode) { + rotate(node: x, direction: .left) + } + + /* + * Right rotation around node y + * Assumes that y.leftChild x is not a nullLeaf, rotates around the link from y to x, + * makes x the new root of the subtree with y as x's right child and x's right child as y's left + * child, where n = a node, [n] = a subtree + * | | + * x y + * / \ <~ / \ + * [A] y x [C] + * / \ / \ + * [B] [C] [A] [B] + */ + fileprivate func rightRotate(node x: RBNode) { + rotate(node: x, direction: .right) + } + + /* + * Rotation around a node x + * Is a local operation preserving the binary-search-tree property that only exchanges pointers. + * Runntime: O(1) + */ + private func rotate(node x: RBNode, direction: RotationDirection) { + var nodeY: RBNode? = RBNode() + + // Set |nodeY| and turn |nodeY|'s left/right subtree into |x|'s right/left subtree + switch direction { + case .left: + nodeY = x.rightChild + x.rightChild = nodeY?.leftChild + x.rightChild?.parent = x + case .right: + nodeY = x.leftChild + x.leftChild = nodeY?.rightChild + x.leftChild?.parent = x + } + + // Link |x|'s parent to nodeY + nodeY?.parent = x.parent + if x.isRoot { + if let node = nodeY { + root = node + } + } else if x.isLeftChild { + x.parent?.leftChild = nodeY + } else if x.isRightChild { + x.parent?.rightChild = nodeY + } + + // Put |x| on |nodeY|'s left + switch direction { + case .left: + nodeY?.leftChild = x + case .right: + nodeY?.rightChild = x + } + x.parent = nodeY + } +} + +// MARK: - Verify +extension RedBlackTree { + /* + * Verifies that the existing tree fulfills all red-black properties + * Returns true if the tree is a valid red-black tree, false otherwise + */ + public func verify() -> Bool { + if root.isNullLeaf { + print("The tree is empty") + return true + } + return property2() && property4() && property5() + } + + // Property 1: Every node is either red or black -> fullfilled through setting node.color of type + // RBTreeColor + + // Property 2: The root is black + private func property2() -> Bool { + if root.color == .red { + print("Property-Error: Root is red") + return false + } + return true + } + + // Property 3: Every nullLeaf is black -> fullfilled through initialising nullLeafs with color = black + + // Property 4: If a node is red, then both its children are black + private func property4() -> Bool { + return property4(node: root) + } + + private func property4(node: RBNode) -> Bool { + if node.isNullLeaf { + return true + } + if let leftChild = node.leftChild, let rightChild = node.rightChild { + if node.color == .red { + if !leftChild.isNullLeaf && leftChild.color == .red { + print("Property-Error: Red node with key \(String(describing: node.key)) has red left child") + return false + } + if !rightChild.isNullLeaf && rightChild.color == .red { + print("Property-Error: Red node with key \(String(describing: node.key)) has red right child") + return false + } + } + return property4(node: leftChild) && property4(node: rightChild) + } + return false + } + + // Property 5: For each node, all paths from the node to descendant leaves contain the same number + // of black nodes (same blackheight) + private func property5() -> Bool { + if property5(node: root) == -1 { + return false + } else { + return true + } + } + + private func property5(node: RBNode) -> Int { + if node.isNullLeaf { + return 0 + } + guard let leftChild = node.leftChild, let rightChild = node.rightChild else { + return -1 + } + let left = property5(node: leftChild) + let right = property5(node: rightChild) + + if left == -1 || right == -1 { + return -1 + } else if left == right { + let addedHeight = node.color == .black ? 1 : 0 + return left + addedHeight + } else { + print("Property-Error: Black height violated at node with key \(String(describing: node.key))") + return -1 + } + } +} + +// MARK: - Debugging + +extension RBTreeNode: CustomDebugStringConvertible { + public var debugDescription: String { + var s = "" + if isNullLeaf { + s = "nullLeaf" + } else { + if let key = key { + s = "key: \(key)" + } else { + s = "key: nil" + } + if let parent = parent { + s += ", parent: \(String(describing: parent.key))" + } + if let left = leftChild { + s += ", left = [" + left.debugDescription + "]" + } + if let right = rightChild { + s += ", right = [" + right.debugDescription + "]" + } + s += ", color = \(color)" + } + return s + } +} + +extension RedBlackTree: CustomDebugStringConvertible { + public var debugDescription: String { + return root.debugDescription + } +} + +extension RBTreeNode: CustomStringConvertible { + public var description: String { + var s = "" + if isNullLeaf { + s += "nullLeaf" + } else { + if let left = leftChild { + s += "(\(left.description)) <- " + } + if let key = key { + s += "\(key)" + } else { + s += "nil" + } + s += ", \(color)" + if let right = rightChild { + s += " -> (\(right.description))" + } + } + return s + } +} + +extension RedBlackTree: CustomStringConvertible { + public var description: String { + if root.isNullLeaf { + return "[]" + } else { + return root.description + } + } +} diff --git a/Selection Sampling/README.markdown b/Selection Sampling/README.markdown index 741f592f4..5c2ff5e25 100644 --- a/Selection Sampling/README.markdown +++ b/Selection Sampling/README.markdown @@ -12,7 +12,7 @@ func select(from a: [T], count k: Int) -> [T] { for i in 0..(from a: [T], count k: Int) -> [T] { for i in 0..() +k.insert(key: 10, data: "10") +k.insert(key: 12, data: "12") +k.insert(key: 13, data: "13") +k.insert(key: 20, data: "20") +k.insert(key: 24, data: "24") + +if let value = k.get(key: 20) { + print(value) +} else { + print("not found!") +} diff --git a/Skip-List/SkipList.playground/Sources/SkipList.swift b/Skip-List/SkipList.playground/Sources/SkipList.swift index f626da0f8..186286977 100644 --- a/Skip-List/SkipList.playground/Sources/SkipList.swift +++ b/Skip-List/SkipList.playground/Sources/SkipList.swift @@ -212,7 +212,7 @@ extension SkipList { } } - func insert(key: Key, data: Payload) { + public func insert(key: Key, data: Payload) { if head != nil { if let node = findNode(key: key) { // replace, in case of key already exists. diff --git a/Skip-List/SkipList.swift b/Skip-List/SkipList.swift index fc635d0f2..8a1959f70 100644 --- a/Skip-List/SkipList.swift +++ b/Skip-List/SkipList.swift @@ -212,7 +212,7 @@ extension SkipList { } } - func insert(key: Key, data: Payload) { + public func insert(key: Key, data: Payload) { if head != nil { if let node = findNode(key: key) { // replace, in case of key already exists. diff --git a/Slow Sort/README.markdown b/Slow Sort/README.markdown index f8cc743cc..f7742b6a3 100644 --- a/Slow Sort/README.markdown +++ b/Slow Sort/README.markdown @@ -17,19 +17,17 @@ We can decompose the problem of sorting n numbers in ascending order into Here is an implementation of slow sort in Swift: ```swift -public func slowsort(_ i: Int, _ j: Int) { - if i>=j { - return - } +func slowSort(_ i: Int, _ j: Int, _ numberList: inout [Int]) { + guard if i < j else { return } let m = (i+j)/2 - slowsort(i,m) - slowsort(m+1,j) + slowSort(i, m, &numberList) + slowSort(m+1, j, &numberList) if numberList[j] < numberList[m] { let temp = numberList[j] numberList[j] = numberList[m] numberList[m] = temp } - slowsort(i,j-1) + slowSort(i, j-1, &numberList) } ``` @@ -47,4 +45,4 @@ public func slowsort(_ i: Int, _ j: Int) { *Written for Swift Algorithm Club by Lukas Schramm* -(used the Insertion Sort Readme as template) \ No newline at end of file +(used the Insertion Sort Readme as template) diff --git a/Slow Sort/SlowSort.playground/Sources/SlowSort.swift b/Slow Sort/SlowSort.playground/Sources/SlowSort.swift index 32bb38505..cf09dcc5d 100644 --- a/Slow Sort/SlowSort.playground/Sources/SlowSort.swift +++ b/Slow Sort/SlowSort.playground/Sources/SlowSort.swift @@ -1,16 +1,15 @@ import Foundation -public func slowsort(_ i: Int, _ j: Int, _ numberList: inout [Int]) { - if i>=j { - return - } +public func slowSort(_ i: Int, _ j: Int, _ numberList: inout [Int]) { + guard i < j else { return } + let m = (i+j)/2 - slowsort(i, m, &numberList) - slowsort(m+1, j, &numberList) + slowSort(i, m, &numberList) + slowSort(m+1, j, &numberList) if numberList[j] < numberList[m] { let temp = numberList[j] numberList[j] = numberList[m] numberList[m] = temp } - slowsort(i, j-1, &numberList) + slowSort(i, j-1, &numberList) } diff --git a/Splay Tree/SplayTree.playground/Contents.swift b/Splay Tree/SplayTree.playground/Contents.swift index a8a0bc5c6..28a201b37 100644 --- a/Splay Tree/SplayTree.playground/Contents.swift +++ b/Splay Tree/SplayTree.playground/Contents.swift @@ -5,29 +5,10 @@ print("Hello, Swift 4!") #endif -let splayTree = SplayTree(value: 1) -splayTree.insert(value: 2) -splayTree.insert(value: 10) -splayTree.insert(value: 6) - -splayTree.remove(value: 10) -splayTree.remove(value: 6) - -splayTree.insert(value: 55) -splayTree.insert(value: 559) -splayTree.remove(value: 2) -splayTree.remove(value: 1) -splayTree.remove(value: 55) -splayTree.remove(value: 559) - -splayTree.insert(value: 1843000) -splayTree.insert(value: 1238) -splayTree.insert(value: -1) -splayTree.insert(value: 87) - -splayTree.minimum() -splayTree.maximum() - - - - +var tree = SplayTree(value: 0) +tree.insert(value: 2) +tree.insert(value: 3) +tree.insert(value: 4) +tree.insert(value: 7) +_ = tree.search(value: 2) +tree.remove(value: 2) diff --git a/Splay Tree/SplayTree.playground/Sources/SplayTree.swift b/Splay Tree/SplayTree.playground/Sources/SplayTree.swift index 9e484129c..f450eaced 100644 --- a/Splay Tree/SplayTree.playground/Sources/SplayTree.swift +++ b/Splay Tree/SplayTree.playground/Sources/SplayTree.swift @@ -289,120 +289,42 @@ extension Node { - Node Resulting from the deletion and the splaying of the removed node */ - public func remove(value: T) -> Node? { - let replacement: Node? + fileprivate func remove(value: T) -> Node? { + guard let target = search(value: value) else { return self } - if let v = self.value, v == value { + if let left = target.left, let right = target.right { + let largestOfLeftChild = left.maximum() + left.parent = nil + right.parent = nil - var parentToSplay: Node? - if let left = left { - if let right = right { - - replacement = removeNodeWithTwoChildren(left, right) - - if let replacement = replacement, - let replacementParent = replacement.parent, - replacementParent.value != self.value { - - parentToSplay = replacement.parent - - } else if self.parent != nil { - parentToSplay = self.parent - } else { - parentToSplay = replacement - } - - } else { - // This node only has a left child. The left child replaces the node. - replacement = left - if self.parent != nil { - parentToSplay = self.parent - } else { - parentToSplay = replacement - } - } - } else if let right = right { - // This node only has a right child. The right child replaces the node. - replacement = right - if self.parent != nil { - parentToSplay = self.parent - } else { - parentToSplay = replacement - } - } else { - // This node has no children. We just disconnect it from its parent. - replacement = nil - parentToSplay = parent - } - - reconnectParentTo(node: replacement) + SplayOperation.splay(node: largestOfLeftChild) + largestOfLeftChild.right = right - // performs the splay operation - if let parentToSplay = parentToSplay { - SplayOperation.splay(node: parentToSplay) - } + return largestOfLeftChild - // The current node is no longer part of the tree, so clean it up. - parent = nil - left = nil - right = nil + } else if let left = target.left { + replace(node: target, with: left) + return left - return parentToSplay + } else if let right = target.right { + replace(node: target, with: right) + return right - } else if let v = self.value, value < v { - if left != nil { - return left!.remove(value: value) - } else { - let node = self - SplayOperation.splay(node: node) - return node - - } } else { - if right != nil { - return right?.remove(value: value) - } else { - let node = self - SplayOperation.splay(node: node) - return node - - } + return nil } } - private func removeNodeWithTwoChildren(_ left: Node, _ right: Node) -> Node { - // This node has two children. It must be replaced by the smallest - // child that is larger than this node's value, which is the leftmost - // descendent of the right child. - let successor = right.minimum() + private func replace(node: Node, with newNode: Node?) { + guard let sourceParent = sourceNode.parent else { return } - // Connect our left child with the new node. - successor.left = left - left.parent = successor - - // Connect our right child with the new node. If the right child does - // not have any left children of its own, then the in-order successor - // *is* the right child. - if right !== successor { - successor.right = right - right.parent = successor + if sourceNode.isLeftChild { + sourceParent.left = newNode } else { - successor.right = nil + sourceParent.right = newNode } - // And finally, connect the successor node to our parent. - return successor - } - - private func reconnectParentTo(node: Node?) { - if let parent = parent { - if isLeftChild { - parent.left = node - } else { - parent.right = node - } - } - node?.parent = parent + newNode?.parent = sourceParent } } diff --git a/Splay Tree/SplayTree.swift b/Splay Tree/SplayTree.swift index a7b20743d..0f1fa48f0 100644 --- a/Splay Tree/SplayTree.swift +++ b/Splay Tree/SplayTree.swift @@ -44,7 +44,7 @@ public enum SplayOperation { - Returns - Operation Case zigZag - zigZig - zig */ - public static func operation(forNode node: Node) -> SplayOperation { + public static func operation(forNode node: Node) -> SplayOperation { if let parent = node.parent, let _ = parent.parent { if (node.isLeftChild && parent.isRightChild) || (node.isRightChild && parent.isLeftChild) { @@ -289,120 +289,42 @@ extension Node { - Node Resulting from the deletion and the splaying of the removed node */ - public func remove(value: T) -> Node? { - let replacement: Node? + fileprivate func remove(value: T) -> Node? { + guard let target = search(value: value) else { return self } - if let v = self.value, v == value { + if let left = target.left, let right = target.right { + let largestOfLeftChild = left.maximum() + left.parent = nil + right.parent = nil - var parentToSplay: Node? - if let left = left { - if let right = right { - - replacement = removeNodeWithTwoChildren(left, right) - - if let replacement = replacement, - let replacementParent = replacement.parent, - replacementParent.value != self.value { - - parentToSplay = replacement.parent - - } else if self.parent != nil { - parentToSplay = self.parent - } else { - parentToSplay = replacement - } - - } else { - // This node only has a left child. The left child replaces the node. - replacement = left - if self.parent != nil { - parentToSplay = self.parent - } else { - parentToSplay = replacement - } - } - } else if let right = right { - // This node only has a right child. The right child replaces the node. - replacement = right - if self.parent != nil { - parentToSplay = self.parent - } else { - parentToSplay = replacement - } - } else { - // This node has no children. We just disconnect it from its parent. - replacement = nil - parentToSplay = parent - } + SplayOperation.splay(node: largestOfLeftChild) + largestOfLeftChild.right = right - reconnectParentTo(node: replacement) + return largestOfLeftChild - // performs the splay operation - if let parentToSplay = parentToSplay { - SplayOperation.splay(node: parentToSplay) - } + } else if let left = target.left { + replace(node: target, with: left) + return left - // The current node is no longer part of the tree, so clean it up. - parent = nil - left = nil - right = nil - - return parentToSplay + } else if let right = target.right { + replace(node: target, with: right) + return right - } else if let v = self.value, value < v { - if left != nil { - return left!.remove(value: value) - } else { - let node = self - SplayOperation.splay(node: node) - return node - - } } else { - if right != nil { - return right?.remove(value: value) - } else { - let node = self - SplayOperation.splay(node: node) - return node - - } + return nil } } - private func removeNodeWithTwoChildren(_ left: Node, _ right: Node) -> Node { - // This node has two children. It must be replaced by the smallest - // child that is larger than this node's value, which is the leftmost - // descendent of the right child. - let successor = right.minimum() + private func replace(node: Node, with newNode: Node?) { + guard let sourceParent = sourceNode.parent else { return } - // Connect our left child with the new node. - successor.left = left - left.parent = successor - - // Connect our right child with the new node. If the right child does - // not have any left children of its own, then the in-order successor - // *is* the right child. - if right !== successor { - successor.right = right - right.parent = successor + if sourceNode.isLeftChild { + sourceParent.left = newNode } else { - successor.right = nil + sourceParent.right = newNode } - // And finally, connect the successor node to our parent. - return successor - } - - private func reconnectParentTo(node: Node?) { - if let parent = parent { - if isLeftChild { - parent.left = node - } else { - parent.right = node - } - } - node?.parent = parent + newNode?.parent = sourceParent } } diff --git a/Splay Tree/readme.md b/Splay Tree/readme.md index 75c3c0389..8a852c950 100644 --- a/Splay Tree/readme.md +++ b/Splay Tree/readme.md @@ -102,8 +102,11 @@ To insert a value: To delete a value: -- Delete it as in a binary search tree -- Splay the parent of the removed node to the root +- Perform search for the value, after performed search function if the tree contains the value, it'll be the root of the new tree. +- If the tree has only left child, change left child to the root of the tree, remove the old root node +- If the tree has only right child, change right child to the root of the tree, remove the old root node +- Else, the tree has both two children, set parent of two children to nil, so that they're two new trees (left-tree and right-tree). +- Splay the minimum node of right-tree (or minimum node of left-tree), then set left-tree as left child of new root of right-tree (or set right-tree as right child of new root of left-tree), return right-tree ### Search diff --git a/Topological Sort/TopologicalSort1.swift b/Topological Sort/TopologicalSort1.swift index 2ad9b51a0..05f1ab887 100644 --- a/Topological Sort/TopologicalSort1.swift +++ b/Topological Sort/TopologicalSort1.swift @@ -1,6 +1,7 @@ extension Graph { private func depthFirstSearch(_ source: Node, visited: inout [Node : Bool]) -> [Node] { var result = [Node]() + visited[source] = true if let adjacencyList = adjacencyList(forNode: source) { for nodeInAdjacencyList in adjacencyList { @@ -10,7 +11,6 @@ extension Graph { } } - visited[source] = true return [source] + result } diff --git a/Trie/Trie/Trie/Trie.swift b/Trie/Trie/Trie/Trie.swift index accc7b61c..3d5031cf3 100644 --- a/Trie/Trie/Trie/Trie.swift +++ b/Trie/Trie/Trie/Trie.swift @@ -117,9 +117,12 @@ extension Trie { /// Determines whether a word is in the trie. /// - /// - Parameter word: the word to check for + /// - Parameters: + /// - word: the word to check for + /// - matchPrefix: whether the search word should match + /// if it is only a prefix of other nodes in the trie /// - Returns: true if the word is present, false otherwise. - func contains(word: String) -> Bool { + func contains(word: String, matchPrefix: Bool = false) -> Bool { guard !word.isEmpty else { return false } @@ -130,7 +133,7 @@ extension Trie { } currentNode = childNode } - return currentNode.isTerminating + return matchPrefix || currentNode.isTerminating } /// Attempts to walk to the last node of a word. The diff --git a/Trie/Trie/TrieTests/TrieTests.swift b/Trie/Trie/TrieTests/TrieTests.swift index 0c5bc69c6..59da2cd9b 100644 --- a/Trie/Trie/TrieTests/TrieTests.swift +++ b/Trie/Trie/TrieTests/TrieTests.swift @@ -167,7 +167,8 @@ class TrieTests: XCTestCase { let trieCopy = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? Trie XCTAssertEqual(trieCopy?.count, trie.count) } - + + /// Tests whether word prefixes are properly found and returned. func testFindWordsWithPrefix() { let trie = Trie() trie.insert(word: "test") @@ -190,4 +191,28 @@ class TrieTests: XCTestCase { let wordsUpperCase = trie.findWordsWithPrefix(prefix: "Te") XCTAssertEqual(wordsUpperCase.sorted(), ["team", "test"]) } + + /// Tests whether word prefixes are properly detected on a boolean contains() check. + func testContainsWordMatchPrefix() { + let trie = Trie() + trie.insert(word: "test") + trie.insert(word: "another") + trie.insert(word: "exam") + let wordsAll = trie.contains(word: "", matchPrefix: true) + XCTAssertEqual(wordsAll, true) + let words = trie.contains(word: "ex", matchPrefix: true) + XCTAssertEqual(words, true) + trie.insert(word: "examination") + let words2 = trie.contains(word: "exam", matchPrefix: true) + XCTAssertEqual(words2, true) + let noWords = trie.contains(word: "tee", matchPrefix: true) + XCTAssertEqual(noWords, false) + let unicodeWord = "😬😎" + trie.insert(word: unicodeWord) + let wordsUnicode = trie.contains(word: "😬", matchPrefix: true) + XCTAssertEqual(wordsUnicode, true) + trie.insert(word: "Team") + let wordsUpperCase = trie.contains(word: "Te", matchPrefix: true) + XCTAssertEqual(wordsUpperCase, true) + } }