diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e5d00c17b..29f4ca7e1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,7 +6,7 @@ Want to help out with the Swift Algorithm Club? Great! While we don't have stric Our repo is all about learning. The `README` file is the cake, and the sample code is the cherry on top. A good contribution has succinct explanations supported by diagrams. Code is best introduced in chunks, weaved into the explanations where relevant. -> When choosing between brievity and performance, err to the side of brievity as long as the time complexity of the particular implementation is the same. You can make a note afterwards suggesting a more performant way of doing things. +> When choosing between brevity and performance, err to the side of brevity as long as the time complexity of the particular implementation is the same. You can make a note afterwards suggesting a more performant way of doing things. **API Design Guidelines** @@ -22,7 +22,7 @@ We follow the following Swift [style guide](https://github.com/raywenderlich/swi Unit tests. Fixes for typos. No contribution is too small. :-) -The repository has over 100 different data structures and algorithms. We're always interested in improvements to existing implementations and better explanations. Suggestions for making the code more Swift-like or to make it fit better with the standard library is most welcomed. +The repository has over 100 different data structures and algorithms. We're always interested in improvements to existing implementations and better explanations. Suggestions for making the code more Swift-like or to make it fit better with the standard library are most welcome. ### New Contributions diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 56e205656..000000000 --- a/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ -language: objective-c -osx_image: xcode9 -# sudo: false - -install: - -- gem install xcpretty-travis-formatter -# - ./install_swiftlint.sh - -script: - -- xcodebuild test -project ./Array2D/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./All-Pairs\ Shortest\ Paths/APSP/APSP.xcodeproj -scheme APSPTests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./AVL\ Tree/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Binary\ Search/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Boyer-Moore-Horspool/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Binary\ Search\ Tree/Solution\ 1/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Bloom\ Filter/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Bounded\ Priority\ Queue/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Breadth-First\ Search/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Bucket\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./B-Tree/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Comb\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Convex\ Hull/Convex\ Hull.xcodeproj -scheme Tests -destination 'platform=iOS Simulator,name=iPhone 7,OS=11.0' | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Counting\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Depth-First\ Search/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Graph/Graph.xcodeproj -scheme GraphTests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Heap/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Heap\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Insertion\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./K-Means/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Linked\ List/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Singly\ Linked\ List/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Longest\ Common\ Subsequence/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Minimum\ Spanning\ Tree\ \(Unweighted\)/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Priority\ Queue/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Queue/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Quicksort/Tests/Tests-Quicksort.xcodeproj -scheme Test-Quicksort | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Radix\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Rootish\ Array\ Stack/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Select\ Minimum\ Maximum/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Selection\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Shell\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Shortest\ Path\ \(Unweighted\)/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Single-Source\ Shortest\ Paths\ \(Weighted\)/SSSP.xcodeproj -scheme SSSPTests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Stack/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Topological\ Sort/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Treap/Treap/Treap.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Palindromes/Test/Test.xcodeproj -scheme Test | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Ternary\ Search\ Tree/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` -- xcodebuild test -project ./Karatsuba\ Multiplication/Tests/Tests.xcodeproj -scheme Tests | xcpretty -f `xcpretty-travis-formatter` - -after_success: - -- if [[ "$TRAVIS_BRANCH" == "master" ]]; then ./gfm-render.sh; fi diff --git a/3Sum and 4Sum/3Sum.playground/Contents.swift b/3Sum and 4Sum/3Sum.playground/Contents.swift index 2581da6e7..d172aed1f 100644 --- a/3Sum and 4Sum/3Sum.playground/Contents.swift +++ b/3Sum and 4Sum/3Sum.playground/Contents.swift @@ -1,3 +1,7 @@ +// last checked with Xcode 10.1 +#if swift(>=4.2) +print("Hello, Swift 4.2!") +#endif extension Collection where Element: Equatable { diff --git a/Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/contents.xcworkspacedata b/3Sum and 4Sum/3Sum.playground/playground.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/contents.xcworkspacedata rename to 3Sum and 4Sum/3Sum.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/3Sum and 4Sum/3Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to 3Sum and 4Sum/3Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/3Sum and 4Sum/4Sum.playground/Contents.swift b/3Sum and 4Sum/4Sum.playground/Contents.swift index 86b530224..37f75918c 100644 --- a/3Sum and 4Sum/4Sum.playground/Contents.swift +++ b/3Sum and 4Sum/4Sum.playground/Contents.swift @@ -1,3 +1,8 @@ +// last checked with Xcode 10.1 +#if swift(>=4.2) +print("Hello, Swift 4.2!") +#endif + extension Collection where Element: Equatable { /// In a sorted collection, replaces the given index with a successor mapping to a unique element. diff --git a/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/contents.xcworkspacedata b/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/swift-algorithm-club.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from swift-algorithm-club.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to 3Sum and 4Sum/4Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist 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/All-Pairs Shortest Paths/APSP/APSP.playground/Contents.swift b/All-Pairs Shortest Paths/APSP/APSP.playground/Contents.swift index b1b499b39..24c27cac8 100644 --- a/All-Pairs Shortest Paths/APSP/APSP.playground/Contents.swift +++ b/All-Pairs Shortest Paths/APSP/APSP.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 Graph import APSP diff --git a/All-Pairs Shortest Paths/APSP/APSP.playground/contents.xcplayground b/All-Pairs Shortest Paths/APSP/APSP.playground/contents.xcplayground index 06828af92..d5a8d0e3f 100644 --- a/All-Pairs Shortest Paths/APSP/APSP.playground/contents.xcplayground +++ b/All-Pairs Shortest Paths/APSP/APSP.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj b/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj index 908d32785..dc8e509a1 100644 --- a/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj +++ b/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj @@ -187,12 +187,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 493D8DDF1CDD2A1C0089795A = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 0820; + LastSwiftMigration = 1010; }; 493D8DF01CDD5B960089795A = { CreatedOnToolsVersion = 7.3; @@ -302,14 +302,22 @@ 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_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; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -349,14 +357,22 @@ 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_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; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -388,7 +404,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.APSPTests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -400,7 +416,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.APSPTests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -420,7 +436,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.APSP"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -443,7 +458,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.APSP"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; diff --git a/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/xcshareddata/xcschemes/APSP.xcscheme b/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/xcshareddata/xcschemes/APSP.xcscheme index e313c4b88..944957d8f 100644 --- a/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/xcshareddata/xcschemes/APSP.xcscheme +++ b/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/xcshareddata/xcschemes/APSP.xcscheme @@ -1,6 +1,6 @@ - IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded - + IDEDidComputeMac32BitWarning + diff --git a/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift b/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift index c2f64e239..ce0dfcf32 100644 --- a/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift +++ b/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift @@ -19,14 +19,6 @@ struct TestCase where T: Hashable { } class APSPTests: XCTestCase { - - func testSwift4() { - // last checked with Xcode 9.0b4 - #if swift(>=4.0) - print("Hello, Swift 4!") - #endif - } - /** See Figure 25.1 of “Introduction to Algorithms” by Cormen et al, 3rd ed., pg 690 */ 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/Big-O Notation.markdown b/Big-O Notation.markdown index 257f80462..c3f020df2 100644 --- a/Big-O Notation.markdown +++ b/Big-O Notation.markdown @@ -18,6 +18,11 @@ Big-O | Name | Description **O(n!)** | factorial | **Intolerably slow.** It literally takes a million years to do anything. + +![Comparison of Big O computations](https://upload.wikimedia.org/wikipedia/commons/7/7e/Comparison_computational_complexity.svg) + + + Below are some examples for each category of performance: **O(1)** @@ -119,9 +124,8 @@ Below are some examples for each category of performance: func solveHanoi(n: Int, from: String, to: String, spare: String) { guard n >= 1 else { return } if n > 1 { - solveHanoi(n: n - 1, from: from, to: spare, spare: to) - } else { - solveHanoi(n: n - 1, from: spare, to: to, spare: from) + solveHanoi(n: n - 1, from: from, to: spare, spare: to) + solveHanoi(n: n - 1, from: spare, to: to, spare: from) } } ``` 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/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift b/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift index 34ade3896..2d0eb7e57 100644 --- a/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift +++ b/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift @@ -235,7 +235,7 @@ extension BinarySearchTree { /* Finds the node whose value precedes our value in sorted order. */ - public func predecessor() -> BinarySearchTree? { + public func predecessor() -> BinarySearchTree? { if let left = left { return left.maximum() } else { @@ -251,7 +251,7 @@ extension BinarySearchTree { /* Finds the node whose value succeeds our value in sorted order. */ - public func successor() -> BinarySearchTree? { + public func successor() -> BinarySearchTree? { if let right = right { return right.minimum() } else { diff --git a/Bit Set/BitSet.playground/Contents.swift b/Bit Set/BitSet.playground/Contents.swift index d11b36e8c..70d5ca5e8 100644 --- a/Bit Set/BitSet.playground/Contents.swift +++ b/Bit Set/BitSet.playground/Contents.swift @@ -89,3 +89,23 @@ z.all1() // false //var bigBits = BitSet(size: 10000) //print(bigBits) + +var smallBitSet = BitSet(size: 16) +smallBitSet[5] = true +smallBitSet[10] = true +print(smallBitSet >> 3) +print(smallBitSet << 6) // one bit shifts off the end + +var bigBitSet = BitSet( size: 120 ) +bigBitSet[1] = true +bigBitSet[3] = true +bigBitSet[7] = true +bigBitSet[32] = true +bigBitSet[55] = true +bigBitSet[64] = true +bigBitSet[80] = true +print(bigBitSet) +print(bigBitSet << 32) +print(bigBitSet << 64) +print(bigBitSet >> 32) +print(bigBitSet >> 64) diff --git a/Bit Set/BitSet.playground/Sources/BitSet.swift b/Bit Set/BitSet.playground/Sources/BitSet.swift index 59306f5a5..258bce457 100644 --- a/Bit Set/BitSet.playground/Sources/BitSet.swift +++ b/Bit Set/BitSet.playground/Sources/BitSet.swift @@ -9,7 +9,7 @@ public struct BitSet { We store the bits in a list of unsigned 64-bit integers. The first entry, `words[0]`, is the least significant word. */ - private let N = 64 + fileprivate let N = 64 public typealias Word = UInt64 fileprivate(set) public var words: [Word] @@ -221,6 +221,50 @@ prefix public func ~ (rhs: BitSet) -> BitSet { return out } +// MARK: - Bit shift operations + +/* + Note: For bitshift operations, the assumption is that any bits that are + shifted off the end of the end of the declared size are not still set. + In other words, we are maintaining the original number of bits. + */ + +public func << (lhs: BitSet, numBitsLeft: Int) -> BitSet { + var out = lhs + let offset = numBitsLeft / lhs.N + let shift = numBitsLeft % lhs.N + for i in 0..= 0) { + out.words[i] = lhs.words[i - offset] << shift + } + if (i - offset - 1 >= 0) { + out.words[i] |= lhs.words[i - offset - 1] >> (lhs.N - shift) + } + } + + out.clearUnusedBits() + return out +} + +public func >> (lhs: BitSet, numBitsRight: Int) -> BitSet { + var out = lhs + let offset = numBitsRight / lhs.N + let shift = numBitsRight % lhs.N + for i in 0..> shift + } + if (i + offset + 1 < lhs.words.count) { + out.words[i] |= lhs.words[i + offset + 1] << (lhs.N - shift) + } + } + + out.clearUnusedBits() + return out +} + // MARK: - Debugging extension UInt64 { diff --git a/Bit Set/BitSet.swift b/Bit Set/BitSet.swift deleted file mode 100644 index cfe8517b5..000000000 --- a/Bit Set/BitSet.swift +++ /dev/null @@ -1,248 +0,0 @@ -/* - A fixed-size sequence of n bits. Bits have indices 0 to n-1. - */ -public struct BitSet { - /* How many bits this object can hold. */ - private(set) public var size: Int - - /* - We store the bits in a list of unsigned 64-bit integers. - The first entry, `words[0]`, is the least significant word. - */ - private let N = 64 - public typealias Word = UInt64 - fileprivate(set) public var words: [Word] - - private let allOnes = ~Word() - - /* Creates a bit set that can hold `size` bits. All bits are initially 0. */ - public init(size: Int) { - precondition(size > 0) - self.size = size - - // Round up the count to the next multiple of 64. - let n = (size + (N-1)) / N - words = [Word](repeating: 0, count: n) - } - - /* Converts a bit index into an array index and a mask inside the word. */ - private func indexOf(_ i: Int) -> (Int, Word) { - precondition(i >= 0) - precondition(i < size) - let o = i / N - let m = Word(i - o*N) - return (o, 1 << m) - } - - /* Returns a mask that has 1s for all bits that are in the last word. */ - private func lastWordMask() -> Word { - let diff = words.count*N - size - if diff > 0 { - // Set the highest bit that's still valid. - let mask = 1 << Word(63 - diff) - // Subtract 1 to turn it into a mask, and add the high bit back in. - return (Word)(mask | (mask - 1)) - } else { - return allOnes - } - } - - /* - If the size is not a multiple of N, then we have to clear out the bits - that we're not using, or bitwise operations between two differently sized - BitSets will go wrong. - */ - fileprivate mutating func clearUnusedBits() { - words[words.count - 1] &= lastWordMask() - } - - /* So you can write bitset[99] = ... */ - public subscript(i: Int) -> Bool { - get { return isSet(i) } - set { if newValue { set(i) } else { clear(i) } } - } - - /* Sets the bit at the specified index to 1. */ - public mutating func set(_ i: Int) { - let (j, m) = indexOf(i) - words[j] |= m - } - - /* Sets all the bits to 1. */ - public mutating func setAll() { - for i in 0.. Bool { - let (j, m) = indexOf(i) - words[j] ^= m - return (words[j] & m) != 0 - } - - /* Determines whether the bit at the specific index is 1 (true) or 0 (false). */ - public func isSet(_ i: Int) -> Bool { - let (j, m) = indexOf(i) - return (words[j] & m) != 0 - } - - /* - Returns the number of bits that are 1. Time complexity is O(s) where s is - the number of 1-bits. - */ - public var cardinality: Int { - var count = 0 - for var x in words { - while x != 0 { - let y = x & ~(x - 1) // find lowest 1-bit - x = x ^ y // and erase it - count += 1 - } - } - return count - } - - /* Checks if all the bits are set. */ - public func all1() -> Bool { - for i in 0.. Bool { - for x in words { - if x != 0 { return true } - } - return false - } - - /* Checks if none of the bits are set. */ - public func all0() -> Bool { - for x in words { - if x != 0 { return false } - } - return true - } -} - -// MARK: - Equality - -extension BitSet: Equatable { -} - -public func == (lhs: BitSet, rhs: BitSet) -> Bool { - return lhs.words == rhs.words -} - -// MARK: - Hashing - -extension BitSet: Hashable { - /* Based on the hashing code from Java's BitSet. */ - public var hashValue: Int { - var h = Word(1234) - for i in stride(from: words.count, to: 0, by: -1) { - h ^= words[i - 1] &* Word(i) - } - return Int((h >> 32) ^ h) - } -} - -// MARK: - Bitwise operations - -extension BitSet { - public static var allZeros: BitSet { - return BitSet(size: 64) - } -} - -private func copyLargest(_ lhs: BitSet, _ rhs: BitSet) -> BitSet { - return (lhs.words.count > rhs.words.count) ? lhs : rhs -} - -/* - Note: In all of these bitwise operations, lhs and rhs are allowed to have a - different number of bits. The new BitSet always has the larger size. - The extra bits that get added to the smaller BitSet are considered to be 0. - That will strip off the higher bits from the larger BitSet when doing &. - */ - -public func & (lhs: BitSet, rhs: BitSet) -> BitSet { - let m = max(lhs.size, rhs.size) - var out = BitSet(size: m) - let n = min(lhs.words.count, rhs.words.count) - for i in 0.. BitSet { - var out = copyLargest(lhs, rhs) - let n = min(lhs.words.count, rhs.words.count) - for i in 0.. BitSet { - var out = copyLargest(lhs, rhs) - let n = min(lhs.words.count, rhs.words.count) - for i in 0.. BitSet { - var out = BitSet(size: rhs.size) - for i in 0.. String { - var s = "" - var n = self - for _ in 1...64 { - s += ((n & 1 == 1) ? "1" : "0") - n >>= 1 - } - return s - } -} - -extension BitSet: CustomStringConvertible { - public var description: String { - var s = "" - for x in words { - s += x.bitsToString() + " " - } - return s - } -} - diff --git a/Bit Set/README.markdown b/Bit Set/README.markdown index 6bb6e7c89..365950d06 100644 --- a/Bit Set/README.markdown +++ b/Bit Set/README.markdown @@ -329,6 +329,90 @@ This is the original value but with the lowest 1-bit removed. We keep repeating this process until the value consists of all zeros. The time complexity is **O(s)** where **s** is the number of 1-bits. +## Bit Shift Operations + +Bit shifts are a common and very useful mechanism when dealing with bitsets. Here is the right-shift function: + +``` +public func >> (lhs: BitSet, numBitsRight: Int) -> BitSet { + var out = lhs + let offset = numBitsRight / lhs.N + let shift = numBitsRight % lhs.N + for i in 0..> shift + } + + if (i + offset + 1 < lhs.words.count) { + out.words[i] |= lhs.words[i + offset + 1] << (lhs.N - shift) + } + } + + out.clearUnusedBits() + return out +} +``` + +Let's start with this line: + +```swift +for i in 0..> 10 + +I've grouped each part of the number by word to make it easier to see what happens. The for-loop goes from least significant word to most significant. So for index zero we're want to know what bits will make up our least significant word. Let's calculate our offset and shift values: + + offset = 10 / 8 = 1 (remember this is integer division) + shift = 10 % 8 = 2 + +So we consult the word at offset 1 to get some of our bits: + + 11000000 >> 2 = 00110000 + +And we get the rest of them from the word one further away: + + 01000010 << (8 - 2) = 10000000 + +And we bitwise OR these together to get our least significant term + + 00110000 + 10000000 + -------- OR + 10110000 + +We repeat this for the 2nd least significant term and obtain: + + 00010000 + +The last term can't get any bits because they are past the end of our number so those are all zeros. Our result is: + + 00000000 00010000 10110000 + ## See also [Bit Twiddling Hacks](http://graphics.stanford.edu/~seander/bithacks.html) 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/Closest Pair/ClosestPair.playground/Contents.swift b/Closest Pair/ClosestPair.playground/Contents.swift new file mode 100644 index 000000000..c11c1770b --- /dev/null +++ b/Closest Pair/ClosestPair.playground/Contents.swift @@ -0,0 +1,224 @@ +//Created by Ahmed Nader (github: AhmedNader42) on 4/4/18. + +func ClosestPairOf(points: [Point]) -> (minimum:Double, firstPoint:Point, secondPoint:Point) { + var innerPoints = mergeSort(points, sortAccording : true) + let result = ClosestPair(&innerPoints, innerPoints.count) + return (result.minValue, result.firstPoint, result.secondPoint) +} + +func ClosestPair(_ p : inout [Point],_ n : Int) -> (minValue: Double,firstPoint: Point,secondPoint: Point) +{ + // Brute force if only 3 points (To end recursion) + if n <= 3 + { + var i=0, j = i+1 + var minDist = Double.infinity + var newFirst:Point? = nil + var newSecond:Point? = nil + while i min { break } + if dist(strip[i], strip[x]) < temp + { + temp = dist(strip[i], strip[x]) + tempFirst = strip[i] + tempSecond = strip[x] + } + x+=1 + } + i+=1 + } + + if temp < min + { + min = temp; + first = tempFirst + second = tempSecond + } + return (min, first, second) +} + + + + +// MergeSort the array (Taken from Swift Algorithms Club with +// minor addition) +// sortAccodrding : true -> x, false -> y. +func mergeSort(_ array: [Point], sortAccording : Bool) -> [Point] { + guard array.count > 1 else { return array } + let middleIndex = array.count / 2 + let leftArray = mergeSort(Array(array[0.. [Point] { + + var compare : (Point, Point) -> Bool + + // Choose to compare with X or Y. + if sortAccording + { + compare = { p1,p2 in + return p1.x < p2.x + } + } + else + { + compare = { p1, p2 in + return p1.y < p2.y + } + } + + var leftIndex = 0 + var rightIndex = 0 + var orderedPile = [Point]() + if orderedPile.capacity < leftPile.count + rightPile.count { + orderedPile.reserveCapacity(leftPile.count + rightPile.count) + } + + while true { + guard leftIndex < leftPile.endIndex else { + orderedPile.append(contentsOf: rightPile[rightIndex.. Double +{ + let equation:Double = (((a.x-b.x)*(a.x-b.x))) + (((a.y-b.y)*(a.y-b.y))) + return equation.squareRoot() +} + + +var a = Point(0,2) +var b = Point(6,67) +var c = Point(43,71) +var d = Point(1000,1000) +var e = Point(39,107) +var f = Point(2000,2000) +var g = Point(3000,3000) +var h = Point(4000,4000) + + +var points = [a,b,c,d,e,f,g,h] +let endResult = ClosestPairOf(points: points) +print("Minimum Distance : \(endResult.minimum), The two points : (\(endResult.firstPoint.x ),\(endResult.firstPoint.y)), (\(endResult.secondPoint.x),\(endResult.secondPoint.y))") + diff --git a/Closest Pair/ClosestPair.playground/contents.xcplayground b/Closest Pair/ClosestPair.playground/contents.xcplayground new file mode 100644 index 000000000..a93d4844a --- /dev/null +++ b/Closest Pair/ClosestPair.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Closest Pair/ClosestPair.playground/playground.xcworkspace/contents.xcworkspacedata b/Closest Pair/ClosestPair.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Closest Pair/ClosestPair.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Closest Pair/ClosestPair.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Closest Pair/ClosestPair.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Closest Pair/ClosestPair.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Closest Pair/ClosestPair.playground/playground.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate b/Closest Pair/ClosestPair.playground/playground.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..82b130340 Binary files /dev/null and b/Closest Pair/ClosestPair.playground/playground.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Closest Pair/Images/1200px-Closest_pair_of_points.png b/Closest Pair/Images/1200px-Closest_pair_of_points.png new file mode 100644 index 000000000..cf61a3eff Binary files /dev/null and b/Closest Pair/Images/1200px-Closest_pair_of_points.png differ diff --git a/Closest Pair/Images/Case.png b/Closest Pair/Images/Case.png new file mode 100644 index 000000000..20b171a1a Binary files /dev/null and b/Closest Pair/Images/Case.png differ diff --git a/Closest Pair/Images/Strip.png b/Closest Pair/Images/Strip.png new file mode 100644 index 000000000..b0193a127 Binary files /dev/null and b/Closest Pair/Images/Strip.png differ diff --git a/Closest Pair/README.markdown b/Closest Pair/README.markdown new file mode 100644 index 000000000..a4de4a28b --- /dev/null +++ b/Closest Pair/README.markdown @@ -0,0 +1,88 @@ +# ClosestPair + +Closest Pair is an algorithm that finds the closest pair of a given array of points By utilizing the Divide and Conquer methodology of solving problems so that it reaches the correct solution with O(nlogn) complexity. + +![Given points and we're required to find the two red ones](Images/1200px-Closest_pair_of_points.png) + +As we see in the above image there are an array of points and we need to find the closest two, But how do we do that without having to compare each two points which results in a whopping O(n^2) complexity? + +Here is the main algorithm (Steps) we'll follow. + +- Sort the array according to their position on the X-axis so that they are sorted in the array as they are naturally in math. + +```swift +var innerPoints = mergeSort(points, sortAccording : true) +``` + +- Divide the points into two arrays Left, Right and keep dividing until you reach to only having 3 points in your array. + +- The base case is you have less than 3 points compare those against each other (Brute force) then return the minimum distance you found and the two points. + +- Now we get the first observation in the below image, There could be 2 points both very close to each other and indeed those two are the closest pair but since our algorithm so far divides from the middle + +```swift +let line:Double = (p[mid].x + p[mid+1].x)/2 +``` + +and just recursively calls itself until it reaches the base case we don't detect those points. + +![ Points lying near the division line](Images/Case.png) + +- To solve this we start by sorting the array on the Y-axis to get the points in their natural order and then we start getting the difference between the X position of the point and the line we drew to divide and if it is less than the min we got so far from the recursion we add it to the strip + +```swift +var strip = [Point]() +var i=0, j = 0 +while i min { break } + if dist(strip[i], strip[x]) < temp + { + temp = dist(strip[i], strip[x]) + tempFirst = strip[i] + tempSecond = strip[x] + } + x+=1 + } + i+=1 + } +``` + +- Of course not every time you end up with the same shape but this is the worst case and it's rare to happen so in reality you end up with far less points valid for comparison and this is why the algorithm gets performance in addition to the sorting tricks we did. + +- Compare the points in the strip and if you find a smaller distance replace the current one with it. + + +So this is the rundown of how the algorithm works and you could see the fun little math tricks used to optimize this and we end up with O(nlogn) complexity mainly because of the sorting. + + +## See also + +See the playground to play around with the implementation of the algorithm + +[Wikipedia](https://en.wikipedia.org/wiki/Closest_pair_of_points_problem) + +*Written for Swift Algorithm Club by [Ahmed Nader](https://github.com/ahmednader42)* diff --git a/Comb Sort/Tests/CombSortTests.swift b/Comb Sort/Tests/CombSortTests.swift index 783fbd150..1d83af002 100644 --- a/Comb Sort/Tests/CombSortTests.swift +++ b/Comb Sort/Tests/CombSortTests.swift @@ -9,26 +9,20 @@ import XCTest class CombSortTests: XCTestCase { - var sequence: [Int]! - let expectedSequence: [Int] = [-12, -10, -1, 2, 9, 32, 55, 67, 89, 101] - - func testSwift4(){ - #if swift(>=4.0) - print("Hello, Swift 4!") - #endif - } + var sequence: [Int]! + let expectedSequence: [Int] = [-12, -10, -1, 2, 9, 32, 55, 67, 89, 101] - override func setUp() { - super.setUp() - sequence = [2, 32, 9, -1, 89, 101, 55, -10, -12, 67] - } + override func setUp() { + super.setUp() + sequence = [2, 32, 9, -1, 89, 101, 55, -10, -12, 67] + } - override func tearDown() { - super.tearDown() - } + override func tearDown() { + super.tearDown() + } - func testCombSort() { - let sortedSequence = combSort(sequence) - XCTAssertEqual(sortedSequence, expectedSequence) - } + func testCombSort() { + let sortedSequence = combSort(sequence) + XCTAssertEqual(sortedSequence, expectedSequence) + } } diff --git a/Comb Sort/Tests/Tests.xcodeproj/project.pbxproj b/Comb Sort/Tests/Tests.xcodeproj/project.pbxproj index 14d059271..4582b3f2d 100644 --- a/Comb Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Comb Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -187,7 +187,7 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -237,7 +237,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Count Occurrences/CountOccurrences.playground/Contents.swift b/Count Occurrences/CountOccurrences.playground/Contents.swift index a34e3485f..6ab4c00f3 100644 --- a/Count Occurrences/CountOccurrences.playground/Contents.swift +++ b/Count Occurrences/CountOccurrences.playground/Contents.swift @@ -1,11 +1,11 @@ -func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { - func leftBoundary() -> Int { +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 { + if array[midIndex] < key { low = midIndex + 1 } else { high = midIndex @@ -14,12 +14,12 @@ func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { return low } - func rightBoundary() -> 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 { + if array[midIndex] > key { high = midIndex } else { low = midIndex + 1 @@ -28,13 +28,13 @@ func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { return low } - return rightBoundary() - leftBoundary() + return rightBoundary - leftBoundary } // Simple test let a = [ 0, 1, 1, 3, 3, 3, 3, 6, 8, 10, 11, 11 ] -countOccurrencesOfKey(3, inArray: a) +countOccurrences(of: 3, in: a) // Test with arrays of random size and contents (see debug output) @@ -59,6 +59,6 @@ for _ in 0..<10 { // Note: we also test -1 and 6 to check the edge cases. for k in -1...6 { - print("\t\(k): \(countOccurrencesOfKey(k, inArray: a))") + print("\t\(k): \(countOccurrences(of: k, in: a))") } } diff --git a/Count Occurrences/CountOccurrences.swift b/Count Occurrences/CountOccurrences.swift index 10dd12c4f..532e3810e 100644 --- a/Count Occurrences/CountOccurrences.swift +++ b/Count Occurrences/CountOccurrences.swift @@ -1,14 +1,15 @@ -/* - Counts the number of times a value appears in an array in O(lg n) time. - The array must be sorted from low to high. -*/ -func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { - func leftBoundary() -> Int { +/// Counts the number of times a value appears in an array in O(log n) time. The array must be sorted from low to high. +/// +/// - Parameter key: the key to be searched for in the array +/// - Parameter array: the array to search +/// - Returns: the count of occurences of the key in the given array +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 { + if array[midIndex] < key { low = midIndex + 1 } else { high = midIndex @@ -17,12 +18,12 @@ func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { return low } - func rightBoundary() -> 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 { + if array[midIndex] > key { high = midIndex } else { low = midIndex + 1 @@ -31,5 +32,5 @@ func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { return low } - return rightBoundary() - leftBoundary() + return rightBoundary - leftBoundary } diff --git a/Count Occurrences/README.markdown b/Count Occurrences/README.markdown index 85b77d2d0..8e935ea58 100644 --- a/Count Occurrences/README.markdown +++ b/Count Occurrences/README.markdown @@ -22,10 +22,10 @@ The trick is to use two binary searches, one to find where the `3`s start (the l In code this looks as follows: ```swift -func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { - func leftBoundary() -> Int { +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 { @@ -37,9 +37,9 @@ func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { return low } - func rightBoundary() -> 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 { @@ -51,18 +51,18 @@ func countOccurrencesOfKey(_ key: Int, inArray a: [Int]) -> Int { return low } - return rightBoundary() - leftBoundary() + return rightBoundary - leftBoundary } ``` -Notice that the helper functions `leftBoundary()` and `rightBoundary()` are very similar to the [binary search](../Binary%20Search/) algorithm. The big difference is that they don't stop when they find the search key, but keep going. +Notice that the variables `leftBoundary` and `rightBoundary` are very similar to the [binary search](../Binary%20Search/) algorithm. The big difference is that they don't stop when they find the search key, but keep going. Also, notice that we constrain the type `T` to be Comparable so that the algorithm can be applied to an array of Strings, Ints or other types that conform to the Swift Comparable protocol. 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! @@ -113,7 +113,7 @@ The right boundary is at index 7. The difference between the two boundaries is 7 Each binary search took 4 steps, so in total this algorithm took 8 steps. Not a big gain on an array of only 12 items, but the bigger the array, the more efficient this algorithm becomes. For a sorted array with 1,000,000 items, it only takes 2 x 20 = 40 steps to count the number of occurrences for any particular value. -By the way, if the value you're looking for is not in the array, then `rightBoundary()` and `leftBoundary()` return the same value and so the difference between them is 0. +By the way, if the value you're looking for is not in the array, then `rightBoundary` and `leftBoundary` return the same value and so the difference between them is 0. This is an example of how you can modify the basic binary search to solve other algorithmic problems as well. Of course, it does require that the array is sorted. 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/CounterClockWise/CounterClockWise.playground/contents.xcplayground b/CounterClockWise/CounterClockWise.playground/contents.xcplayground new file mode 100644 index 000000000..9f5f2f40c --- /dev/null +++ b/CounterClockWise/CounterClockWise.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/contents.xcworkspacedata b/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/CounterClockWise/CounterClockWise.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + 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/Genetic/README.markdown b/Genetic/README.markdown new file mode 100644 index 000000000..5dc4747f2 --- /dev/null +++ b/Genetic/README.markdown @@ -0,0 +1,312 @@ +# Genetic Algorthim + +## What is it? + +A genetic algorithm (GA) is process inspired by natural selection to find high quality solutions. Most commonly used for optimization. GAs rely on the bio-inspired processes of natural selection, more specifically the process of selection (fitness), crossover and mutation. To understand more, let's walk through these processes in terms of biology: + +### Selection +>**Selection**, in biology, the preferential survival and reproduction or preferential elimination of individuals with certain genotypes (genetic compositions), by means of natural or artificial controlling factors. + +In other words, survival of the fittest. Organisms that survive in their environment tend to reproduce more. With GAs we generate a fitness model that will rank individuals and give them a better chance for reproduction. + +### Crossover +>**Chromosomal crossover** (or crossing over) is the exchange of genetic material between homologous chromosomes that results in recombinant chromosomes during sexual reproduction [Wikipedia](https://en.wikipedia.org/wiki/Chromosomal_crossover) + +Simply reproduction. A generation will be a mixed representation of the previous generation, with offspring taking DNA from both parents. GAs do this by randomly, but weightily, mating offspring to create new generations. + +### Mutation +>**Mutation**, an alteration in the genetic material (the genome) of a cell of a living organism or of a virus that is more or less permanent and that can be transmitted to the cell’s or the virus’s descendants. [Britannica](https://www.britannica.com/science/mutation-genetics) + +The randomization that allows for organisms to change over time. In GAs we build a randomization process that will mutate offspring in a population in order to introduce fitness variance. + +### Resources: +* [Genetic Algorithms in Search Optimization, and Machine Learning](https://www.amazon.com/Genetic-Algorithms-Optimization-Machine-Learning/dp/0201157675/ref=sr_1_sc_1?ie=UTF8&qid=1520628364&sr=8-1-spell&keywords=Genetic+Algortithms+in+search) +* [Wikipedia](https://en.wikipedia.org/wiki/Genetic_algorithm) +* [My Original Gist](https://gist.github.com/blainerothrock/efda6e12fe10792c99c990f8ff3daeba) + +## The Code + +### Problem +For this quick and dirty example, we are going to produce an optimized string using a simple genetic algorithm. More specifically we are trying to take a randomly generated origin string of a fixed length and evolve it into the most optimized string of our choosing. + +We will be creating a bio-inspired world where the absolute existence is the string `Hello, World!`. Nothing in this universe is better and it's our goal to get as close to it as possible to ensure survival. + +### Define the Universe + +Before we dive into the core processes we need to set up our "universe". First let's define a lexicon, a set of everything that exists in our universe. + +```swift +let lex: [UInt8] = " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".asciiArray +``` + +To make things easier, we are actually going to work in [Unicode values](https://en.wikipedia.org/wiki/List_of_Unicode_characters), so let's define a String extension to help with that. + +```swift +extension String { + var unicodeArray: [UInt8] { + return [UInt8](self.utf8) + } +} +``` + + Now, let's define a few global variables for the universe: + * `OPTIMAL`: This is the end goal and what we will be using to rate fitness. In the real world this will not exist + * `DNA_SIZE`: The length of the string in our population. Organisms need to be similar + * `POP_SIZE`: Size of each generation + * `MAX_GENERATIONS`: Max number of generations, script will stop when it reach 5000 if the optimal value is not found + * `MUTATION_CHANCE`: The chance in which a random nucleotide can mutate (`1/MUTATION_CHANCE`) + + ```swift +let OPTIMAL:[UInt8] = "Hello, World".unicodeArray +let DNA_SIZE = OPTIMAL.count +let POP_SIZE = 50 +let GENERATIONS = 5000 +let MUTATION_CHANCE = 100 + ``` + + ### Population Zero + +Before selecting, crossover and mutation, we need a population to start with. Now that we have the universe defined we can write that function: + + ```swift + func randomPopulation(from lexicon: [UInt8], populationSize: Int, dnaSize: Int) -> [[UInt8]] { + guard lexicon.count > 1 else { return [] } + var pop = [[UInt8]]() + + (0.. Int { + guard dna.count == optimal.count else { return -1 } + var fitness = 0 + for index in dna.indices { + fitness += abs(Int(dna[index]) - Int(optimal[index])) + } + return fitness +} +``` + +The above will produce a fitness value to an individual. The perfect solution, "Hello, World" will have a fitness of 0. "Gello, World" will have a fitness of 1 since it is one unicode value off from the optimal (`H->G`). + +This example is very simple, but it'll work for our example. In a real world problem, the optimal solution is unknown or impossible. [Here](https://iccl.inf.tu-dresden.de/w/images/b/b7/GA_for_TSP.pdf) is a paper about optimizing a solution for the famous [traveling salesman problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) using a GA. In this example the problem is unsolvable by modern computers, but you can rate a individual solution by distance traveled. The optimal fitness here is an impossible 0. The closer the solution is to 0, the better chance for survival. In our example we will reach our goal, a fitness of 0. + +The second part to selection is weighted choice, also called roulette wheel selection. This defines how individuals are selected for the reproduction process out of the current population. Just because you are the best choice for natural selection doesn't mean the environment will select you. The individual could fall off a cliff, get dysentery or be unable to reproduce. + +Let's take a second and ask why on this one. Why would you not always want to select the most fit from a population? It's hard to see from this simple example, but let's think about dog breeding, because breeders remove this process and hand select dogs for the next generation. As a result you get improved desired characteristics, but the individuals will also continue to carry genetic disorders that come along with those traits. A certain "branch" of evolution may beat out the current fittest solution at a later time. This may be ok depending on the problem, but to keep this educational we will go with the bio-inspired way. + +With all that, here is our weight choice function: + +func weightedChoice(items: [(dna: [UInt8], weight: Double)]) -> (dna: [UInt8], weight: Double) { + + let total = items.reduce(0) { $0 + $1.weight } + var n = Double.random(in: 0..<(total * 1000000)) / 1000000.0 + + for item in items { + if n < item.weight { + return item + } + n = n - item.weight + } + return items[1] +} + + +The above function takes a list of individuals with their calculated fitness. Then selects one at random offset by their fitness value. The horrible 1,000,000 multiplication and division is to insure precision by calculating decimals. `Double.random` only uses integers so this is required to convert to a precise Double, it's not perfect, but enough for our example. + +## Mutation + +The all powerful mutation, the thing that introduces otherwise non existent fitness variance. It can either hurt of improve a individuals fitness but over time it will cause evolution towards more fit populations. Imagine if our initial random population was missing the charachter `H`, in that case we need to rely on mutation to introduce that character into the population in order to achieve the optimal solution. + +```swift +func mutate(lexicon: [UInt8], dna: [UInt8], mutationChance: Int) -> [UInt8] { + var outputDna = dna + (0.. [UInt8] { + let pos = Int.random(in: 0..?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".unicodeArray + +// This is the end goal and what we will be using to rate fitness. In the real world this will not exist +let OPTIMAL:[UInt8] = "Hello, World".unicodeArray + +// The length of the string in our population. Organisms need to be similar +let DNA_SIZE = OPTIMAL.count + +// Size of each generation +let POP_SIZE = 50 + +// Max number of generations, script will stop when it reaches 5000 if the optimal value is not found +let GENERATIONS = 5000 + +// The chance in which a random nucleotide can mutate (1/n) +let MUTATION_CHANCE = 100 + +func randomPopulation(from lexicon: [UInt8], populationSize: Int, dnaSize: Int) -> [[UInt8]] { + var pop = [[UInt8]]() + + (0.. Int { + var fitness = 0 + for index in dna.indices { + fitness += abs(Int(dna[index]) - Int(optimal[index])) + } + return fitness +} + +func weightedChoice(items: [(dna: [UInt8], weight: Double)]) -> (dna: [UInt8], weight: Double) { + + let total = items.reduce(0) { $0 + $1.weight } + var n = Double.random(in: 0..<(total * 1000000)) / 1000000.0 + + for item in items { + if n < item.weight { + return item + } + n = n - item.weight + } + return items[1] +} + +func mutate(lexicon: [UInt8], dna: [UInt8], mutationChance: Int) -> [UInt8] { + var outputDna = dna + (0.. [UInt8] { + let pos = Int.random(in: 0.. + + + + diff --git a/Genetic/gen.swift b/Genetic/gen.swift new file mode 100644 index 000000000..a61e61785 --- /dev/null +++ b/Genetic/gen.swift @@ -0,0 +1,148 @@ +//: Playground - noun: a place where people can play + +import Foundation + +extension String { + var unicodeArray: [UInt8] { + return [UInt8](self.utf8) + } +} + + +let lex: [UInt8] = " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".unicodeArray + +// This is the end goal and what we will be using to rate fitness. In the real world this will not exist +let OPTIMAL:[UInt8] = "Hello, World".unicodeArray + +// The length of the string in our population. Organisms need to be similar +let DNA_SIZE = OPTIMAL.count + +// size of each generation +let POP_SIZE = 50 + +// max number of generations, script will stop when it reach 5000 if the optimal value is not found +let MAX_GENERATIONS = 5000 + +// The chance in which a random nucleotide can mutate (1/n) +let MUTATION_CHANCE = 100 + +func randomChar(from lexicon: [UInt8]) -> UInt8 { + let len = UInt32(lexicon.count-1) + let rand = Int(arc4random_uniform(len)) + return lexicon[rand] +} + +func randomPopulation(from lexicon: [UInt8], populationSize: Int, dnaSize: Int) -> [[UInt8]] { + + var pop = [[UInt8]]() + + (0.. Int { + var fitness = 0 + (0...dna.count-1).forEach { c in + fitness += abs(Int(dna[c]) - Int(optimal[c])) + } + return fitness +} + +func weightedChoice(items:[(dna:[UInt8], weight:Double)]) -> (dna:[UInt8], weight:Double) { + + let total = items.reduce(0.0) { return $0 + $1.weight} + + var n = Double(arc4random_uniform(UInt32(total * 1000000.0))) / 1000000.0 + + for item in items { + if n < item.weight { + return item + } + n = n - item.weight + } + return items[1] +} + +func mutate(lexicon: [UInt8], dna:[UInt8], mutationChance:Int) -> [UInt8] { + var outputDna = dna + + (0.. [UInt8] { + let pos = Int(arc4random_uniform(UInt32(dnaSize-1))) + + let dna1Index1 = dna1.index(dna1.startIndex, offsetBy: pos) + let dna2Index1 = dna2.index(dna2.startIndex, offsetBy: pos) + + return [UInt8](dna1.prefix(upTo: dna1Index1) + dna2.suffix(from: dna2Index1)) +} + +func main() { + + // generate the starting random population + var population:[[UInt8]] = randomPopulation(from: lex, populationSize: POP_SIZE, dnaSize: DNA_SIZE) + // print("population: \(population), dnaSize: \(DNA_SIZE) ") + var fittest = [UInt8]() + + for generation in 0...MAX_GENERATIONS { + + var weightedPopulation = [(dna:[UInt8], weight:Double)]() + + // calulcated the fitness of each individual in the population + // and add it to the weight population (weighted = 1.0/fitness) + for individual in population { + let fitnessValue = calculateFitness(dna: individual, optimal: OPTIMAL) + + let pair = ( individual, fitnessValue == 0 ? 1.0 : Double(100/POP_SIZE)/Double( fitnessValue ) ) + + weightedPopulation.append(pair) + } + + population = [] + + // create a new generation using the individuals in the origional population + (0...POP_SIZE).forEach { _ in + let ind1 = weightedChoice(items: weightedPopulation) + let ind2 = weightedChoice(items: weightedPopulation) + + let offspring = crossover(dna1: ind1.dna, dna2: ind2.dna, dnaSize: DNA_SIZE) + + // append to the population and mutate + population.append(mutate(lexicon: lex, dna: offspring, mutationChance: MUTATION_CHANCE)) + } + + fittest = population[0] + var minFitness = calculateFitness(dna: fittest, optimal: OPTIMAL) + + // parse the population for the fittest string + population.forEach { indv in + let indvFitness = calculateFitness(dna: indv, optimal: OPTIMAL) + if indvFitness < minFitness { + fittest = indv + minFitness = indvFitness + } + } + if minFitness == 0 { break; } + print("\(generation): \(String(bytes: fittest, encoding: .utf8)!)") + + } + print("fittest string: \(String(bytes: fittest, encoding: .utf8)!)") +} + +main() 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.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme b/Graph/Graph.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme index 73524effc..84c688e66 100644 --- a/Graph/Graph.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme +++ b/Graph/Graph.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme @@ -1,6 +1,6 @@ + + + + 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/Hash Table/HashTable.playground/Sources/HashTable.swift b/Hash Table/HashTable.playground/Sources/HashTable.swift index 476a51174..f2a1be040 100644 --- a/Hash Table/HashTable.playground/Sources/HashTable.swift +++ b/Hash Table/HashTable.playground/Sources/HashTable.swift @@ -131,7 +131,7 @@ public struct HashTable { Returns the given key's array index. */ private func index(forKey key: Key) -> Int { - return abs(key.hashValue) % buckets.count + return abs(key.hashValue % buckets.count) } } diff --git a/Hash Table/README.markdown b/Hash Table/README.markdown index 9c4065a51..fb0bda788 100644 --- a/Hash Table/README.markdown +++ b/Hash Table/README.markdown @@ -141,7 +141,7 @@ The hash table does not do anything yet, so let's add the remaining functionalit ```swift private func index(forKey key: Key) -> Int { - return abs(key.hashValue) % buckets.count + return abs(key.hashValue % buckets.count) } ``` 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 76d2bee39..fee881129 100755 --- a/Heap/README.markdown +++ b/Heap/README.markdown @@ -29,7 +29,7 @@ As a result of this heap property, a max-heap always stores its largest item at ## How does a heap compare to regular trees? -A heap is not a replacement for a binary search tree, and there are similarities and differnces between them. Here are some main differences: +A heap is not a replacement for a binary search tree, and there are similarities and differences between them. Here are some main differences: **Order of the nodes.** In a [binary search tree (BST)](../Binary%20Search%20Tree/), the left child must be smaller than its parent, and the right child must be greater. This is not true for a heap. In a max-heap both children must be smaller than the parent, while in a min-heap they both must be greater. @@ -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 9faf9dc42..5f0b6c2b4 100644 --- a/Insertion Sort/InsertionSort.swift +++ b/Insertion Sort/InsertionSort.swift @@ -5,37 +5,36 @@ /// - 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 /// -/// - Parameter array: the array to be sorted, conatining elements that conform to the Comparable protocol +/// - Parameter array: the array to be sorted, containing elements that conform to the Comparable protocol /// - Returns: a sorted array containing the same elements 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/Karatsuba Multiplication/README.markdown b/Karatsuba Multiplication/README.markdown index aef0d744a..02e54a1bf 100644 --- a/Karatsuba Multiplication/README.markdown +++ b/Karatsuba Multiplication/README.markdown @@ -115,10 +115,10 @@ What about the running time of this algorithm? Is all this extra work worth it? ## Resources -[Wikipedia] (https://en.wikipedia.org/wiki/Karatsuba_algorithm) +[Wikipedia](https://en.wikipedia.org/wiki/Karatsuba_algorithm) -[WolframMathWorld] (http://mathworld.wolfram.com/KaratsubaMultiplication.html) +[WolframMathWorld](http://mathworld.wolfram.com/KaratsubaMultiplication.html) -[Master Theorem] (https://en.wikipedia.org/wiki/Master_theorem) +[Master Theorem](https://en.wikipedia.org/wiki/Master_theorem) *Written for Swift Algorithm Club by Richard Ash* 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/Knuth-Morris-Pratt/README.markdown b/Knuth-Morris-Pratt/README.markdown index 95fdea9e9..2e4d5360b 100644 --- a/Knuth-Morris-Pratt/README.markdown +++ b/Knuth-Morris-Pratt/README.markdown @@ -14,7 +14,7 @@ let concert = "🎼🎹🎹🎸🎸🎻🎻🎷🎺🎤👏👏👏" concert.indexesOf(ptnr: "🎻🎷") // Output: [6] ``` -The [Knuth-Morris-Pratt algorithm](https://en.wikipedia.org/wiki/Knuth–Morris–Pratt_algorithm) is considered one of the best algorithms for solving the pattern matching problem. Although in practice [Boyer-Moore](../Boyer-Moore/) is usually preferred, the algorithm that we will introduce is simpler, and has the same (linear) running time. +The [Knuth-Morris-Pratt algorithm](https://en.wikipedia.org/wiki/Knuth–Morris–Pratt_algorithm) is considered one of the best algorithms for solving the pattern matching problem. Although in practice [Boyer-Moore](../Boyer-Moore-Horspool/) is usually preferred, the algorithm that we will introduce is simpler, and has the same (linear) running time. The idea behind the algorithm is not too different from the [naive string search](../Brute-Force%20String%20Search/) procedure. As it, Knuth-Morris-Pratt aligns the text with the pattern and goes with character comparisons from left to right. But, instead of making a shift of one character when a mismatch occurs, it uses a more intelligent way to move the pattern along the text. In fact, the algorithm features a pattern pre-processing stage where it acquires all the informations that will make the algorithm skip redundant comparisons, resulting in larger shifts. diff --git a/Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift b/Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift index 397c43a38..aad15eac6 100644 --- a/Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift +++ b/Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift @@ -16,12 +16,6 @@ public func kthLargest(_ a: [Int], _ k: Int) -> Int? { // MARK: - Randomized selection -/* Returns a random integer in the range min...max, inclusive. */ -public func random(min: Int, max: Int) -> Int { - assert(min < max) - return min + Int(arc4random_uniform(UInt32(max - min + 1))) -} - /* Returns the i-th smallest element from the array. @@ -39,7 +33,7 @@ public func randomizedSelect(_ array: [T], order k: Int) -> T { var a = array func randomPivot(_ a: inout [T], _ low: Int, _ high: Int) -> T { - let pivotIndex = random(min: low, max: high) + let pivotIndex = Int.random(in: low...high) a.swapAt(pivotIndex, high) return a[high] } diff --git a/Linear Regression/README.markdown b/Linear Regression/README.markdown index 4f9d030db..d07bf1497 100644 --- a/Linear Regression/README.markdown +++ b/Linear Regression/README.markdown @@ -21,7 +21,7 @@ Let's start by looking at the data plotted out: We could imagine a straight line drawn through the points on this graph. It's not (in this case) going to go exactly through every point, but we could place the line so that it goes as close to all the points as possible. -To say this in another way, we want to make the distance from the line to each point as small as possible. This is most often done by minimising the square of the distance from the line to each point. +To say this in another way, we want to make the distance from the line to each point as small as possible. This is most often done by minimizing the square of the distance from the line to each point. We can describe the straight line in terms of two variables: @@ -75,7 +75,7 @@ for _ in 1...numberOfIterations { The program loops through each data point (each car age and car price). For each data point it adjusts the intercept and the slope to bring them closer to the correct values. The equations used in the code to adjust the intercept and the slope are based on moving in the direction of the maximal reduction of these variables. This is a *gradient descent*. -We want to minimise the square of the distance between the line and the points. We define a function `J` which represents this distance - for simplicity we consider only one point here. This function `J` is proportional to `((slope.carAge + intercept) - carPrice)) ^ 2`. +We want to minimize the square of the distance between the line and the points. We define a function `J` which represents this distance - for simplicity we consider only one point here. This function `J` is proportional to `((slope * carAge + intercept) - carPrice)) ^ 2`. In order to move in the direction of maximal reduction, we take the partial derivative of this function with respect to the slope, and similarly for the intercept. We multiply these derivatives by our factor alpha and then use them to adjust the values of slope and intercept on each iteration. diff --git a/Linked List/LinkedList.swift b/Linked List/LinkedList.swift index 7c2cbb3b1..de8a42c93 100755 --- a/Linked List/LinkedList.swift +++ b/Linked List/LinkedList.swift @@ -67,7 +67,7 @@ public final class LinkedList { /// - Returns: LinkedListNode public func node(at index: Int) -> Node { assert(head != nil, "List is empty") - assert(index >= 0, "index must be greater than 0") + assert(index >= 0, "index must be greater or equal to 0") if index == 0 { return head! 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/Merge Sort/README.markdown b/Merge Sort/README.markdown index 7ad6fd95e..d86f062f4 100644 --- a/Merge Sort/README.markdown +++ b/Merge Sort/README.markdown @@ -79,6 +79,7 @@ func merge(leftPile: [Int], rightPile: [Int]) -> [Int] { // 2 var orderedPile = [Int]() + orderedPile.reserveCapacity(leftPile.count + rightPile.count) // 3 while leftIndex < leftPile.count && rightIndex < rightPile.count { @@ -115,7 +116,7 @@ This method may look scary, but it is quite straightforward: 1. You need two indexes to keep track of your progress for the two arrays while merging. -2. This is the merged array. It is empty right now, but you will build it up in subsequent steps by appending elements from the other arrays. +2. This is the merged array. It is empty right now, but you will build it up in subsequent steps by appending elements from the other arrays. Since you already know number of elements that will end up in this array, you reserve capacity to avoid reallocation overhead later. 3. This while-loop will compare the elements from the left and right sides and append them into the `orderedPile` while making sure that the result stays in order. diff --git a/Myers Difference Algorithm/Images/EditGraph.png b/Myers Difference Algorithm/Images/EditGraph.png new file mode 100644 index 000000000..f31bb50a3 Binary files /dev/null and b/Myers Difference Algorithm/Images/EditGraph.png differ diff --git a/Myers Difference Algorithm/Images/EditGraph_k_move.png b/Myers Difference Algorithm/Images/EditGraph_k_move.png new file mode 100644 index 000000000..a8ea45c29 Binary files /dev/null and b/Myers Difference Algorithm/Images/EditGraph_k_move.png differ diff --git a/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/Contents.swift b/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/Contents.swift new file mode 100644 index 000000000..113487acc --- /dev/null +++ b/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/Contents.swift @@ -0,0 +1,22 @@ +//: Playground - noun: a place where people can play +import Foundation + +let shortestEditDistance: ([String], [String]) -> Int = MyersDifferenceAlgorithm.calculateShortestEditDistance(from:to:) + +/*** + All elements are same, so any scripts do not need. + So, the edit distance is 0 + ***/ +shortestEditDistance(["1", "2", "3"], ["1", "2", "3"]) + +/*** + Last element "3" should be inserted. + So, the edit distance is 1 + ***/ +shortestEditDistance(["1", "2"], ["1", "2", "3"]) + +/*** + First, remove "1", then insert "1" after "2". + So, the edit distance is 2 +***/ +shortestEditDistance(["1", "2", "3"], ["2", "1", "3"]) diff --git a/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/Sources/MyersDifferenceAlgorithm.swift b/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/Sources/MyersDifferenceAlgorithm.swift new file mode 100644 index 000000000..aedbf3e71 --- /dev/null +++ b/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/Sources/MyersDifferenceAlgorithm.swift @@ -0,0 +1,67 @@ +// +// MyersDifferenceAlgorithm.swift +// +// Created by Yuya Horita on 2018/02/27. +// Copyright © 2018年 hy. All rights reserved. +// + +import Foundation + +public struct MyersDifferenceAlgorithm { + public static func calculateShortestEditDistance(from fromArray: Array, to toArray: Array) -> Int { + let fromCount = fromArray.count + let toCount = toArray.count + let totalCount = toCount + fromCount + var furthestReaching = Array(repeating: 0, count: 2 * totalCount + 1) + + let isReachedAtSink: (Int, Int) -> Bool = { x, y in + return x == fromCount && y == toCount + } + + let snake: (Int, Int, Int) -> Int = { x, D, k in + var _x = x + while _x < fromCount && _x - k < toCount && fromArray[_x] == toArray[_x - k] { + _x += 1 + } + return _x + } + + for D in 0...totalCount { + for k in stride(from: -D, through: D, by: 2) { + let index = k + totalCount + + // (x, D, k) => the x position on the k_line where the number of scripts is D + // scripts means insertion or deletion + var x = 0 + if D == 0 { } + // k == -D, D will be the boundary k_line + // when k == -D, moving right on the Edit Graph(is delete script) from k - 1_line where D - 1 is unavailable. + // when k == D, moving bottom on the Edit Graph(is insert script) from k + 1_line where D - 1 is unavailable. + // furthestReaching x position has higher calculating priority. (x, D - 1, k - 1), (x, D - 1, k + 1) + else if k == -D || k != D && furthestReaching[index - 1] < furthestReaching[index + 1] { + // Getting initial x position + // ,using the furthestReaching X position on the k + 1_line where D - 1 + // ,meaning get (x, D, k) by (x, D - 1, k + 1) + moving bottom + snake + // this moving bottom on the edit graph is compatible with insert script + x = furthestReaching[index + 1] + } else { + // Getting initial x position + // ,using the futrhest X position on the k - 1_line where D - 1 + // ,meaning get (x, D, k) by (x, D - 1, k - 1) + moving right + snake + // this moving right on the edit graph is compatible with delete script + x = furthestReaching[index - 1] + 1 + } + + // snake + // diagonal moving can be performed with 0 cost. + // `same` script is needed ? + let _x = snake(x, D, k) + + if isReachedAtSink(_x, _x - k) { return D } + furthestReaching[index] = _x + } + } + + fatalError("Never comes here") + } +} diff --git a/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/contents.xcplayground b/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Myers Difference Algorithm/MyersDifferenceAlgorithm.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Myers Difference Algorithm/MyersDifferenceAlgorithm.swift b/Myers Difference Algorithm/MyersDifferenceAlgorithm.swift new file mode 100644 index 000000000..aedbf3e71 --- /dev/null +++ b/Myers Difference Algorithm/MyersDifferenceAlgorithm.swift @@ -0,0 +1,67 @@ +// +// MyersDifferenceAlgorithm.swift +// +// Created by Yuya Horita on 2018/02/27. +// Copyright © 2018年 hy. All rights reserved. +// + +import Foundation + +public struct MyersDifferenceAlgorithm { + public static func calculateShortestEditDistance(from fromArray: Array, to toArray: Array) -> Int { + let fromCount = fromArray.count + let toCount = toArray.count + let totalCount = toCount + fromCount + var furthestReaching = Array(repeating: 0, count: 2 * totalCount + 1) + + let isReachedAtSink: (Int, Int) -> Bool = { x, y in + return x == fromCount && y == toCount + } + + let snake: (Int, Int, Int) -> Int = { x, D, k in + var _x = x + while _x < fromCount && _x - k < toCount && fromArray[_x] == toArray[_x - k] { + _x += 1 + } + return _x + } + + for D in 0...totalCount { + for k in stride(from: -D, through: D, by: 2) { + let index = k + totalCount + + // (x, D, k) => the x position on the k_line where the number of scripts is D + // scripts means insertion or deletion + var x = 0 + if D == 0 { } + // k == -D, D will be the boundary k_line + // when k == -D, moving right on the Edit Graph(is delete script) from k - 1_line where D - 1 is unavailable. + // when k == D, moving bottom on the Edit Graph(is insert script) from k + 1_line where D - 1 is unavailable. + // furthestReaching x position has higher calculating priority. (x, D - 1, k - 1), (x, D - 1, k + 1) + else if k == -D || k != D && furthestReaching[index - 1] < furthestReaching[index + 1] { + // Getting initial x position + // ,using the furthestReaching X position on the k + 1_line where D - 1 + // ,meaning get (x, D, k) by (x, D - 1, k + 1) + moving bottom + snake + // this moving bottom on the edit graph is compatible with insert script + x = furthestReaching[index + 1] + } else { + // Getting initial x position + // ,using the futrhest X position on the k - 1_line where D - 1 + // ,meaning get (x, D, k) by (x, D - 1, k - 1) + moving right + snake + // this moving right on the edit graph is compatible with delete script + x = furthestReaching[index - 1] + 1 + } + + // snake + // diagonal moving can be performed with 0 cost. + // `same` script is needed ? + let _x = snake(x, D, k) + + if isReachedAtSink(_x, _x - k) { return D } + furthestReaching[index] = _x + } + } + + fatalError("Never comes here") + } +} diff --git a/Myers Difference Algorithm/README.md b/Myers Difference Algorithm/README.md new file mode 100644 index 000000000..6be30ecda --- /dev/null +++ b/Myers Difference Algorithm/README.md @@ -0,0 +1,162 @@ +# Myers Difference Algorithm + +Myers Difference Algorithm(MDA) is an algorithm that finds a longest common subsequence(LCS) or shortest edit scripts(SES) of two sequences. The common subsequence of two sequences is the sequence of elements that appear in the same order in both sequences. For example, let's assume you have two arrays: + +``` +let firstArray = [1, 2, 3] +let secondArray = [2, 3, 4] +``` + +The common subsequences of these two arrays are `[2]`, and `[2, 3]`. The longest common sequence in this case is `[2, 3]`. MDA can accomplish this in O(ND) time, where N is the sum of the lengths of the two sequences. + +## Finding the length of the Longest Common Subsequence with Myers Algorithm on Edit Graph + +### Edit Graph + +MDA uses an **Edit Graph** to solve the LCS/SES problem. Below is a illustration depicting an edit graph: + + + +The x-axis at the top of the graph represents one of the sequences, `X`. The y-axis at the left side of the graph represents the other sequence, `Y`. Hence, the two sequences in question is the following: + +``` +X = [A, B, C, A, B, B, A] +Y = [C, B, A, B, A, C] +``` + +MDA generates the edit graph through the following steps: + +1. Line the element of sequence `X` on the x axis. And do for `Y` on the y axis. +2. Make grid and vertex at each point in the grid (x, y), `x in [0, N] and y in [0, M]`. `N` is the length of sequence `X`, `M` is of `Y` +3. Line for `x - y = k`, this line called k-line. Black dot line is this and pink number is the value of k. +3. Check the points `(i, j)`, where `X[i] = Y[j]`, called match point, light green one. +4. Connect vertex `(i - 1, j - 1)` and vertex `(i, j)`, where `(i, j)` is match point, then diagonal edge appears. + +Each elements on the figure shows that, +- `Red number and dotted lines`: The red number is the value of k and dotted lines are k-line. +- `Green dots: The match points`, which is the point `(i, j)` where `X[i] == Y[j]` +- `Blue line`: The shortest path from source to sink, which is the path we are going to find finally. + +> **Note:** Here, the sequences' start index is 1 not 0, so `X[1] = A`, `Y[1] = C` + +We discuss about which path is the shortest from `source` to `sink`. Can move on the edges on the graph. I mean we can move on the grid, horizontal and vertical edges, and the diagonal edges. + +The movements are compatible with the `Edit Scripts`, insert or delete. The word `Edit Scripts` appeared here, as referred at Introduction, SES is Shortest Edit Scripts. + +Let's get back on track. On this edit graph, the horizontal movement to vertex `(i, j)` is compatible with the script `delete at index i from X`, the vertical movement to vertex `(i, j)` is compatible with the script `insert the element of Y at index j to immediately after the element of X at index i`. How about for the diagonal movement?. This movement to vertex `(i, j)` means `X[i] = Y[j]`, so no script needs. + +- horizontal movement -> delete +- vertical movement -> insert +- diagonal movement -> no script because both are same. + +Next, add cost 1 for non-diagonal movement, because they can be compatible with script. And 0 for diagonal movement, same means no script. + +The total cost for the minimum path, exploring from `source` to `sink`, is the same as the length of the Longest Common Subsequence or Shortest Edit Script. + +So, LCS/SES problem can be solved by finding the shortest path from `source` to `sink`. + +### Myers Algorithm + +As mentioned above, the problem of finding a shortest edit script can be reduced to finding a path from `source (0, 0)` to `sink (N, M)` with the fewest number of horizontal and vertical edges. Let `D-path` be a path starting at `source` that has exactly `D` non-diagonal edges, or must move non-diagonally D-times. + +For example, A 0-path consists solely of diagonal edges. This means both sequences are completely same. + +By a simple induction, D-path must consist of a (D-1)-path followed by a non-diagonal edge and then diagonal edges, which called `snake`. The minimum value of D is 0, both sequences being same. To the contrary, the maximum value of D is N + M because delete all elements from X and insert all elements from Y to X is the worst case edit scripts. For getting D, or the length of SES, running loop from 0 to N + M is enough. + +```swift +for D in 0...N + M +``` + +Next, thinking about, where is the furthest reaching point for D-path on k-line. Like below, moving horizontally from k-line reaches (k+1)-line, moving vertically from k-line reaches (k-1)-line. Red chalky line shows that. + + + +So, threre are several end points of D-path, or D-path can end on several k-line. We need the information to get the next path ((D+1)-path) as mentioned above. In fact, D-path must end on +k-line, where k in { -D, -D + 2, ....., D - 2, D }. This is so simple, starting point, `source` is `(0, 0)` on (k=0)-line. D is the number of non-diagonal edges and non-diagonal movement changes current k-line to (kpm1)-line. Because 0 is even number, if D is even number D-path will end on (even_k)-line, if D is odd number D-path will end on (odd_k)-line. + +Searching loop outline will be below. + +```swift +for D in 0...N + M { + for k in stride(from: -D, through: D, by: 2) { + //Find the end point of the furthest reaching D-path in k-line. + if furthestReachingX == N && furthestReachingY == M { + // The D-path is the shortest path + // D is the length of Shortest Edit Script + return + } + } +} +``` + +The D-path on k-line can be decomposed into +- a furthest reaching (D-1)-path on (k-1)-line, followed by a horizontal edge, followed by `snake`. +- a furthest reaching (D-1)-path on (k+1)-line, followed by a vertical edge, followed by `snake`. +as discussed above. + +The Myers Algorithm key point are these. +- D-path must end on k-line, where k in { -D, -D + 2, ....., D - 2, D } +- The D-path on k-line can be decomposed into two patterns + +thanks for these, the number of calculation become less. + +```swift +public struct MyersDifferenceAlgorithm { + public static func calculateShortestEditDistance(from fromArray: Array, to toArray: Array) -> Int { + let fromCount = fromArray.count + let toCount = toArray.count + let totalCount = toCount + fromCount + var furthestReaching = Array(repeating: 0, count: 2 * totalCount + 1) + + let isReachedAtSink: (Int, Int) -> Bool = { x, y in + return x == fromCount && y == toCount + } + + let snake: (Int, Int, Int) -> Int = { x, D, k in + var _x = x + while _x < fromCount && _x - k < toCount && fromArray[_x] == toArray[_x - k] { + _x += 1 + } + return _x + } + + for D in 0...totalCount { + for k in stride(from: -D, through: D, by: 2) { + let index = k + totalCount + + // (x, D, k) => the x position on the k_line where the number of scripts is D + // scripts means insertion or deletion + var x = 0 + if D == 0 { } + // k == -D, D will be the boundary k_line + // when k == -D, moving right on the Edit Graph(is delete script) from k - 1_line where D - 1 is unavailable. + // when k == D, moving bottom on the Edit Graph(is insert script) from k + 1_line where D - 1 is unavailable. + // furthestReaching x position has higher calculating priority. (x, D - 1, k - 1), (x, D - 1, k + 1) + else if k == -D || k != D && furthestReaching[index - 1] < furthestReaching[index + 1] { + // Getting initial x position + // ,using the furthestReaching X position on the k + 1_line where D - 1 + // ,meaning get (x, D, k) by (x, D - 1, k + 1) + moving bottom + snake + // this moving bottom on the edit graph is compatible with insert script + x = furthestReaching[index + 1] + } else { + // Getting initial x position + // ,using the futrhest X position on the k - 1_line where D - 1 + // ,meaning get (x, D, k) by (x, D - 1, k - 1) + moving right + snake + // this moving right on the edit graph is compatible with delete script + x = furthestReaching[index - 1] + 1 + } + + // snake + // diagonal moving can be performed with 0 cost. + // `same` script is needed ? + let _x = snake(x, D, k) + + if isReachedAtSink(_x, _x - k) { return D } + furthestReaching[index] = _x + } + } + + fatalError("Never comes here") + } +} +``` diff --git a/Points Lines Planes/Points Lines Planes.xcodeproj/project.pbxproj b/Points Lines Planes/Points Lines Planes.xcodeproj/project.pbxproj new file mode 100644 index 000000000..aeba4777b --- /dev/null +++ b/Points Lines Planes/Points Lines Planes.xcodeproj/project.pbxproj @@ -0,0 +1,296 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 8EA8A3BB1F9F7B6300FD8BC0 /* Point2D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EA8A3BA1F9F7B6300FD8BC0 /* Point2D.swift */; }; + 8EA8A3BD1F9F7B7100FD8BC0 /* Line2D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EA8A3BC1F9F7B7100FD8BC0 /* Line2D.swift */; }; + 8EF612481F9E00E000267358 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EF612471F9E00E000267358 /* main.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8EF612421F9E00E000267358 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 8EA8A3BA1F9F7B6300FD8BC0 /* Point2D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Point2D.swift; sourceTree = ""; }; + 8EA8A3BC1F9F7B7100FD8BC0 /* Line2D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line2D.swift; sourceTree = ""; }; + 8EF612441F9E00E000267358 /* Points Lines Planes */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Points Lines Planes"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8EF612471F9E00E000267358 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8EF612411F9E00E000267358 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8EA8A3C21F9F7C9000FD8BC0 /* 2D */ = { + isa = PBXGroup; + children = ( + 8EA8A3BA1F9F7B6300FD8BC0 /* Point2D.swift */, + 8EA8A3BC1F9F7B7100FD8BC0 /* Line2D.swift */, + ); + path = 2D; + sourceTree = ""; + }; + 8EF6123B1F9E00DF00267358 = { + isa = PBXGroup; + children = ( + 8EF612461F9E00E000267358 /* Points Lines Planes */, + 8EF612451F9E00E000267358 /* Products */, + ); + sourceTree = ""; + }; + 8EF612451F9E00E000267358 /* Products */ = { + isa = PBXGroup; + children = ( + 8EF612441F9E00E000267358 /* Points Lines Planes */, + ); + name = Products; + sourceTree = ""; + }; + 8EF612461F9E00E000267358 /* Points Lines Planes */ = { + isa = PBXGroup; + children = ( + 8EF612471F9E00E000267358 /* main.swift */, + 8EA8A3C21F9F7C9000FD8BC0 /* 2D */, + ); + path = "Points Lines Planes"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8EF612431F9E00E000267358 /* Points Lines Planes */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8EF6124B1F9E00E000267358 /* Build configuration list for PBXNativeTarget "Points Lines Planes" */; + buildPhases = ( + 8EF612401F9E00E000267358 /* Sources */, + 8EF612411F9E00E000267358 /* Frameworks */, + 8EF612421F9E00E000267358 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Points Lines Planes"; + productName = "Points Lines Planes"; + productReference = 8EF612441F9E00E000267358 /* Points Lines Planes */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8EF6123C1F9E00DF00267358 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0900; + LastUpgradeCheck = 0900; + ORGANIZATIONNAME = Workmoose; + TargetAttributes = { + 8EF612431F9E00E000267358 = { + CreatedOnToolsVersion = 9.0.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 8EF6123F1F9E00DF00267358 /* Build configuration list for PBXProject "Points Lines Planes" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 8EF6123B1F9E00DF00267358; + productRefGroup = 8EF612451F9E00E000267358 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8EF612431F9E00E000267358 /* Points Lines Planes */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8EF612401F9E00E000267358 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8EA8A3BB1F9F7B6300FD8BC0 /* Point2D.swift in Sources */, + 8EF612481F9E00E000267358 /* main.swift in Sources */, + 8EA8A3BD1F9F7B7100FD8BC0 /* Line2D.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 8EF612491F9E00E000267358 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_DOCUMENTATION_COMMENTS = YES; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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.13; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 8EF6124A1F9E00E000267358 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_DOCUMENTATION_COMMENTS = YES; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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.13; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + 8EF6124C1F9E00E000267358 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 7C4LVS3ZVC; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 8EF6124D1F9E00E000267358 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 7C4LVS3ZVC; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8EF6123F1F9E00DF00267358 /* Build configuration list for PBXProject "Points Lines Planes" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8EF612491F9E00E000267358 /* Debug */, + 8EF6124A1F9E00E000267358 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8EF6124B1F9E00E000267358 /* Build configuration list for PBXNativeTarget "Points Lines Planes" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8EF6124C1F9E00E000267358 /* Debug */, + 8EF6124D1F9E00E000267358 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8EF6123C1F9E00DF00267358 /* Project object */; +} diff --git a/Points Lines Planes/Points Lines Planes.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Points Lines Planes/Points Lines Planes.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..82bfe2b7d --- /dev/null +++ b/Points Lines Planes/Points Lines Planes.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Points Lines Planes/Points Lines Planes/2D/Line2D.swift b/Points Lines Planes/Points Lines Planes/2D/Line2D.swift new file mode 100644 index 000000000..c23040b14 --- /dev/null +++ b/Points Lines Planes/Points Lines Planes/2D/Line2D.swift @@ -0,0 +1,141 @@ +// +// Line2D.swift +// Points Lines Planes +// +// Created by Jaap Wijnen on 24-10-17. +// + +struct Line2D: Equatable { + + var slope: Slope + var offset: Double + var direction: Direction + + enum Slope: Equatable { + case finite(slope: Double) + case infinite(offset: Double) + } + + enum Direction: Equatable { + case increasing + case decreasing + } + + init(from p1: Point2D, to p2: Point2D) { + if p1 == p2 { fatalError("Points can not be equal when creating a line between them") } + if p1.x == p2.x { + self.slope = .infinite(offset: p1.x) + self.offset = 0 + self.direction = p1.y < p2.y ? .increasing : .decreasing + + return + } + + let slope = (p1.y - p2.y)/(p1.x - p2.x) + self.slope = .finite(slope: slope) + offset = (p1.y + p2.y - slope * (p1.x + p2.x))/2 + if slope >= 0 { + // so a horizontal line going left to right is called increasing + self.direction = p1.x < p2.x ? .increasing : .decreasing + } else { + self.direction = p1.x < p2.x ? .decreasing : .increasing + } + } + + fileprivate init(slope: Slope, offset: Double, direction: Direction) { + self.slope = slope + self.offset = offset + self.direction = direction + } + + // returns y coordinate on line for given x + func y(at x: Double) -> Double { + switch self.slope { + case .finite(let slope): + return slope * x + self.offset + case .infinite: + fatalError("y can be anywhere on vertical line") + } + } + + // returns x coordinate on line for given y + func x(at y: Double) -> Double { + switch self.slope { + case .finite(let slope): + if slope == 0 { + fatalError("x can be anywhere on horizontal line") + } + return (y - self.offset)/slope + case .infinite(let offset): + return offset + } + } + + // finds intersection point between two lines. returns nil when lines don't intersect or lie on top of each other. + func intersect(with line: Line2D) -> Point2D? { + if self == line { return nil } + switch (self.slope, line.slope) { + case (.infinite, .infinite): + // lines are either parallel or on top of each other. + return nil + case (.finite(let slope1), .finite(let slope2)): + if slope1 == slope2 { return nil } // lines are parallel + // lines are not parallel calculate intersection point + let x = (line.offset - self.offset)/(slope1 - slope2) + let y = (slope1 + slope2) * x + self.offset + line.offset + return Point2D(x: x, y: y) + case (.infinite(let offset), .finite): + // one line is vertical so we only check what y value the other line has at that point + let x = offset + let y = line.y(at: x) + return Point2D(x: x, y: y) + case (.finite, .infinite(let offset)): + // one line is vertical so we only check what y value the other line has at that point + // lines are switched with respect to case above this one + let x = offset + let y = self.y(at: x) + return Point2D(x: x, y: y) + } + } + + // returns a line perpendicular to self at the given y coordinate + // direction of perpendicular lines always changes clockwise + func perpendicularLineAt(y: Double) -> Line2D { + return perpendicularLineAt(p: Point2D(x: self.x(at: y), y: y)) + } + + // returns a line perpendicular to self at the given x coordinate + // direction of perpendicular lines always changes clockwise + func perpendicularLineAt(x: Double) -> Line2D { + return perpendicularLineAt(p: Point2D(x: x, y: self.y(at: x))) + } + + private func perpendicularLineAt(p: Point2D) -> Line2D { + switch self.slope { + case .finite(let slope): + if slope == 0 { + // line is horizontal so new line will be vertical + let dir: Direction = self.direction == .increasing ? .decreasing : .increasing + return Line2D(slope: .infinite(offset: p.x), offset: 0, direction: dir) + } + + // line is neither horizontal nor vertical + // we make a new line through the point p with new slope -1/slope + let offset = (slope + 1/slope)*p.x + self.offset + + // determine direction of new line based on direction of the old line and its slope + let dir: Direction + switch self.direction { + case .increasing: + dir = slope > 0 ? .decreasing : .increasing + case .decreasing: + dir = slope > 0 ? .increasing : .decreasing + } + return Line2D(slope: .finite(slope: -1/slope), offset: offset, direction: dir) + case .infinite: + // line is vertical so new line will be horizontal + let dir: Direction = self.direction == .increasing ? .increasing : .decreasing + return Line2D(slope: .finite(slope: 0), offset: p.y, direction: dir) + } + } +} diff --git a/Points Lines Planes/Points Lines Planes/2D/Point2D.swift b/Points Lines Planes/Points Lines Planes/2D/Point2D.swift new file mode 100644 index 000000000..bbd8305f8 --- /dev/null +++ b/Points Lines Planes/Points Lines Planes/2D/Point2D.swift @@ -0,0 +1,36 @@ +// +// Point2D.swift +// Points Lines Planes +// +// Created by Jaap Wijnen on 24-10-17. +// + +struct Point2D: Equatable { + var x: Double + var y: Double + + // returns true if point is on or right of line + func isRight(of line: Line2D) -> Bool { + switch line.slope { + case .finite: + let y = line.y(at: self.x) + switch line.direction { + case .increasing: + return y >= self.y + case .decreasing: + return y <= self.y + } + case .infinite(let offset): + switch line.direction { + case .increasing: + return self.x >= offset + case .decreasing: + return self.x <= offset + } + } + } + + func isLeft(of line: Line2D) -> Bool { + return !self.isRight(of: line) + } +} diff --git a/Points Lines Planes/Points Lines Planes/main.swift b/Points Lines Planes/Points Lines Planes/main.swift new file mode 100644 index 000000000..02137fc9d --- /dev/null +++ b/Points Lines Planes/Points Lines Planes/main.swift @@ -0,0 +1,24 @@ +// +// main.swift +// Points Lines Planes +// +// Created by Jaap Wijnen on 23-10-17. +// + +let p0 = Point2D(x: 0, y: 2) +let p1 = Point2D(x: 1, y: 1) +let p2 = Point2D(x: 1, y: 3) +let p3 = Point2D(x: 3, y: 1) +let p4 = Point2D(x: 3, y: 3) + +let horLine = Line2D(from: p1, to: p3) +let verLine = Line2D(from: p1, to: p2) +let line45 = Line2D(from: p1, to: p4) + +print(horLine.intersect(with: verLine)) +print(p0.isRight(of: line45)) +print(p0.isRight(of: horLine)) +print(p0.isRight(of: verLine)) +let lineperp = horLine.perpendicularLineAt(x: 2) +print(lineperp) + diff --git a/Points Lines Planes/README.md b/Points Lines Planes/README.md new file mode 100644 index 000000000..2d36667ec --- /dev/null +++ b/Points Lines Planes/README.md @@ -0,0 +1,34 @@ +# Points Lines (Planes) +This implements data structures for points lines and planes(not yet) in (for now) 2D space and a few functions to play around with them. This was originally written to improve on the Convex Hull algorithm but I thought it might be a nice addition in itself. Im planning to add 3D implementations as well. + +# implementation +Two `struct`s are implemented the `Point2D` and `Line2D`. + +``` +struct Point2D: { + var x: Double + var y: double +} +``` + +``` +struct Line2D { + var slope: Slope + var offset: Double + var direction: Direction +} +``` +Here `Slope` is an enum to account for vertical lines. +slope is infinite for vertical lines, offset is the x coordinate where the line crosses the line `y=0`. +slope is finite for any other line and contains a double with the actual value of the slope. +``` +enum Slope { + case finite(slope: Double) + case infinite(offset: Double) +} +``` +`Line2D` also contains a `Direction` enum. This is introduced in order to make lines directional in order to be able to determine what is left and right of a certain line. It is `.increasing` if the line points in positive y direction and `.decreasing` if it points in the negative y direction. + +`Line2D`'s offset is the the y-coordinate where the line crosses the vertical `x=0` line. + +*Written for the Swift Algorithm Club by Jaap Wijnen.* diff --git a/Queue/Queue-Optimized.swift b/Queue/Queue-Optimized.swift index 7dff91698..3b17b98cb 100644 --- a/Queue/Queue-Optimized.swift +++ b/Queue/Queue-Optimized.swift @@ -23,7 +23,7 @@ public struct Queue { } public mutating func dequeue() -> T? { - guard head < array.count, let element = array[head] else { return nil } + guard let element = array[guarded: head] else { return nil } array[head] = nil head += 1 @@ -45,3 +45,12 @@ public struct Queue { } } } + +extension Array { + subscript(guarded idx: Int) -> Element? { + guard (startIndex..=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 - } - - 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 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 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 +// MARK: - Finding a nodes successor and predecessor extension RBTreeNode { /* @@ -145,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.. [Int] { var a = [Int](repeating: 0, count: n) for i in 0.. [Int] { var a = Array(repeating: 0, count: n) 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/Slow Sort/SlowSort.swift b/Slow Sort/SlowSort.swift index 217733daa..968f6841b 100644 --- a/Slow Sort/SlowSort.swift +++ b/Slow Sort/SlowSort.swift @@ -1,6 +1,6 @@ // // SlowSort.swift -// +// // // Created by Pope Lukas Schramm (Dabendorf Orthodox Religion) on 16-07-16. // @@ -9,12 +9,12 @@ func slowSort(_ i: Int, _ j: Int, _ numberList: inout [Int]) { guard if 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) + } } diff --git a/swift-algorithm-club.xcworkspace/contents.xcworkspacedata b/swift-algorithm-club.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a37dac494..000000000 --- a/swift-algorithm-club.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,2894 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -