diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3bd5a1a91..29f4ca7e1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,7 +4,9 @@ Want to help out with the Swift Algorithm Club? Great! While we don't have stric **Readability** -The `README` file is the cake, and the sample code is the cherry on top. Readablity is really important for us. A good contribution has succinct explanations supported by diagrams. Code is best introduced in chunks, weaved into the explanations where relevant. +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 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** @@ -20,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/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 416641261..5c72ed5a5 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,13 +1,7 @@ -### Category - -- [ ] Bug -- [ ] Feature Request -- [ ] Question - ### Brief Intro ### More Details - \ No newline at end of file + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d413140fc..000000000 --- a/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -language: objective-c -osx_image: xcode8.3 -# sudo: false - -install: - -# - ./install_swiftlint.sh - -script: - -- xcodebuild test -project ./All-Pairs\ Shortest\ Paths/APSP/APSP.xcodeproj -scheme APSPTests -- xcodebuild test -project ./Array2D/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./AVL\ Tree/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Binary\ Search/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Boyer-Moore/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Binary\ Search\ Tree/Solution\ 1/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Bloom\ Filter/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Bounded\ Priority\ Queue/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Breadth-First\ Search/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Bucket\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./B-Tree/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Comb\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Counting\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Depth-First\ Search/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Graph/Graph.xcodeproj -scheme GraphTests -- xcodebuild test -project ./Heap/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Heap\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Insertion\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./K-Means/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Linked\ List/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Longest\ Common\ Subsequence/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Minimum\ Spanning\ Tree\ \(Unweighted\)/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Priority\ Queue/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Queue/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Quicksort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Radix\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Rootish\ Array\ Stack/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Select\ Minimum\ Maximum/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Selection\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Shell\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Shortest\ Path\ \(Unweighted\)/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Single-Source\ Shortest\ Paths\ \(Weighted\)/SSSP.xcodeproj -scheme SSSPTests -- xcodebuild test -project ./Stack/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Topological\ Sort/Tests/Tests.xcodeproj -scheme Tests -- xcodebuild test -project ./Treap/Treap/Treap.xcodeproj -scheme Tests -- xcodebuild test -project ./Palindromes/Test/Test.xcodeproj -scheme Test -- xcodebuild test -project ./Ternary\ Search\ Tree/Tests/Tests.xcodeproj -scheme Tests diff --git a/3Sum and 4Sum/3Sum.playground/Contents.swift b/3Sum and 4Sum/3Sum.playground/Contents.swift new file mode 100644 index 000000000..d172aed1f --- /dev/null +++ b/3Sum and 4Sum/3Sum.playground/Contents.swift @@ -0,0 +1,67 @@ +// 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. + /// + /// - Parameter index: A valid index of the collection. `index` must be less than `endIndex` + func formUniqueIndex(after index: inout Index) { + var prev = index + repeat { + prev = index + formIndex(after: &index) + } while index < endIndex && self[prev] == self[index] + } +} + +extension BidirectionalCollection where Element: Equatable { + + /// In a sorted collection, replaces the given index with a predecessor that maps to a unique element. + /// + /// - Parameter index: A valid index of the collection. `index` must be greater than `startIndex`. + func formUniqueIndex(before index: inout Index) { + var prev = index + repeat { + prev = index + formIndex(before: &index) + } while index > startIndex && self[prev] == self[index] + } +} + +func threeSum(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable { + let sorted = collection.sorted() + var ret: [[T.Element]] = [] + var l = sorted.startIndex + + ThreeSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) } + var m = sorted.index(after: l) + var r = sorted.index(before: sorted.endIndex) + + TwoSum: while m < r && r < sorted.endIndex { + let sum = sorted[l] + sorted[m] + sorted[r] + if sum == target { + ret.append([sorted[l], sorted[m], sorted[r]]) + sorted.formUniqueIndex(after: &m) + sorted.formUniqueIndex(before: &r) + } else if sum < target { + sorted.formUniqueIndex(after: &m) + } else { + sorted.formUniqueIndex(before: &r) + } + } + } + + return ret +} + +// Answer: [[-1, 0, 1], [-1, -1, 2]] +threeSum([-1, 0, 1, 2, -1, -4], target: 0) + +// Answer: [[-1, -1, 2], [-1, 0, 1]] +threeSum([-1, -1, -1, -1, 2, 1, -4, 0], target: 0) + +// Answer: [[-1, -1, 2]] +threeSum([-1, -1, -1, -1, -1, -1, 2], target: 0) diff --git a/Fizz Buzz/FizzBuzz.playground/contents.xcplayground b/3Sum and 4Sum/3Sum.playground/contents.xcplayground similarity index 100% rename from Fizz Buzz/FizzBuzz.playground/contents.xcplayground rename to 3Sum and 4Sum/3Sum.playground/contents.xcplayground diff --git a/Binary Search/BinarySearch.playground/playground.xcworkspace/contents.xcworkspacedata b/3Sum and 4Sum/3Sum.playground/playground.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Binary Search/BinarySearch.playground/playground.xcworkspace/contents.xcworkspacedata rename to 3Sum and 4Sum/3Sum.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/3Sum and 4Sum/3Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/3Sum and 4Sum/3Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/3Sum and 4Sum/3Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/3Sum and 4Sum/4Sum.playground/Contents.swift b/3Sum and 4Sum/4Sum.playground/Contents.swift new file mode 100644 index 000000000..37f75918c --- /dev/null +++ b/3Sum and 4Sum/4Sum.playground/Contents.swift @@ -0,0 +1,64 @@ +// 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. + /// + /// - Parameter index: A valid index of the collection. `index` must be less than `endIndex` + func formUniqueIndex(after index: inout Index) { + var prev = index + repeat { + prev = index + formIndex(after: &index) + } while index < endIndex && self[prev] == self[index] + } +} + +extension BidirectionalCollection where Element: Equatable { + + /// In a sorted collection, replaces the given index with a predecessor that maps to a unique element. + /// + /// - Parameter index: A valid index of the collection. `index` must be greater than `startIndex`. + func formUniqueIndex(before index: inout Index) { + var prev = index + repeat { + prev = index + formIndex(before: &index) + } while index > startIndex && self[prev] == self[index] + } +} + +func fourSum(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable { + let sorted = collection.sorted() + var ret: [[T.Element]] = [] + + var l = sorted.startIndex + FourSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) } + var ml = sorted.index(after: l) + + ThreeSum: while ml < sorted.endIndex { defer { sorted.formUniqueIndex(after: &ml) } + var mr = sorted.index(after: ml) + var r = sorted.index(before: sorted.endIndex) + + TwoSum: while mr < r && r < sorted.endIndex { + let sum = sorted[l] + sorted[ml] + sorted[mr] + sorted[r] + if sum == target { + ret.append([sorted[l], sorted[ml], sorted[mr], sorted[r]]) + sorted.formUniqueIndex(after: &mr) + sorted.formUniqueIndex(before: &r) + } else if sum < target { + sorted.formUniqueIndex(after: &mr) + } else { + sorted.formUniqueIndex(before: &r) + } + } + } + } + return ret +} + +// answer: [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]] +fourSum([1, 0, -1, 0, -2, 2], target: 0) diff --git a/Ordered Set/AppleOrderedSet.playground/contents.xcplayground b/3Sum and 4Sum/4Sum.playground/contents.xcplayground similarity index 100% rename from Ordered Set/AppleOrderedSet.playground/contents.xcplayground rename to 3Sum and 4Sum/4Sum.playground/contents.xcplayground diff --git a/Boyer-Moore/BoyerMoore.playground/playground.xcworkspace/contents.xcworkspacedata b/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Boyer-Moore/BoyerMoore.playground/playground.xcworkspace/contents.xcworkspacedata rename to 3Sum and 4Sum/4Sum.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/3Sum and 4Sum/4Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/3Sum and 4Sum/README.md b/3Sum and 4Sum/README.md new file mode 100644 index 000000000..188f89109 --- /dev/null +++ b/3Sum and 4Sum/README.md @@ -0,0 +1,162 @@ +# 3Sum and 4Sum + +3Sum and 4Sum are extensions of a popular algorithm question, the [2Sum][5]. + +## 3Sum + +> Given an array of integers, find all subsets of the array with 3 values where the 3 values sum up to a target number. +> +> **Note**: The solution subsets must not contain duplicate triplets. +> +> For example, given the array [-1, 0, 1, 2, -1, -4], and the target **0**: +> The solution set is: [[-1, 0, 1], [-1, -1, 2]] // The two **-1** values in the array are considered to be distinct + +There are 2 key procedures in solving this algorithm. Sorting the array, and avoiding duplicates. + +### Sorting + +Sorting your input array allows for powerful assumptions: + +* duplicates are always adjacent to each other +* moving an index to the right increases the value, while moving an index to the left decreases the value + +You'll make use of these two rules to create an efficient algorithm. + +### Avoiding Duplicates + +Since you pre-sort the array, duplicates will be adjacent to each other. You just need to skip over duplicates by comparing adjacent values: + +```swift +extension Collection where Element: Equatable { + + /// In a sorted collection, replaces the given index with a successor mapping to a unique element. + /// + /// - Parameter index: A valid index of the collection. `index` must be less than `endIndex` + func formUniqueIndex(after index: inout Index) { + var prev = index + repeat { + prev = index + formIndex(after: &index) + } while index < endIndex && self[prev] == self[index] + } +} +``` + +A similar implementation is used to get the unique index *before* a given index: + +```swift +extension BidirectionalCollection where Element: Equatable { + + /// In a sorted collection, replaces the given index with a predecessor that maps to a unique element. + /// + /// - Parameter index: A valid index of the collection. `index` must be greater than `startIndex`. + func formUniqueIndex(before index: inout Index) { + var prev = index + repeat { + prev = index + formIndex(before: &index) + } while index > startIndex && self[prev] == self[index] + } +} +``` + +### Assembling the Subsets + +You'll keep track of 3 indices to represent the 3 numbers. The sum at any given moment is `array[l] + array[m] + array[r]`: + +```swift + m -> <- r +[-4, -1, -1, 0, 1, 2] +  l +``` + +The premise is quite straightforward (given that you're familiar with 2Sum). You'll iterate `l` through the array. For every iteration, you also apply the 2Sum algorithm to elements after `l`. You'll check the sum every time you moving the indices to check if you found match. Here's the algorithm: + +```swift +func threeSum(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable { + let sorted = collection.sorted() + var ret: [[T.Element]] = [] + var l = sorted.startIndex + + ThreeSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) } + var m = sorted.index(after: l) + var r = sorted.index(before: sorted.endIndex) + + TwoSum: while m < r && r < sorted.endIndex { + let sum = sorted[l] + sorted[m] + sorted[r] + if sum == target { + ret.append([sorted[l], sorted[m], sorted[r]]) + sorted.formUniqueIndex(after: &m) + sorted.formUniqueIndex(before: &r) + } else if sum < target { + sorted.formUniqueIndex(after: &m) + } else { + sorted.formUniqueIndex(before: &r) + } + } + } + + return ret +} +``` + +## 4Sum + +> Given an array S of n integers, find all subsets of the array with 4 values where the 4 values sum up to a target number. +> +> **Note**: The solution set must not contain duplicate quadruplets. + +### Solution + +Foursum is a very straightforward extension to the threeSum algorithm. In threeSum, you kept track of 3 indices: + +```swift + m -> <- r +[-4, -1, -1, 0, 1, 2] +  l +``` + +For fourSum, you'll keep track of 4: + +```swift + mr -> <- r +[-4, -1, -1, 0, 1, 2] +  l ml -> +``` + +Here's the code for it (notice it is very similar to 3Sum): + +```swift +func fourSum(_ collection: T, target: T.Element) -> [[T.Element]] where T.Element: Numeric & Comparable { + let sorted = collection.sorted() + var ret: [[T.Element]] = [] + + var l = sorted.startIndex + FourSum: while l < sorted.endIndex { defer { sorted.formUniqueIndex(after: &l) } + var ml = sorted.index(after: l) + + ThreeSum: while ml < sorted.endIndex { defer { sorted.formUniqueIndex(after: &ml) } + var mr = sorted.index(after: ml) + var r = sorted.index(before: sorted.endIndex) + + TwoSum: while mr < r && r < sorted.endIndex { + let sum = sorted[l] + sorted[ml] + sorted[mr] + sorted[r] + if sum == target { + ret.append([sorted[l], sorted[ml], sorted[mr], sorted[r]]) + sorted.formUniqueIndex(after: &mr) + sorted.formUniqueIndex(before: &r) + } else if sum < target { + sorted.formUniqueIndex(after: &mr) + } else { + sorted.formUniqueIndex(before: &r) + } + } + } + } + return ret +} +``` + +[5]: https://github.com/raywenderlich/swift-algorithm-club/tree/master/Two-Sum%20Problem + +*Written for the Swift Algorithm Club by Kai Chen and Kelvin Lau* 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/Boyer-Moore/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/A-Star/Tests/AStarTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Boyer-Moore/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to A-Star/Tests/AStarTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata 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/Boyer-Moore/Tests/Info.plist b/A-Star/Tests/Info.plist similarity index 100% rename from Boyer-Moore/Tests/Info.plist rename to A-Star/Tests/Info.plist diff --git a/AVL Tree/AVLTree.playground/Contents.swift b/AVL Tree/AVLTree.playground/Contents.swift index 409dfdc49..34c1e6ddd 100644 --- a/AVL Tree/AVLTree.playground/Contents.swift +++ b/AVL Tree/AVLTree.playground/Contents.swift @@ -1,5 +1,6 @@ //: Playground - noun: a place where people can play + let tree = AVLTree() tree.insert(key: 5, payload: "five") diff --git a/AVL Tree/AVLTree.swift b/AVL Tree/AVLTree.swift index 52ea082ce..04763f03b 100644 --- a/AVL Tree/AVLTree.swift +++ b/AVL Tree/AVLTree.swift @@ -22,15 +22,15 @@ public class TreeNode { public typealias Node = TreeNode - - var payload: Payload? - - fileprivate var key: Key + + var payload: Payload? // Value held by the node + + fileprivate var key: Key // Node's name internal var leftChild: Node? internal var rightChild: Node? fileprivate var height: Int - fileprivate weak var parent: Node? - + weak fileprivate var parent: Node? + public init(key: Key, payload: Payload?, leftChild: Node?, rightChild: Node?, parent: Node?, height: Int) { self.key = key self.payload = payload @@ -38,47 +38,47 @@ public class TreeNode { self.rightChild = rightChild self.parent = parent self.height = height - + self.leftChild?.parent = self self.rightChild?.parent = self } - + public convenience init(key: Key, payload: Payload?) { self.init(key: key, payload: payload, leftChild: nil, rightChild: nil, parent: nil, height: 1) } - + public convenience init(key: Key) { self.init(key: key, payload: nil) } - + var isRoot: Bool { return parent == nil } - + var isLeaf: Bool { return rightChild == nil && leftChild == nil } - + var isLeftChild: Bool { return parent?.leftChild === self } - + var isRightChild: Bool { return parent?.rightChild === self } - + var hasLeftChild: Bool { return leftChild != nil } - + var hasRightChild: Bool { return rightChild != nil } - + var hasAnyChild: Bool { return leftChild != nil || rightChild != nil } - + var hasBothChildren: Bool { return leftChild != nil && rightChild != nil } @@ -88,10 +88,10 @@ public class TreeNode { open class AVLTree { public typealias Node = TreeNode - + fileprivate(set) var root: Node? fileprivate(set) var size = 0 - + public init() { } } @@ -101,7 +101,7 @@ extension TreeNode { public func minimum() -> TreeNode? { return leftChild?.minimum() ?? self } - + public func maximum() -> TreeNode? { return rightChild?.maximum() ?? self } @@ -112,11 +112,11 @@ extension AVLTree { get { return search(input: key) } set { insert(key: key, payload: newValue) } } - + public func search(input: Key) -> Payload? { return search(key: input, node: root)?.payload } - + fileprivate func search(key: Key, node: Node?) -> Node? { if let node = node { if key == node.key { @@ -142,7 +142,7 @@ extension AVLTree { } size += 1 } - + private func insert(input: Key, payload: Payload?, node: Node) { if input < node.key { if let child = node.leftChild { @@ -152,7 +152,7 @@ extension AVLTree { node.leftChild = child balance(node: child) } - } else { + } else if input != node.key { if let child = node.rightChild { insert(input: input, payload: payload, node: child) } else { @@ -175,25 +175,25 @@ extension AVLTree { updateHeightUpwards(node: node.parent) } } - + fileprivate func lrDifference(node: Node?) -> Int { let lHeight = node?.leftChild?.height ?? 0 let rHeight = node?.rightChild?.height ?? 0 return lHeight - rHeight } - + fileprivate func balance(node: Node?) { guard let node = node else { return } - + updateHeightUpwards(node: node.leftChild) updateHeightUpwards(node: node.rightChild) - + var nodes = [Node?](repeating: nil, count: 3) var subtrees = [Node?](repeating: nil, count: 4) let nodeParent = node.parent - + let lrFactor = lrDifference(node: node) if lrFactor > 1 { // left-left or left-right @@ -202,7 +202,7 @@ extension AVLTree { nodes[0] = node nodes[2] = node.leftChild nodes[1] = nodes[2]?.leftChild - + subtrees[0] = nodes[1]?.leftChild subtrees[1] = nodes[1]?.rightChild subtrees[2] = nodes[2]?.rightChild @@ -212,7 +212,7 @@ extension AVLTree { nodes[0] = node nodes[1] = node.leftChild nodes[2] = nodes[1]?.rightChild - + subtrees[0] = nodes[1]?.leftChild subtrees[1] = nodes[2]?.leftChild subtrees[2] = nodes[2]?.rightChild @@ -225,7 +225,7 @@ extension AVLTree { nodes[1] = node nodes[2] = node.rightChild nodes[0] = nodes[2]?.rightChild - + subtrees[0] = nodes[1]?.leftChild subtrees[1] = nodes[2]?.leftChild subtrees[2] = nodes[0]?.leftChild @@ -235,7 +235,7 @@ extension AVLTree { nodes[1] = node nodes[0] = node.rightChild nodes[2] = nodes[0]?.leftChild - + subtrees[0] = nodes[1]?.leftChild subtrees[1] = nodes[2]?.leftChild subtrees[2] = nodes[2]?.rightChild @@ -246,9 +246,9 @@ extension AVLTree { balance(node: node.parent) return } - + // nodes[2] is always the head - + if node.isRoot { root = nodes[2] root?.parent = nil @@ -259,25 +259,25 @@ extension AVLTree { nodeParent?.rightChild = nodes[2] nodes[2]?.parent = nodeParent } - + nodes[2]?.leftChild = nodes[1] nodes[1]?.parent = nodes[2] nodes[2]?.rightChild = nodes[0] nodes[0]?.parent = nodes[2] - + nodes[1]?.leftChild = subtrees[0] subtrees[0]?.parent = nodes[1] nodes[1]?.rightChild = subtrees[1] subtrees[1]?.parent = nodes[1] - + nodes[0]?.leftChild = subtrees[2] subtrees[2]?.parent = nodes[0] nodes[0]?.rightChild = subtrees[3] subtrees[3]?.parent = nodes[0] - + updateHeightUpwards(node: nodes[1]) // Update height from left updateHeightUpwards(node: nodes[0]) // Update height from right - + balance(node: nodes[2]?.parent) } } @@ -299,11 +299,35 @@ extension AVLTree { display(node: node.leftChild, level: level + 1) } } - + public func display(node: Node) { display(node: node, level: 0) print("") } + + public func inorder(node: Node?) -> String { + var output = "" + if let node = node { + output = "\(inorder(node: node.leftChild)) \(print("\(node.key) ")) \(inorder(node: node.rightChild))" + } + return output + } + + public func preorder(node: Node?) -> String { + var output = "" + if let node = node { + output = "\(preorder(node: node.leftChild)) \(print("\(node.key) ")) \(preorder(node: node.rightChild))" + } + return output + } + + public func postorder(node: Node?) -> String { + var output = "" + if let node = node { + output = "\(postorder(node: node.leftChild)) \(print("\(node.key) ")) \(postorder(node: node.rightChild))" + } + return output + } } // MARK: - Delete node @@ -318,7 +342,7 @@ extension AVLTree { size -= 1 } } - + private func delete(node: Node) { if node.isLeaf { // Just remove and balance up @@ -327,13 +351,13 @@ extension AVLTree { // just in case fatalError("Error: tree is invalid.") } - + if node.isLeftChild { parent.leftChild = nil } else if node.isRightChild { parent.rightChild = nil } - + balance(node: parent) } else { // at root @@ -354,6 +378,46 @@ extension AVLTree { } } +// MARK: - Advanced Stuff + +extension AVLTree { + public func doInOrder(node: Node?, _ completion: (Node) -> Void) { + if let node = node { + doInOrder(node: node.leftChild) { lnode in + completion(lnode) + } + completion(node) + doInOrder(node: node.rightChild) { rnode in + completion(rnode) + } + } + } + + public func doInPreOrder(node: Node?, _ completion: (Node) -> Void) { + if let node = node { + completion(node) + doInPreOrder(node: node.leftChild) { lnode in + completion(lnode) + } + doInPreOrder(node: node.rightChild) { rnode in + completion(rnode) + } + } + } + + public func doInPostOrder(node: Node?, _ completion: (Node) -> Void) { + if let node = node { + doInPostOrder(node: node.leftChild) { lnode in + completion(lnode) + } + doInPostOrder(node: node.rightChild) { rnode in + completion(rnode) + } + completion(node) + } + } +} + // MARK: - Debugging extension TreeNode: CustomDebugStringConvertible { diff --git a/AVL Tree/README.markdown b/AVL Tree/README.markdown index e7515d99f..4e974ba71 100644 --- a/AVL Tree/README.markdown +++ b/AVL Tree/README.markdown @@ -88,4 +88,4 @@ The interesting bits are in the `balance()` method which is called after inserti AVL tree was the first self-balancing binary tree. These days, the [red-black tree](../Red-Black%20Tree/) seems to be more popular. -*Written for Swift Algorithm Club by Mike Taghavi and Matthijs Hollemans* +*Written for Swift Algorithm Club by [Mike Taghavi](https://github.com/mitghi) and [Matthijs Hollemans](https://github.com/hollance)* diff --git a/AVL Tree/Tests/AVLTreeTests.swift b/AVL Tree/Tests/AVLTreeTests.swift index 7765c6d29..a9e932117 100644 --- a/AVL Tree/Tests/AVLTreeTests.swift +++ b/AVL Tree/Tests/AVLTreeTests.swift @@ -7,11 +7,15 @@ // import XCTest - class AVLTreeTests: XCTestCase { - var tree: AVLTree? + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } override func setUp() { super.setUp() diff --git a/AVL Tree/Tests/Tests.xcodeproj/project.pbxproj b/AVL Tree/Tests/Tests.xcodeproj/project.pbxproj index 64c244747..e7c6e2989 100644 --- a/AVL Tree/Tests/Tests.xcodeproj/project.pbxproj +++ b/AVL Tree/Tests/Tests.xcodeproj/project.pbxproj @@ -180,6 +180,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -219,6 +220,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -230,7 +232,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 = 4.0; }; name = Debug; }; @@ -242,7 +244,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 = 4.0; }; name = Release; }; diff --git a/AVL Tree/Tests/TreeNodeTests.swift b/AVL Tree/Tests/TreeNodeTests.swift index cac7e549b..dc393232c 100644 --- a/AVL Tree/Tests/TreeNodeTests.swift +++ b/AVL Tree/Tests/TreeNodeTests.swift @@ -9,11 +9,16 @@ import XCTest class TreeNodeTests: XCTestCase { - + var root: TreeNode? var left: TreeNode? var right: TreeNode? - + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } override func setUp() { super.setUp() 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.playground/timeline.xctimeline b/All-Pairs Shortest Paths/APSP/APSP.playground/timeline.xctimeline index 2c73a4e3e..c938ca857 100644 --- a/All-Pairs Shortest Paths/APSP/APSP.playground/timeline.xctimeline +++ b/All-Pairs Shortest Paths/APSP/APSP.playground/timeline.xctimeline @@ -3,12 +3,12 @@ version = "3.0"> diff --git a/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj b/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj index b2d93ea35..dc8e509a1 100644 --- a/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj +++ b/All-Pairs Shortest Paths/APSP/APSP.xcodeproj/project.pbxproj @@ -187,16 +187,16 @@ 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; - LastSwiftMigration = 0820; + LastSwiftMigration = 1010; }; }; }; @@ -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 = 3.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 = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -420,7 +436,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.APSP"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -442,7 +458,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.APSP"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + 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 @@ + + + + IDEDidComputeMac32BitWarning + + + diff --git a/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift b/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift index 9d026f680..ce0dfcf32 100644 --- a/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift +++ b/All-Pairs Shortest Paths/APSP/APSPTests/APSPTests.swift @@ -19,7 +19,6 @@ struct TestCase where T: Hashable { } class APSPTests: XCTestCase { - /** See Figure 25.1 of “Introduction to Algorithms” by Cormen et al, 3rd ed., pg 690 */ diff --git a/All-Pairs Shortest Paths/README.markdown b/All-Pairs Shortest Paths/README.markdown index 8a75c8b73..bbef6c64e 100644 --- a/All-Pairs Shortest Paths/README.markdown +++ b/All-Pairs Shortest Paths/README.markdown @@ -1,14 +1,14 @@ # All-Pairs Shortest Paths -The All-Pairs shortest path problem simultaneously computes the shortest path from each node in the graph to each other node, provded a path exists for each pair. In the naive approach, we could simply compute a single-source shortest path from each node to each other node. The number of shortest paths computed is then bound by `O(V^2)`, where `V` is the number of vertices in the graph. Because SSSP is also bounded by `O(V^2)`, the total running time for the naive approach would be `O(V^4)`. +The All-Pairs shortest path problem simultaneously computes the shortest path from each node in the graph to each other node, provided a path exists for each pair. In the naive approach, we could simply compute a single-source shortest path from each node to each other node. The number of shortest paths computed is then bound by `O(V^2)`, where `V` is the number of vertices in the graph. Because SSSP is also bounded by `O(V^2)`, the total running time for the naive approach would be `O(V^4)`. -However, by applying a dynamic approach on the adjacency matrix of the graph, a running time of `Θ(V^3)` is achievable, using the `Floyd-Warshall` algorithm. Floyd-Warshall iterates through an adjacency matrix, and for each pair of start(`i`) and end(`j`) vertices it considers if the current distance between them is greater than a path taken through another vertex(`k`) in the graph (if paths `i` ~> `k` and `k` ~> `j` exist). It moves through an adjacency matrix for every vertex `k` applying its comparison for each pair (`i`, `j`), so for each `k` a new adjacency matrix `D(k)` is derived, where each value `d(k)[i][j]` is defined as: +However, by applying a dynamic approach on the adjacency matrix of the graph, a running time of `O(V^3)` is achievable, using the `Floyd-Warshall` algorithm. Floyd-Warshall iterates through an adjacency matrix, and for each pair of start(`i`) and end(`j`) vertices it considers if the current distance between them is greater than a path taken through another vertex(`k`) in the graph (if paths `i` ~> `k` and `k` ~> `j` exist). It moves through an adjacency matrix for every vertex `k` applying its comparison for each pair (`i`, `j`), so for each `k` a new adjacency matrix `D(k)` is derived, where each value `d(k)[i][j]` is defined as: -where `w[i][j]` is the weight of the edge connecting vertex `i` to vertex `j` in the graph's original adjacency matrix. +where `w[i][j]` is the weight of the edge connecting vertex `i` to vertex `j` in the graph's original adjacency matrix. -When the algorithm memoizes each refined adjacency and predecessor matrix, its space complexity is `Θ(V^3)`, which can be optimised to `Θ(V^2)` by only memoizing the latest refinements. Reconstructing paths is a recursive procedure which requires `Θ(V)` time and `Θ(V^2)` space. +When the algorithm memoizes each refined adjacency and predecessor matrix, its space complexity is `O(V^3)`, which can be optimised to `O(V^2)` by only memoizing the latest refinements. Reconstructing paths is a recursive procedure which requires `O(V)` time and `O(V^2)` space. # Example @@ -22,9 +22,9 @@ the adjacency matrix representation `w` is ### Calculating shortest paths' weights -At the beginning of the algorithm, `D(0)` is the same as `w`, with the following exceptions to accomodate the comparison function: +At the beginning of the algorithm, `D(0)` is the same as `w`, with the following exceptions to accommodate the comparison function: -1. vertices with no path connecting them have the `ø` replaced with `∞` +1. vertices with no path connecting them have the `ø` replaced with `∞` 2. the diagonal has all `0`s Here are all the adjacency matrices derived when perform Floyd-Warshall on the above graph: @@ -56,7 +56,7 @@ This algorithm finds only the lengths of the shortest paths between all pairs of # Project Structure -The provided xcworkspace allows working in the playground, which imports the APSP framework target from the xcodeproj. Build the framework target and rerun the playground to get started. There is also a test target in the xcodeproj. +The provided xcworkspace allows working in the playground, which imports the APSP framework target from the xcodeproj. Build the framework target and rerun the playground to get started. There is also a test target in the xcodeproj. In the framework: @@ -65,7 +65,7 @@ In the framework: # TODO -- Implement naive `Θ(V^4)` method for comparison +- Implement naive `O(V^4)` method for comparison - Implement Johnson's algorithm for sparse graphs - Implement other cool optimized versions @@ -73,4 +73,4 @@ In the framework: Chapter 25 of Introduction to Algorithms, Third Edition by Cormen, Leiserson, Rivest and Stein [https://mitpress.mit.edu/books/introduction-algorithms](https://mitpress.mit.edu/books/introduction-algorithms) -*Written for Swift Algorithm Club by [Andrew McKnight](https://github.com/armcknight)* \ No newline at end of file +*Written for Swift Algorithm Club by [Andrew McKnight](https://github.com/armcknight)* diff --git a/Array2D/README.markdown b/Array2D/README.markdown index 88f25c99a..cf00638e1 100644 --- a/Array2D/README.markdown +++ b/Array2D/README.markdown @@ -33,7 +33,7 @@ You can also create the array in a single line of code: var cookies = [[Int]](repeating: [Int](repeating: 0, count: 7), count: 9) ``` -This looks complicated, but can simplfy it in a helper function: +This looks complicated, but you can simplify it with a helper function: ```swift func dim(_ count: Int, _ value: T) -> [T] { diff --git a/Array2D/Tests/Tests.xcodeproj/project.pbxproj b/Array2D/Tests/Tests.xcodeproj/project.pbxproj index a0fb1480b..af9b7c3b8 100644 --- a/Array2D/Tests/Tests.xcodeproj/project.pbxproj +++ b/Array2D/Tests/Tests.xcodeproj/project.pbxproj @@ -226,7 +226,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 = 4.2; }; name = Debug; }; @@ -238,7 +238,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 = 4.2; }; name = Release; }; diff --git a/B-Tree/BTree.playground/Contents.swift b/B-Tree/BTree.playground/Contents.swift index 710ace701..f6325513a 100644 --- a/B-Tree/BTree.playground/Contents.swift +++ b/B-Tree/BTree.playground/Contents.swift @@ -2,6 +2,8 @@ import Foundation +// last checked with Xcode 10.0 + let bTree = BTree(order: 1)! bTree.insert(1, for: 1) @@ -15,7 +17,7 @@ bTree[3] bTree.remove(2) bTree.traverseKeysInOrder { key in - print(key) + print(key) } bTree.numberOfKeys 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/B-Tree/BTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/B-Tree/BTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/B-Tree/BTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/B-Tree/README.md b/B-Tree/README.md index f6924aadc..10a06853e 100644 --- a/B-Tree/README.md +++ b/B-Tree/README.md @@ -133,7 +133,7 @@ Else: ![Moving Key](Images/MovingKey.png) -####Merging two nodes +#### Merging two nodes Let's say we want to merge the child `c1` at index `i` in its parent's children array. diff --git a/B-Tree/Tests/Tests.xcodeproj/project.pbxproj b/B-Tree/Tests/Tests.xcodeproj/project.pbxproj index 79552bbe5..66eb85190 100644 --- a/B-Tree/Tests/Tests.xcodeproj/project.pbxproj +++ b/B-Tree/Tests/Tests.xcodeproj/project.pbxproj @@ -85,12 +85,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Viktor Szilárd Simkó"; TargetAttributes = { C66702771D0EEE25008CD769 = { CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1000; }; }; }; @@ -144,14 +144,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; @@ -179,6 +187,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -191,14 +200,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; @@ -219,6 +236,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -232,7 +250,7 @@ PRODUCT_BUNDLE_IDENTIFIER = viktorsimko.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -245,7 +263,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = viktorsimko.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/B-Tree/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/B-Tree/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/B-Tree/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/B-Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/B-Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 9d6c542ae..d1555acfb 100644 --- a/B-Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/B-Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ (order: 2)! - var root: BTreeNode! - var leftChild: BTreeNode! - var rightChild: BTreeNode! - - override func setUp() { - super.setUp() - - root = BTreeNode(owner: owner) - leftChild = BTreeNode(owner: owner) - rightChild = BTreeNode(owner: owner) - - root.insert(1, for: 1) - root.children = [leftChild, rightChild] - } - - func testIsLeafRoot() { - XCTAssertFalse(root.isLeaf) - } - - func testIsLeafLeaf() { - XCTAssertTrue(leftChild.isLeaf) - XCTAssertTrue(rightChild.isLeaf) - } - - func testOwner() { - XCTAssert(root.owner === owner) - XCTAssert(leftChild.owner === owner) - XCTAssert(rightChild.owner === owner) - } - - func testNumberOfKeys() { - XCTAssertEqual(root.numberOfKeys, 1) - XCTAssertEqual(leftChild.numberOfKeys, 0) - XCTAssertEqual(rightChild.numberOfKeys, 0) - } - - func testChildren() { - XCTAssertEqual(root.children!.count, 2) - } + + let owner = BTree(order: 2)! + var root: BTreeNode! + var leftChild: BTreeNode! + var rightChild: BTreeNode! + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + override func setUp() { + super.setUp() + + root = BTreeNode(owner: owner) + leftChild = BTreeNode(owner: owner) + rightChild = BTreeNode(owner: owner) + + root.insert(1, for: 1) + root.children = [leftChild, rightChild] + } + + func testIsLeafRoot() { + XCTAssertFalse(root.isLeaf) + } + + func testIsLeafLeaf() { + XCTAssertTrue(leftChild.isLeaf) + XCTAssertTrue(rightChild.isLeaf) + } + + func testOwner() { + XCTAssert(root.owner === owner) + XCTAssert(leftChild.owner === owner) + XCTAssert(rightChild.owner === owner) + } + + func testNumberOfKeys() { + XCTAssertEqual(root.numberOfKeys, 1) + XCTAssertEqual(leftChild.numberOfKeys, 0) + XCTAssertEqual(rightChild.numberOfKeys, 0) + } + + func testChildren() { + XCTAssertEqual(root.children!.count, 2) + } } diff --git a/B-Tree/Tests/Tests/BTreeTests.swift b/B-Tree/Tests/Tests/BTreeTests.swift index 1e94bfd7d..69d7ce55d 100644 --- a/B-Tree/Tests/Tests/BTreeTests.swift +++ b/B-Tree/Tests/Tests/BTreeTests.swift @@ -11,6 +11,13 @@ import XCTest class BTreeTests: XCTestCase { var bTree: BTree! + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + override func setUp() { super.setUp() bTree = BTree(order: 3)! diff --git a/Big-O Notation.markdown b/Big-O Notation.markdown index 746cba8a3..c3f020df2 100644 --- a/Big-O Notation.markdown +++ b/Big-O Notation.markdown @@ -15,7 +15,133 @@ Big-O | Name | Description **O(n^2)** | quadratic | **Kinda slow.** If you have 100 items, this does 100^2 = 10,000 units of work. Doubling the number of items makes it four times slower (because 2 squared equals 4). Example: algorithms using nested loops, such as insertion sort. **O(n^3)** | cubic | **Poor performance.** If you have 100 items, this does 100^3 = 1,000,000 units of work. Doubling the input size makes it eight times slower. Example: matrix multiplication. **O(2^n)** | exponential | **Very poor performance.** You want to avoid these kinds of algorithms, but sometimes you have no choice. Adding just one bit to the input doubles the running time. Example: traveling salesperson problem. -**O(n!)** | factorial | **Intolerably slow.** It literally takes a million years to do anything. +**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)** + + The most common example with O(1) complexity is accessing an array index. + + ```swift + let value = array[5] + ``` + + Another example of O(1) is pushing and popping from Stack. + + +**O(log n)** + + ```swift + var j = 1 + while j < n { + // do constant time stuff + j *= 2 + } + ``` + + Instead of simply incrementing, 'j' is increased by 2 times itself in each run. + + Binary Search Algorithm is an example of O(log n) complexity. + + +**O(n)** + + ```swift + for i in stride(from: 0, to: n, by: 1) { + print(array[i]) + } + ``` + + Array Traversal and Linear Search are examples of O(n) complexity. + + +**O(n log n)** + + ```swift + for i in stride(from: 0, to: n, by: 1) { + var j = 1 + while j < n { + j *= 2 + // do constant time stuff + } + } + ``` + + OR + + ```swift + for i in stride(from: 0, to: n, by: 1) { + func index(after i: Int) -> Int? { // multiplies `i` by 2 until `i` >= `n` + return i < n ? i * 2 : nil + } + for j in sequence(first: 1, next: index(after:)) { + // do constant time stuff + } + } + ``` + + Merge Sort and Heap Sort are examples of O(n log n) complexity. + + +**O(n^2)** + + ```swift + for i in stride(from: 0, to: n, by: 1) { + for j in stride(from: 1, to: n, by: 1) { + // do constant time stuff + } + } + ``` + + Traversing a simple 2-D array and Bubble Sort are examples of O(n^2) complexity. + + +**O(n^3)** + + ```swift + for i in stride(from: 0, to: n, by: 1) { + for j in stride(from: 1, to: n, by: 1) { + for k in stride(from: 1, to: n, by: 1) { + // do constant time stuff + } + } + } + ``` + +**O(2^n)** + + Algorithms with running time O(2^N) are often recursive algorithms that solve a problem of size N by recursively solving two smaller problems of size N-1. + The following example prints all the moves necessary to solve the famous "Towers of Hanoi" problem for N disks. + + ```swift + 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) + solveHanoi(n: n - 1, from: spare, to: to, spare: from) + } + } + ``` + + +**O(n!)** + + The most trivial example of function that takes O(n!) time is given below. + + ```swift + func nFactFunc(n: Int) { + for i in stride(from: 0, to: n, by: 1) { + nFactFunc(n: n - 1) + } + } + ``` Often you don't need math to figure out what the Big-O of an algorithm is but you can simply use your intuition. If your code uses a single loop that looks at all **n** elements of your input, the algorithm is **O(n)**. If the code has two nested loops, it is **O(n^2)**. Three nested loops gives **O(n^3)**, and so on. diff --git a/Binary Search Tree/README.markdown b/Binary Search Tree/README.markdown index 80c2df7df..57d1f4bff 100644 --- a/Binary Search Tree/README.markdown +++ b/Binary Search Tree/README.markdown @@ -1,5 +1,8 @@ # Binary Search Tree (BST) +> This topic has been tutorialized [here](https://www.raywenderlich.com/139821/swift-algorithm-club-swift-binary-search-tree-data-structure) + + A binary search tree is a special kind of [binary tree](../Binary%20Tree/) (a tree in which each node has at most two children) that performs insertions and deletions such that the tree is always sorted. For more information about a tree, [read this first](../Tree/). @@ -265,9 +268,9 @@ If there are no more nodes to look at -- when `left` or `right` is nil -- then w Searching is a recursive process, but you can also implement it with a simple loop instead: ```swift - public func search(value: T) -> BinarySearchTree? { + public func search(_ value: T) -> BinarySearchTree? { var node: BinarySearchTree? = self - while case let n? = node { + while let n = node { if value < n.value { node = n.left } else if value > n.value { @@ -372,7 +375,7 @@ As an exercise, see if you can implement filter and reduce. We can make the code more readable by defining some helper functions. ```swift - private func reconnectParentToNode(node: BinarySearchTree?) { + private func reconnectParentTo(node: BinarySearchTree?) { if let parent = parent { if isLeftChild { parent.left = node @@ -391,7 +394,7 @@ We also need a function that returns the minimum and maximum of a node: ```swift public func minimum() -> BinarySearchTree { var node = self - while case let next? = node.left { + while let next = node.left { node = next } return node @@ -399,7 +402,7 @@ We also need a function that returns the minimum and maximum of a node: public func maximum() -> BinarySearchTree { var node = self - while case let next? = node.right { + while let next = node.right { node = next } return node @@ -471,7 +474,7 @@ You can also calculate the *depth* of a node, which is the distance to the root. public func depth() -> Int { var node = self var edges = 0 - while case let parent? = node.parent { + while let parent = node.parent { node = parent edges += 1 } @@ -503,7 +506,7 @@ The `predecessor()` function returns the node whose value precedes the current v return left.maximum() } else { var node = self - while case let parent? = node.parent { + while let parent = node.parent { if parent.value < value { return parent } node = parent } @@ -524,7 +527,7 @@ The code for `successor()` works the same way but mirrored: return right.minimum() } else { var node = self - while case let parent? = node.parent { + while let parent = node.parent { if parent.value > value { return parent } node = parent } @@ -554,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 @@ -612,8 +615,8 @@ This implementation is recursive, and each case of the enum will be treated diff public var height: Int { switch self { - case .Empty: return 0 - case .Leaf: return 1 + case .Empty: return -1 + case .Leaf: return 0 case let .Node(left, _, right): return 1 + max(left.height, right.height) } } 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 8a126e950..2d0eb7e57 100644 --- a/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift +++ b/Binary Search Tree/Solution 1/BinarySearchTree.playground/Sources/BinarySearchTree.swift @@ -155,7 +155,7 @@ extension BinarySearchTree { */ public func search(value: T) -> BinarySearchTree? { var node: BinarySearchTree? = self - while case let n? = node { + while let n = node { if value < n.value { node = n.left } else if value > n.value { @@ -189,7 +189,7 @@ extension BinarySearchTree { */ public func minimum() -> BinarySearchTree { var node = self - while case let next? = node.left { + while let next = node.left { node = next } return node @@ -200,7 +200,7 @@ extension BinarySearchTree { */ public func maximum() -> BinarySearchTree { var node = self - while case let next? = node.right { + while let next = node.right { node = next } return node @@ -213,7 +213,7 @@ extension BinarySearchTree { public func depth() -> Int { var node = self var edges = 0 - while case let parent? = node.parent { + while let parent = node.parent { node = parent edges += 1 } @@ -235,12 +235,12 @@ 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 { var node = self - while case let parent? = node.parent { + while let parent = node.parent { if parent.value < value { return parent } node = parent } @@ -251,12 +251,12 @@ 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 { var node = self - while case let parent? = node.parent { + while let parent = node.parent { if parent.value > value { return parent } node = parent } diff --git a/Binary Search Tree/Solution 1/BinarySearchTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Binary Search Tree/Solution 1/BinarySearchTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Binary Search Tree/Solution 1/BinarySearchTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Binary Search Tree/Solution 1/BinarySearchTree.playground/timeline.xctimeline b/Binary Search Tree/Solution 1/BinarySearchTree.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Binary Search Tree/Solution 1/BinarySearchTree.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/project.pbxproj b/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/project.pbxproj index f4c3f65c7..d7c687e0c 100644 --- a/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/project.pbxproj +++ b/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/project.pbxproj @@ -83,12 +83,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0820; + LastSwiftMigration = 1000; }; }; }; @@ -141,13 +141,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -185,13 +195,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -210,6 +230,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; @@ -221,7 +242,8 @@ 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_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -233,7 +255,8 @@ 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_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 8ef8d8581..afd69e6a7 100644 --- a/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Binary Search Tree/Solution 1/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Binary Search Tree/Solution 2/BinarySearchTree.playground/timeline.xctimeline b/Binary Search Tree/Solution 2/BinarySearchTree.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Binary Search Tree/Solution 2/BinarySearchTree.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Binary Search Tree/Solution 2/BinarySearchTree.swift b/Binary Search Tree/Solution 2/BinarySearchTree.swift index 272b86368..23b1c8aca 100644 --- a/Binary Search Tree/Solution 2/BinarySearchTree.swift +++ b/Binary Search Tree/Solution 2/BinarySearchTree.swift @@ -20,8 +20,8 @@ public enum BinarySearchTree { /* Distance of this node to its lowest leaf. Performance: O(n). */ public var height: Int { switch self { - case .empty: return 0 - case .leaf: return 1 + case .empty: return -1 + case .leaf: return 0 case let .node(left, _, right): return 1 + max(left.height, right.height) } } diff --git a/Binary Search/BinarySearch.playground/Sources/BinarySearch.swift b/Binary Search/BinarySearch.playground/Sources/BinarySearch.swift deleted file mode 100644 index d6623e355..000000000 --- a/Binary Search/BinarySearch.playground/Sources/BinarySearch.swift +++ /dev/null @@ -1,52 +0,0 @@ -/** - Binary Search - - Recursively splits the array in half until the value is found. - - If there is more than one occurrence of the search key in the array, then - there is no guarantee which one it finds. - - Note: The array must be sorted! - **/ - -import Foundation - -// The recursive version of binary search. - -public func binarySearch(_ a: [T], key: T, range: Range) -> Int? { - if range.lowerBound >= range.upperBound { - return nil - } else { - let midIndex = range.lowerBound + (range.upperBound - range.lowerBound) / 2 - if a[midIndex] > key { - return binarySearch(a, key: key, range: range.lowerBound ..< midIndex) - } else if a[midIndex] < key { - return binarySearch(a, key: key, range: midIndex + 1 ..< range.upperBound) - } else { - return midIndex - } - } -} - -/** - The iterative version of binary search. - - Notice how similar these functions are. The difference is that this one - uses a while loop, while the other calls itself recursively. - **/ - -public func binarySearch(_ a: [T], key: T) -> Int? { - var lowerBound = 0 - var upperBound = a.count - while lowerBound < upperBound { - let midIndex = lowerBound + (upperBound - lowerBound) / 2 - if a[midIndex] == key { - return midIndex - } else if a[midIndex] < key { - lowerBound = midIndex + 1 - } else { - upperBound = midIndex - } - } - return nil -} diff --git a/Binary Search/BinarySearch.playground/contents.xcplayground b/Binary Search/BinarySearch.playground/contents.xcplayground index 06828af92..69d154d1e 100644 --- a/Binary Search/BinarySearch.playground/contents.xcplayground +++ b/Binary Search/BinarySearch.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Binary Search/BinarySearch.playground/timeline.xctimeline b/Binary Search/BinarySearch.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Binary Search/BinarySearch.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Binary Search/README.markdown b/Binary Search/README.markdown index 09b141e04..0cecce6cf 100644 --- a/Binary Search/README.markdown +++ b/Binary Search/README.markdown @@ -4,15 +4,15 @@ Goal: Quickly find an element in an array. Let's say you have an array of numbers and you want to determine whether a specific number is in that array, and if so, at which index. -In most cases, Swift's `indexOf()` function is good enough for that: +In most cases, Swift's `Collection.index(of:)` function is good enough for that: ```swift let numbers = [11, 59, 3, 2, 53, 17, 31, 7, 19, 67, 47, 13, 37, 61, 29, 43, 5, 41, 23] -numbers.indexOf(43) // returns 15 +numbers.index(of: 43) // returns 15 ``` -The built-in `indexOf()` function performs a [linear search](../Linear%20Search/). In code that looks something like this: +The built-in `Collection.index(of:)` function performs a [linear search](../Linear%20Search/). In code that looks something like this: ```swift func linearSearch(_ a: [T], _ key: T) -> Int? { diff --git a/Binary Tree/BinaryTree.playground/contents.xcplayground b/Binary Tree/BinaryTree.playground/contents.xcplayground index 06828af92..69d154d1e 100644 --- a/Binary Tree/BinaryTree.playground/contents.xcplayground +++ b/Binary Tree/BinaryTree.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Binary Tree/BinaryTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Binary Tree/BinaryTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Binary Tree/BinaryTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Binary Tree/BinaryTree.swift b/Binary Tree/BinaryTree.swift index 102f23e06..6146880b4 100644 --- a/Binary Tree/BinaryTree.swift +++ b/Binary Tree/BinaryTree.swift @@ -29,7 +29,7 @@ extension BinaryTree: CustomStringConvertible { } extension BinaryTree { - public func traverseInOrder(process: T -> Void) { + public func traverseInOrder(process: (T) -> Void) { if case let .node(left, value, right) = self { left.traverseInOrder(process: process) process(value) @@ -37,7 +37,7 @@ extension BinaryTree { } } - public func traversePreOrder(process: T -> Void) { + public func traversePreOrder(process: (T) -> Void) { if case let .node(left, value, right) = self { process(value) left.traversePreOrder(process: process) @@ -45,7 +45,7 @@ extension BinaryTree { } } - public func traversePostOrder(process: T -> Void) { + public func traversePostOrder(process: (T) -> Void) { if case let .node(left, value, right) = self { left.traversePostOrder(process: process) right.traversePostOrder(process: process) 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 d8985bd13..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] @@ -41,7 +41,7 @@ public struct BitSet { // 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 mask | (mask - 1) + return (Word)(mask | (mask - 1)) } else { return allOnes } @@ -167,7 +167,7 @@ extension BitSet: Hashable { // MARK: - Bitwise operations -extension BitSet: BitwiseOperations { +extension BitSet { public static var allZeros: BitSet { return BitSet(size: 64) } @@ -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.playground/contents.xcplayground b/Bit Set/BitSet.playground/contents.xcplayground index 06828af92..69d154d1e 100644 --- a/Bit Set/BitSet.playground/contents.xcplayground +++ b/Bit Set/BitSet.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Bit Set/BitSet.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bit Set/BitSet.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bit Set/BitSet.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Bit Set/BitSet.playground/timeline.xctimeline b/Bit Set/BitSet.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Bit Set/BitSet.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Bit Set/BitSet.swift b/Bit Set/BitSet.swift deleted file mode 100644 index d8985bd13..000000000 --- a/Bit Set/BitSet.swift +++ /dev/null @@ -1,247 +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 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: BitwiseOperations { - 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 3dac76e8e..8ed808a06 100644 --- a/Bloom Filter/BloomFilter.playground/Contents.swift +++ b/Bloom Filter/BloomFilter.playground/Contents.swift @@ -51,7 +51,7 @@ public class BloomFilter { func djb2(x: String) -> Int { var hash = 5381 - for char in x.characters { + for char in x { hash = ((hash << 5) &+ hash) &+ char.hashValue } return Int(hash) @@ -59,7 +59,7 @@ func djb2(x: String) -> Int { func sdbm(x: String) -> Int { var hash = 0 - for char in x.characters { + for char in x { hash = char.hashValue &+ (hash << 6) &+ (hash << 16) &- hash } return Int(hash) @@ -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/Bloom Filter/BloomFilter.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bloom Filter/BloomFilter.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bloom Filter/BloomFilter.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Bloom Filter/BloomFilter.playground/timeline.xctimeline b/Bloom Filter/BloomFilter.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Bloom Filter/BloomFilter.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Bloom Filter/README.markdown b/Bloom Filter/README.markdown index 3fff0a379..5f2ede069 100644 --- a/Bloom Filter/README.markdown +++ b/Bloom Filter/README.markdown @@ -100,4 +100,31 @@ public func query(_ value: T) -> Bool { If you're coming from another imperative language, you might notice the unusual syntax in the `exists` assignment. Swift makes use of functional paradigms when it makes code more consise and readable, and in this case `reduce` is a much more consise way to check if all the required bits are `true` than a `for` loop. -*Written for Swift Algorithm Club by Jamil Dhanani. Edited by Matthijs Hollemans.* +## Another approach + +In the previous section, you learnt about how using multiple different hash functions can help reduce the chance of collisions in the bloom filter. However, good hash functions are difficult to design. A simple alternative to multiple hash functions is to use a set of random numbers. + +As an example, let's say a bloom filter wants to hash each element 15 times during insertion. Instead of using 15 different hash functions, you can rely on just one hash function. The hash value can then be combined with 15 different values to form the indices for flipping. This bloom filter would initialize a set of 15 random numbers ahead of time and use these values during each insertion. + +``` +hash("Hello world!") >> hash(987654321) // would flip bit 8 +hash("Hello world!") >> hash(123456789) // would flip bit 2 +``` + +Since Swift 4.2, `Hasher` is now included in the Standard library, which is designed to reduce multiple hashes to a single hash in an efficient manner. This makes combining the hashes trivial. + +``` +private func computeHashes(_ value: T) -> [Int] { + return randomSeeds.map() { seed in + let hasher = Hasher() + hasher.combine(seed) + hasher.combine(value) + let hashValue = hasher.finalize() + return abs(hashValue % array.count) + } +} +``` + +If you want to learn more about this approach, you can read about the [Hasher documentation](https://developer.apple.com/documentation/swift/hasher) or Soroush Khanlou's [Swift 4.2 Bloom filter](http://khanlou.com/2018/09/bloom-filters/) implementation. + +*Written for Swift Algorithm Club by Jamil Dhanani. Edited by Matthijs Hollemans. Updated by Bruno Scheele.* diff --git a/Bloom Filter/Tests/BloomFilterTests.swift b/Bloom Filter/Tests/BloomFilterTests.swift index 55db71f3f..d88ec0a31 100644 --- a/Bloom Filter/Tests/BloomFilterTests.swift +++ b/Bloom Filter/Tests/BloomFilterTests.swift @@ -6,7 +6,7 @@ import XCTest func djb2(_ x: String) -> Int { var hash = 5381 - for char in x.characters { + for char in x { hash = ((hash << 5) &+ hash) &+ char.hashValue } @@ -16,7 +16,7 @@ func djb2(_ x: String) -> Int { func sdbm(_ x: String) -> Int { var hash = 0 - for char in x.characters { + for char in x { hash = char.hashValue &+ (hash << 6) &+ (hash << 16) &- hash } @@ -24,7 +24,12 @@ func sdbm(_ x: String) -> Int { } class BloomFilterTests: XCTestCase { - + func testSwift4(){ + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } func testSingleHashFunction() { let bloom = BloomFilter(hashFunctions: [djb2]) diff --git a/Bloom Filter/Tests/Tests.xcodeproj/project.pbxproj b/Bloom Filter/Tests/Tests.xcodeproj/project.pbxproj index 20998a80e..f0649ba26 100644 --- a/Bloom Filter/Tests/Tests.xcodeproj/project.pbxproj +++ b/Bloom Filter/Tests/Tests.xcodeproj/project.pbxproj @@ -83,12 +83,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 1000; }; }; }; @@ -141,14 +141,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; @@ -176,6 +184,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -187,14 +196,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; @@ -215,6 +232,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -226,7 +244,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 = 4.2; }; name = Debug; }; @@ -238,7 +256,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 = 4.2; }; name = Release; }; diff --git a/Bloom Filter/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bloom Filter/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bloom Filter/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Bloom Filter/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Bloom Filter/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 14f27f777..afd69e6a7 100644 --- a/Bloom Filter/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Bloom Filter/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/Bounded Priority Queue/BoundedPriorityQueue.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bounded Priority Queue/BoundedPriorityQueue.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bounded Priority Queue/BoundedPriorityQueue.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj b/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj index 842c51695..11f907b47 100644 --- a/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj +++ b/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj @@ -86,7 +86,7 @@ TargetAttributes = { B80004B21C83E342001FE2D7 = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 0820; + LastSwiftMigration = 1000; }; }; }; @@ -226,7 +226,7 @@ SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -269,7 +269,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Bounded Priority Queue/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bounded Priority Queue/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bounded Priority Queue/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Boyer-Moore/BoyerMoore.playground/Contents.swift b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/Contents.swift similarity index 94% rename from Boyer-Moore/BoyerMoore.playground/Contents.swift rename to Boyer-Moore-Horspool/BoyerMooreHorspool.playground/Contents.swift index f553e1a00..933cbc4ac 100644 --- a/Boyer-Moore/BoyerMoore.playground/Contents.swift +++ b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/Contents.swift @@ -11,13 +11,13 @@ extension String { func index(of pattern: String, usingHorspoolImprovement: Bool = false) -> Index? { // Cache the length of the search pattern because we're going to // use it a few times and it's expensive to calculate. - let patternLength = pattern.characters.count - guard patternLength > 0, patternLength <= characters.count else { return nil } + let patternLength = pattern.count + guard patternLength > 0, patternLength <= self.count else { return nil } // Make the skip table. This table determines how far we skip ahead // when a character from the pattern is found. var skipTable = [Character: Int]() - for (i, c) in pattern.characters.enumerated() { + for (i, c) in pattern.enumerated() { skipTable[c] = patternLength - i - 1 } diff --git a/Boyer-Moore/BoyerMoore.playground/contents.xcplayground b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/contents.xcplayground similarity index 100% rename from Boyer-Moore/BoyerMoore.playground/contents.xcplayground rename to Boyer-Moore-Horspool/BoyerMooreHorspool.playground/contents.xcplayground diff --git a/Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/contents.xcworkspacedata b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/playground.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Fizz Buzz/FizzBuzz.playground/playground.xcworkspace/contents.xcworkspacedata rename to Boyer-Moore-Horspool/BoyerMooreHorspool.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/timeline.xctimeline b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/timeline.xctimeline new file mode 100644 index 000000000..2688d72c1 --- /dev/null +++ b/Boyer-Moore-Horspool/BoyerMooreHorspool.playground/timeline.xctimeline @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/Boyer-Moore/BoyerMoore.swift b/Boyer-Moore-Horspool/BoyerMooreHorspool.swift similarity index 94% rename from Boyer-Moore/BoyerMoore.swift rename to Boyer-Moore-Horspool/BoyerMooreHorspool.swift index af8aea9d9..481b7d483 100644 --- a/Boyer-Moore/BoyerMoore.swift +++ b/Boyer-Moore-Horspool/BoyerMooreHorspool.swift @@ -9,13 +9,13 @@ extension String { func index(of pattern: String, usingHorspoolImprovement: Bool = false) -> Index? { // Cache the length of the search pattern because we're going to // use it a few times and it's expensive to calculate. - let patternLength = pattern.characters.count - guard patternLength > 0, patternLength <= characters.count else { return nil } - + let patternLength = pattern.count + guard patternLength > 0, patternLength <= self.count else { return nil } + // Make the skip table. This table determines how far we skip ahead // when a character from the pattern is found. var skipTable = [Character: Int]() - for (i, c) in pattern.characters.enumerated() { + for (i, c) in pattern.enumerated() { skipTable[c] = patternLength - i - 1 } diff --git a/Boyer-Moore/README.markdown b/Boyer-Moore-Horspool/README.markdown similarity index 96% rename from Boyer-Moore/README.markdown rename to Boyer-Moore-Horspool/README.markdown index 5b3841eca..8879d8df6 100644 --- a/Boyer-Moore/README.markdown +++ b/Boyer-Moore-Horspool/README.markdown @@ -1,5 +1,8 @@ # Boyer-Moore String Search +> This topic has been tutorialized [here](https://www.raywenderlich.com/163964/swift-algorithm-club-booyer-moore-string-search-algorithm) + + Goal: Write a string search algorithm in pure Swift without importing Foundation or using `NSString`'s `rangeOfString()` method. In other words, we want to implement an `indexOf(pattern: String)` extension on `String` that returns the `String.Index` of the first occurrence of the search pattern, or `nil` if the pattern could not be found inside the string. @@ -35,13 +38,13 @@ extension String { func index(of pattern: String) -> Index? { // Cache the length of the search pattern because we're going to // use it a few times and it's expensive to calculate. - let patternLength = pattern.characters.count - guard patternLength > 0, patternLength <= characters.count else { return nil } + let patternLength = pattern.count + guard patternLength > 0, patternLength <= count else { return nil } // Make the skip table. This table determines how far we skip ahead // when a character from the pattern is found. var skipTable = [Character: Int]() - for (i, c) in pattern.characters.enumerated() { + for (i, c) in pattern.enumerated() { skipTable[c] = patternLength - i - 1 } @@ -159,13 +162,13 @@ extension String { func index(of pattern: String) -> Index? { // Cache the length of the search pattern because we're going to // use it a few times and it's expensive to calculate. - let patternLength = pattern.characters.count + let patternLength = pattern.count guard patternLength > 0, patternLength <= characters.count else { return nil } // Make the skip table. This table determines how far we skip ahead // when a character from the pattern is found. var skipTable = [Character: Int]() - for (i, c) in pattern.characters.enumerated() { + for (i, c) in pattern.enumerated() { skipTable[c] = patternLength - i - 1 } diff --git a/Boyer-Moore/Tests/BoyerMooreHorspoolTests.swift b/Boyer-Moore-Horspool/Tests/BoyerMooreHorspoolTests.swift similarity index 100% rename from Boyer-Moore/Tests/BoyerMooreHorspoolTests.swift rename to Boyer-Moore-Horspool/Tests/BoyerMooreHorspoolTests.swift diff --git a/Boyer-Moore/Tests/BoyerMooreTests.swift b/Boyer-Moore-Horspool/Tests/BoyerMooreTests.swift similarity index 94% rename from Boyer-Moore/Tests/BoyerMooreTests.swift rename to Boyer-Moore-Horspool/Tests/BoyerMooreTests.swift index 9efe60874..436173127 100755 --- a/Boyer-Moore/Tests/BoyerMooreTests.swift +++ b/Boyer-Moore-Horspool/Tests/BoyerMooreTests.swift @@ -26,8 +26,8 @@ class BoyerMooreTest: XCTestCase { XCTAssertNotNil(index) let startIndex = index! - let endIndex = string.index(index!, offsetBy: pattern.characters.count) - let match = string.substring(with: startIndex.. + + + + 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/Boyer-Moore/Tests/Tests.xcodeproj/project.pbxproj b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.pbxproj similarity index 86% rename from Boyer-Moore/Tests/Tests.xcodeproj/project.pbxproj rename to Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.pbxproj index c4a005163..d29ac95f5 100644 --- a/Boyer-Moore/Tests/Tests.xcodeproj/project.pbxproj +++ b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 7B80C3CE1C77A256003CECC7 /* BoyerMooreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B80C3CD1C77A256003CECC7 /* BoyerMooreTests.swift */; }; - 9AED567C1E0ED6DE00958DCC /* BoyerMoore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AED567B1E0ED6DE00958DCC /* BoyerMoore.swift */; }; + 8ED7591C1F9A064A00440D89 /* BoyerMooreHorspool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ED7591B1F9A064A00440D89 /* BoyerMooreHorspool.swift */; }; 9AED56801E0EE60B00958DCC /* BoyerMooreHorspoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AED567F1E0EE60B00958DCC /* BoyerMooreHorspoolTests.swift */; }; /* End PBXBuildFile section */ @@ -16,7 +16,7 @@ 7B2BBC801C779D720067B71D /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7B2BBC941C779E7B0067B71D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; 7B80C3CD1C77A256003CECC7 /* BoyerMooreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoyerMooreTests.swift; sourceTree = SOURCE_ROOT; }; - 9AED567B1E0ED6DE00958DCC /* BoyerMoore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoyerMoore.swift; path = ../../BoyerMoore.swift; sourceTree = ""; }; + 8ED7591B1F9A064A00440D89 /* BoyerMooreHorspool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoyerMooreHorspool.swift; path = ../BoyerMooreHorspool.swift; sourceTree = ""; }; 9AED567F1E0EE60B00958DCC /* BoyerMooreHorspoolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoyerMooreHorspoolTests.swift; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -34,7 +34,10 @@ 7B2BBC681C779D710067B71D = { isa = PBXGroup; children = ( - 7B2BBC831C779D720067B71D /* Tests */, + 8ED7591B1F9A064A00440D89 /* BoyerMooreHorspool.swift */, + 7B80C3CD1C77A256003CECC7 /* BoyerMooreTests.swift */, + 7B2BBC941C779E7B0067B71D /* Info.plist */, + 9AED567F1E0EE60B00958DCC /* BoyerMooreHorspoolTests.swift */, 7B2BBC721C779D710067B71D /* Products */, ); sourceTree = ""; @@ -47,18 +50,6 @@ name = Products; sourceTree = ""; }; - 7B2BBC831C779D720067B71D /* Tests */ = { - isa = PBXGroup; - children = ( - 9AED567B1E0ED6DE00958DCC /* BoyerMoore.swift */, - 7B80C3CD1C77A256003CECC7 /* BoyerMooreTests.swift */, - 7B2BBC941C779E7B0067B71D /* Info.plist */, - 9AED567F1E0EE60B00958DCC /* BoyerMooreHorspoolTests.swift */, - ); - name = Tests; - path = TestsTests; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -87,12 +78,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = ""; }; }; }; @@ -145,8 +136,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8ED7591C1F9A064A00440D89 /* BoyerMooreHorspool.swift in Sources */, 9AED56801E0EE60B00958DCC /* BoyerMooreHorspoolTests.swift in Sources */, - 9AED567C1E0ED6DE00958DCC /* BoyerMoore.swift in Sources */, 7B80C3CE1C77A256003CECC7 /* BoyerMooreTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -162,14 +153,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; @@ -197,6 +196,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; }; name = Debug; }; @@ -208,14 +208,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; @@ -236,6 +244,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; }; name = Release; }; @@ -247,7 +256,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 = 4.2; }; name = Debug; }; @@ -259,7 +268,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 = 4.2; }; name = Release; }; diff --git a/Quicksort/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Quicksort/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Boyer-Moore/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from Boyer-Moore/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to Boyer-Moore-Horspool/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/Boyer-Moore/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme similarity index 99% rename from Boyer-Moore/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme rename to Boyer-Moore-Horspool/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 14f27f777..afd69e6a7 100644 --- a/Boyer-Moore/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Boyer-Moore-Horspool/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ - - - - - - - - - - diff --git a/Breadth-First Search/BreadthFirstSearch.playground/Pages/Simple example.xcplaygroundpage/Contents.swift b/Breadth-First Search/BreadthFirstSearch.playground/Pages/Simple example.xcplaygroundpage/Contents.swift index 675be2203..cf22ebb25 100644 --- a/Breadth-First Search/BreadthFirstSearch.playground/Pages/Simple example.xcplaygroundpage/Contents.swift +++ b/Breadth-First Search/BreadthFirstSearch.playground/Pages/Simple example.xcplaygroundpage/Contents.swift @@ -1,3 +1,4 @@ + func breadthFirstSearch(_ graph: Graph, source: Node) -> [String] { var queue = Queue() queue.enqueue(source) diff --git a/Breadth-First Search/BreadthFirstSearch.playground/Sources/Graph.swift b/Breadth-First Search/BreadthFirstSearch.playground/Sources/Graph.swift index 87e21897c..0343120f8 100644 --- a/Breadth-First Search/BreadthFirstSearch.playground/Sources/Graph.swift +++ b/Breadth-First Search/BreadthFirstSearch.playground/Sources/Graph.swift @@ -5,7 +5,7 @@ public class Graph: CustomStringConvertible, Equatable { self.nodes = [] } - public func addNode(_ label: String) -> Node { + @discardableResult public func addNode(_ label: String) -> Node { let node = Node(label) nodes.append(node) return node diff --git a/Breadth-First Search/BreadthFirstSearch.playground/contents.xcplayground b/Breadth-First Search/BreadthFirstSearch.playground/contents.xcplayground index 513c2e7e9..f635e9804 100644 --- a/Breadth-First Search/BreadthFirstSearch.playground/contents.xcplayground +++ b/Breadth-First Search/BreadthFirstSearch.playground/contents.xcplayground @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Breadth-First Search/BreadthFirstSearch.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Breadth-First Search/BreadthFirstSearch.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Breadth-First Search/BreadthFirstSearch.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Breadth-First Search/README.markdown b/Breadth-First Search/README.markdown index 2ef92fd73..097f4278d 100644 --- a/Breadth-First Search/README.markdown +++ b/Breadth-First Search/README.markdown @@ -1,5 +1,8 @@ # Breadth-First Search +> This topic has been tutorialized [here](https://www.raywenderlich.com/155801/swift-algorithm-club-swift-breadth-first-search) + + Breadth-first search (BFS) is an algorithm for traversing or searching [tree](../Tree/) or [graph](../Graph/) data structures. It starts at a source node and explores the immediate neighbor nodes first, before moving to the next level neighbors. Breadth-first search can be used on both directed and undirected graphs. @@ -148,7 +151,7 @@ This will output: `["a", "b", "c", "d", "e", "f", "g", "h"]` Breadth-first search can be used to solve many problems. A small selection: -* Computing the [shortest path](../Shortest%20Path/) between a source node and each of the other nodes (only for unweighted graphs). +* Computing the [shortest path](../Shortest%20Path%20(Unweighted)/) between a source node and each of the other nodes (only for unweighted graphs). * Calculating the [minimum spanning tree](../Minimum%20Spanning%20Tree%20(Unweighted)/) on an unweighted graph. *Written by [Chris Pilcher](https://github.com/chris-pilcher) and Matthijs Hollemans* diff --git a/Breadth-First Search/Tests/Tests.xcodeproj/project.pbxproj b/Breadth-First Search/Tests/Tests.xcodeproj/project.pbxproj index f86abf86e..a7f04e00a 100644 --- a/Breadth-First Search/Tests/Tests.xcodeproj/project.pbxproj +++ b/Breadth-First Search/Tests/Tests.xcodeproj/project.pbxproj @@ -59,7 +59,6 @@ 7B2BBC941C779E7B0067B71D /* Info.plist */, ); name = Tests; - path = TestsTests; sourceTree = ""; }; /* End PBXGroup section */ @@ -89,12 +88,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0820; + LastSwiftMigration = 1000; }; }; }; @@ -149,13 +148,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -193,13 +202,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -218,6 +237,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; @@ -231,7 +251,7 @@ PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -244,7 +264,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 = 4.2; }; name = Release; }; diff --git a/Breadth-First Search/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Breadth-First Search/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Breadth-First Search/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Breadth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Breadth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index dfcf6de42..afd69e6a7 100644 --- a/Breadth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Breadth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ =4.0) +print("Hello, Swift 4!") +#endif + extension String { func indexOf(_ pattern: String) -> String.Index? { diff --git a/Brute-Force String Search/README.markdown b/Brute-Force String Search/README.markdown index 4e967bcfb..599fe75ea 100644 --- a/Brute-Force String Search/README.markdown +++ b/Brute-Force String Search/README.markdown @@ -51,6 +51,6 @@ extension String { This looks at each character in the source string in turn. If the character equals the first character of the search pattern, then the inner loop checks whether the rest of the pattern matches. If no match is found, the outer loop continues where it left off. This repeats until a complete match is found or the end of the source string is reached. -The brute-force approach works OK, but it's not very efficient (or pretty). It should work fine on small strings, though. For a smarter algorithm that works better with large chunks of text, check out [Boyer-Moore](../Boyer-Moore/) string search. +The brute-force approach works OK, but it's not very efficient (or pretty). It should work fine on small strings, though. For a smarter algorithm that works better with large chunks of text, check out [Boyer-Moore](../Boyer-Moore-Horspool) string search. *Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Bubble Sort/MyPlayground.playground/Contents.swift b/Bubble Sort/MyPlayground.playground/Contents.swift new file mode 100644 index 000000000..6188a0763 --- /dev/null +++ b/Bubble Sort/MyPlayground.playground/Contents.swift @@ -0,0 +1,8 @@ +import Foundation + +var array = [4,2,1,3] + +print("before:",array) +print("after:", bubbleSort(array)) +print("after:", bubbleSort(array, <)) +print("after:", bubbleSort(array, >)) diff --git a/Bubble Sort/MyPlayground.playground/Sources/BubbleSort.swift b/Bubble Sort/MyPlayground.playground/Sources/BubbleSort.swift new file mode 100644 index 000000000..09e01542b --- /dev/null +++ b/Bubble Sort/MyPlayground.playground/Sources/BubbleSort.swift @@ -0,0 +1,46 @@ +// +// BubbleSort.swift +// +// Created by Julio Brazil on 1/10/18. +// +// 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 + +/// Performs the bubble sort algorithm in the array +/// +/// - Parameter elements: a array of elements that implement the Comparable protocol +/// - Returns: an array with the same elements but in order +public func bubbleSort (_ elements: [T]) -> [T] where T: Comparable { + return bubbleSort(elements, <) +} + +public func bubbleSort (_ elements: [T], _ comparison: (T,T) -> Bool) -> [T] { + var array = elements + + for i in 0.. + + + \ No newline at end of file diff --git a/Ordered Set/OrderedSet.playground/playground.xcworkspace/contents.xcworkspacedata b/Bubble Sort/MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Ordered Set/OrderedSet.playground/playground.xcworkspace/contents.xcworkspacedata rename to Bubble Sort/MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata diff --git a/Bubble Sort/MyPlayground.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bubble Sort/MyPlayground.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bubble Sort/MyPlayground.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Bubble Sort/README.markdown b/Bubble Sort/README.markdown index 93d370827..211a5aefa 100644 --- a/Bubble Sort/README.markdown +++ b/Bubble Sort/README.markdown @@ -1,6 +1,6 @@ # Bubble Sort -Bubble sort is a sorting algorithm that is implemented by starting in the beginning of the array and swapping the first two elements only if the first element is greater than the second element. This comparison is then moved onto the next pair and so on and so forth. This is done until the array is completely sorted. The smaller items slowly “bubble” up to the beginning of the array. +Bubble sort is a sorting algorithm that is implemented by starting in the beginning of the array and swapping the first two elements only if the first element is greater than the second element. This comparison is then moved onto the next pair and so on and so forth. This is done until the array is completely sorted. The smaller items slowly “bubble” up to the beginning of the array. Sometimes this algorithm is refered as Sinking sort, due to the larger, or heavier elements sinking to the end of the array. ##### Runtime: - Average: O(N^2) @@ -12,3 +12,116 @@ Bubble sort is a sorting algorithm that is implemented by starting in the beginn ### Implementation: The implementation will not be shown as the average and worst runtimes show that this is a very inefficient algorithm. However, having a grasp of the concept will help you understand the basics of simple sorting algorithms. + +Bubble sort is a very simple sorting algorithm, it consists in comparing pairs of adjacent elements in the array, if the first element is larger, swap them, otherwise, you do nothing and go for the next comparison. +This is accomplished by looking through the array `n` times, `n` being the amount of elements in the array. + +![animated gif of the bubble sort algorithm](https://s3.amazonaws.com/codecademy-content/programs/tdd-js/articles/BubbleSort.gif) + +This GIF shows a inverted implementation than + +#### Example +Let us take the array `[5, 1, 4, 2, 8]`, and sort the array from lowest number to greatest number using bubble sort. In each step, elements written in bold are being compared. Three passes will be required. + +##### First Pass +[ **5 1** 4 2 8 ] -> [ **1 5** 4 2 8 ], Here, algorithm compares the first two elements, and swaps since 5 > 1. + +[ 1 **5 4** 2 8 ] -> [ 1 **4 5** 2 8 ], Swap since 5 > 4 + +[ 1 4 **5 2** 8 ] -> [ 1 4 **2 5** 8 ], Swap since 5 > 2 + +[ 1 4 2 **5 8** ] -> [ 1 4 2 **5 8** ], Now, since these elements are already in order (8 > 5), algorithm does not swap them. + +##### Second Pass +[ **1 4** 2 5 8 ] -> [ **1 4** 2 5 8 ] + +[ 1 **4 2** 5 8 ] -> [ 1 **2 4** 5 8 ], Swap since 4 > 2 + +[ 1 2 **4 5** 8 ] -> [ 1 2 **4 5** 8 ] + +[ 1 2 4 **5 8** ] -> [ 1 2 4 **5 8** ] +Now, the array is already sorted, but the algorithm does not know if it is completed. The algorithm needs one whole pass without any swap to know it is sorted. + +##### Third Pass +[ **1 2** 4 5 8 ] -> [ **1 2** 4 5 8 ] + +[ 1 **2 4** 5 8 ] -> [ 1 **2 4** 5 8 ] + +[ 1 2 **4 5** 8 ] -> [ 1 2 **4 5** 8 ] + +[ 1 2 4 **5 8** ] -> [ 1 2 4 **5 8** ] + +This is the same for the forth and fifth passes. + +#### Code +```swift +for i in 0.. [ **1 5** 4 2 8 ], Swaps since 5 > 1 + +[ 1 **5 4** 2 8 ] -> [ 1 **4 5** 2 8 ], Swap since 5 > 4 + +[ 1 4 **5 2** 8 ] -> [ 1 4 **2 5** 8 ], Swap since 5 > 2 + +[ 1 4 2 **5 8** ] -> [ 1 4 2 **5 8** ], Now, since these elements are already in order (8 > 5), algorithm does not swap them. + +*by the end of the first pass, the last element is guaranteed to be the largest* + +##### Second Pass +[ **1 4** 2 5 8 ] -> [ **1 4** 2 5 8 ] + +[ 1 **4 2** 5 8 ] -> [ 1 **2 4** 5 8 ], Swap since 4 > 2 + +[ 1 2 **4 5** 8 ] -> [ 1 2 **4 5** 8 ], As the first loop has occured once, the inner loop stops here, not comparing 5 with 8 + +##### Third Pass +[ **1 2** 4 5 8 ] -> [ **1 2** 4 5 8 ] + +[ 1 **2 4** 5 8 ] -> [ 1 **2 4** 5 8 ] again, stoping one comparison short + +##### Fourth Pass +[ **1 2** 4 5 8 ] -> [ **1 2** 4 5 8 ] + +There is no Fifth pass + +#### Conclusion + +Even with the proposed optimizations, this is still a terribly inefficient sorting algorithm. A good alternative is [Merge Sort](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Merge%20Sort), that not only is better performing, has a similar degree of dificulty to implement. + +*Updated for the Swift Algorithm Club by Julio Brazil* + +##### Supporting Links +[Code Pumpkin](https://codepumpkin.com/bubble-sort/) +[Wikipedia](https://en.wikipedia.org/wiki/Bubble_sort) +[GeeksforGeeks](https://www.geeksforgeeks.org/bubble-sort/) diff --git a/Bucket Sort/BucketSort.playground/Contents.swift b/Bucket Sort/BucketSort.playground/Contents.swift index c111c3961..e862f8bb1 100644 --- a/Bucket Sort/BucketSort.playground/Contents.swift +++ b/Bucket Sort/BucketSort.playground/Contents.swift @@ -34,9 +34,9 @@ extension Int: IntConvertible, Sortable { // MARK: Playing code ////////////////////////////////////// -let input = [1, 2, 4, 6, 10] +let input = [1, 2, 4, 6, 10, 5] var buckets = [Bucket(capacity: 15), Bucket(capacity: 15), Bucket(capacity: 15)] -let sortedElements = bucketSort(elements: input, distributor: RangeDistributor(), sorter: InsertionSorter(), buckets: &buckets) +let sortedElements = bucketSort(input, distributor: RangeDistributor(), sorter: InsertionSorter(), buckets: buckets) print(sortedElements) diff --git a/Bucket Sort/BucketSort.playground/Sources/BucketSort.swift b/Bucket Sort/BucketSort.playground/Sources/BucketSort.swift index f16db3e67..64408eca4 100644 --- a/Bucket Sort/BucketSort.playground/Sources/BucketSort.swift +++ b/Bucket Sort/BucketSort.playground/Sources/BucketSort.swift @@ -20,24 +20,53 @@ // // -import Foundation - ////////////////////////////////////// // MARK: Main algorithm ////////////////////////////////////// -public func bucketSort(elements: [T], distributor: Distributor, sorter: Sorter, buckets: inout [Bucket]) -> [T] { - for elem in elements { - distributor.distribute(element: elem, buckets: &buckets) - } +/** + Performs bucket sort algorithm on the given input elements. + [Bucket Sort Algorithm Reference](https://en.wikipedia.org/wiki/Bucket_sort) + + - Parameter elements: Array of Sortable elements + - Parameter distributor: Performs the distribution of each element of a bucket + - Parameter sorter: Performs the sorting inside each bucket, after all the elements are distributed + - Parameter buckets: An array of buckets + + - Returns: A new array with sorted elements + */ - var results = [T]() +public func bucketSort(_ elements: [T], distributor: Distributor, sorter: Sorter, buckets: [Bucket]) -> [T] { + precondition(allPositiveNumbers(elements)) + precondition(enoughSpaceInBuckets(buckets, elements: elements)) + + var bucketsCopy = buckets + for elem in elements { + distributor.distribute(elem, buckets: &bucketsCopy) + } + + var results = [T]() + + for bucket in bucketsCopy { + results += bucket.sort(sorter) + } + + return results +} - for bucket in buckets { - results += bucket.sort(algorithm: sorter) - } +private func allPositiveNumbers(_ array: [T]) -> Bool { + return array.filter { $0.toInt() >= 0 }.count > 0 +} - return results +private func enoughSpaceInBuckets(_ buckets: [Bucket], elements: [T]) -> Bool { + let maximumValue = elements.max()?.toInt() + let totalCapacity = buckets.count * (buckets.first?.capacity)! + + guard let max = maximumValue else { + return false + } + + return totalCapacity >= max } ////////////////////////////////////// @@ -45,7 +74,7 @@ public func bucketSort(elements: [T], distributor: Distributor, sor ////////////////////////////////////// public protocol Distributor { - func distribute(element: T, buckets: inout [Bucket]) + func distribute(_ element: T, buckets: inout [Bucket]) } /* @@ -64,16 +93,16 @@ public protocol Distributor { * By following the formula: element / capacity = #ofBucket */ public struct RangeDistributor: Distributor { - - public init() {} - - public func distribute(element: T, buckets: inout [Bucket]) { - let value = element.toInt() - let bucketCapacity = buckets.first!.capacity - - let bucketIndex = value / bucketCapacity - buckets[bucketIndex].add(item: element) - } + + public init() {} + + public func distribute(_ element: T, buckets: inout [Bucket]) { + let value = element.toInt() + let bucketCapacity = buckets.first!.capacity + + let bucketIndex = value / bucketCapacity + buckets[bucketIndex].add(element) + } } ////////////////////////////////////// @@ -81,7 +110,7 @@ public struct RangeDistributor: Distributor { ////////////////////////////////////// public protocol IntConvertible { - func toInt() -> Int + func toInt() -> Int } public protocol Sortable: IntConvertible, Comparable { @@ -92,28 +121,28 @@ public protocol Sortable: IntConvertible, Comparable { ////////////////////////////////////// public protocol Sorter { - func sort(items: [T]) -> [T] + func sort(_ items: [T]) -> [T] } public struct InsertionSorter: Sorter { - - public init() {} - - public func sort(items: [T]) -> [T] { - var results = items - for i in 0 ..< results.count { - var j = i - while j > 0 && results[j-i] > results[j] { - - let auxiliar = results[i] - results[i] = results[j] - results[j] = auxiliar - - j -= 1 - } + + public init() {} + + public func sort(_ items: [T]) -> [T] { + var results = items + for i in 0 ..< results.count { + var j = i + while j > 0 && results[j-1] > results[j] { + + let auxiliar = results[j-1] + results[j-1] = results[j] + results[j] = auxiliar + + j -= 1 + } + } + return results } - return results - } } ////////////////////////////////////// @@ -121,21 +150,22 @@ public struct InsertionSorter: Sorter { ////////////////////////////////////// public struct Bucket { - var elements: [T] - let capacity: Int - - public init(capacity: Int) { - self.capacity = capacity - elements = [T]() - } - - public mutating func add(item: T) { - if elements.count < capacity { - elements.append(item) + var elements: [T] + let capacity: Int + + public init(capacity: Int) { + self.capacity = capacity + elements = [T]() + } + + public mutating func add(_ item: T) { + if elements.count < capacity { + elements.append(item) + } + } + + public func sort(_ algorithm: Sorter) -> [T] { + return algorithm.sort(elements) } - } - - public func sort(algorithm: Sorter) -> [T] { - return algorithm.sort(items: elements) - } } + diff --git a/Bucket Sort/BucketSort.playground/contents.xcplayground b/Bucket Sort/BucketSort.playground/contents.xcplayground index 5da2641c9..9f5f2f40c 100644 --- a/Bucket Sort/BucketSort.playground/contents.xcplayground +++ b/Bucket Sort/BucketSort.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Bucket Sort/BucketSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bucket Sort/BucketSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bucket Sort/BucketSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Bucket Sort/BucketSort.swift b/Bucket Sort/BucketSort.swift index f2aeaae99..4c2abd372 100644 --- a/Bucket Sort/BucketSort.swift +++ b/Bucket Sort/BucketSort.swift @@ -20,31 +20,6 @@ // // -import Foundation -// FIXME: comparison operators with optionals were removed from the Swift Standard Libary. -// Consider refactoring the code to use the non-optional operators. -fileprivate func < (lhs: T?, rhs: T?) -> Bool { - switch (lhs, rhs) { - case let (l?, r?): - return l < r - case (nil, _?): - return true - default: - return false - } -} - -// FIXME: comparison operators with optionals were removed from the Swift Standard Libary. -// Consider refactoring the code to use the non-optional operators. -fileprivate func >= (lhs: T?, rhs: T?) -> Bool { - switch (lhs, rhs) { - case let (l?, r?): - return l >= r - default: - return !(lhs < rhs) - } -} - ////////////////////////////////////// // MARK: Main algorithm ////////////////////////////////////// @@ -61,7 +36,7 @@ fileprivate func >= (lhs: T?, rhs: T?) -> Bool { - Returns: A new array with sorted elements */ -public func bucketSort(_ elements: [T], distributor: Distributor, sorter: Sorter, buckets: [Bucket]) -> [T] { +public func bucketSort(_ elements: [T], distributor: Distributor, sorter: Sorter, buckets: [Bucket]) -> [T] { precondition(allPositiveNumbers(elements)) precondition(enoughSpaceInBuckets(buckets, elements: elements)) @@ -83,11 +58,15 @@ private func allPositiveNumbers(_ array: [T]) -> Bool { return array.filter { $0.toInt() >= 0 }.count > 0 } -private func enoughSpaceInBuckets(_ buckets: [Bucket], elements: [T]) -> Bool { +private func enoughSpaceInBuckets(_ buckets: [Bucket], elements: [T]) -> Bool { let maximumValue = elements.max()?.toInt() let totalCapacity = buckets.count * (buckets.first?.capacity)! - return totalCapacity >= maximumValue + guard let max = maximumValue else { + return false + } + + return totalCapacity >= max } ////////////////////////////////////// @@ -95,7 +74,7 @@ private func enoughSpaceInBuckets(_ buckets: [Bucket], elements: ////////////////////////////////////// public protocol Distributor { - func distribute(_ element: T, buckets: inout [Bucket]) + func distribute(_ element: T, buckets: inout [Bucket]) } /* @@ -117,7 +96,7 @@ public struct RangeDistributor: Distributor { public init() {} - public func distribute(_ element: T, buckets: inout [Bucket]) { + public func distribute(_ element: T, buckets: inout [Bucket]) { let value = element.toInt() let bucketCapacity = buckets.first!.capacity diff --git a/Bucket Sort/README.markdown b/Bucket Sort/README.markdown index f572fc9a1..f2708d71e 100644 --- a/Bucket Sort/README.markdown +++ b/Bucket Sort/README.markdown @@ -256,4 +256,4 @@ The following are some of the variation to the general [Bucket Sort](https://en. - [Postman Sort](https://en.wikipedia.org/wiki/Bucket_sort#Postman.27s_sort) - [Shuffle Sort](https://en.wikipedia.org/wiki/Bucket_sort#Shuffle_sort) -*Written for Swift Algorithm Club by Barbara Rodeker. Images from Wikipedia.* +*Written for Swift Algorithm Club by Barbara Rodeker. Images from Wikipedia. Updated by Bruno Scheele.* diff --git a/Bucket Sort/Tests/Tests.xcodeproj/project.pbxproj b/Bucket Sort/Tests/Tests.xcodeproj/project.pbxproj index 64eb170b0..6df507720 100644 --- a/Bucket Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Bucket Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -83,12 +83,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0810; + LastSwiftMigration = 0900; }; }; }; @@ -141,13 +141,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -185,13 +195,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -210,6 +230,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -223,7 +244,8 @@ PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -236,7 +258,8 @@ 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_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Bucket Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Bucket Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Bucket Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Bucket Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Bucket Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 8ef8d8581..afd69e6a7 100644 --- a/Bucket Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Bucket Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ (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/Comb Sort.playground/Contents.swift b/Comb Sort/Comb Sort.playground/Contents.swift index 40176be9f..1f7723838 100644 --- a/Comb Sort/Comb Sort.playground/Contents.swift +++ b/Comb Sort/Comb Sort.playground/Contents.swift @@ -2,7 +2,7 @@ // Created by Stephen Rutstein // 7-16-2016 -import Cocoa +import Foundation // Test Comb Sort with small array of ten values let array = [2, 32, 9, -1, 89, 101, 55, -10, -12, 67] diff --git a/Comb Sort/Comb Sort.playground/Sources/Comb Sort.swift b/Comb Sort/Comb Sort.playground/Sources/Comb Sort.swift index f7c4eec61..559fe47e8 100644 --- a/Comb Sort/Comb Sort.playground/Sources/Comb Sort.swift +++ b/Comb Sort/Comb Sort.playground/Sources/Comb Sort.swift @@ -21,7 +21,7 @@ public func combSort(_ input: [T]) -> [T] { var index = 0 while !(index + gap >= copy.count) { if copy[index] > copy[index + gap] { - swap(©[index], ©[index + gap]) + copy.swapAt(index, index + gap) } index += 1 } diff --git a/Comb Sort/Comb Sort.swift b/Comb Sort/Comb Sort.swift index f7c4eec61..bd37fed42 100644 --- a/Comb Sort/Comb Sort.swift +++ b/Comb Sort/Comb Sort.swift @@ -11,17 +11,17 @@ public func combSort(_ input: [T]) -> [T] { var copy: [T] = input var gap = copy.count let shrink = 1.3 - + while gap > 1 { gap = (Int)(Double(gap) / shrink) if gap < 1 { gap = 1 } - + var index = 0 while !(index + gap >= copy.count) { if copy[index] > copy[index + gap] { - swap(©[index], ©[index + gap]) + copy.swapAt(index, index + gap) } index += 1 } diff --git a/Comb Sort/README.markdown b/Comb Sort/README.markdown index d30d429b3..20c939307 100644 --- a/Comb Sort/README.markdown +++ b/Comb Sort/README.markdown @@ -64,8 +64,8 @@ This will sort the values of the array into ascending order -- increasing in val ## Performance Comb Sort was created to improve upon the worst case time complexity of Bubble Sort. With Comb -Sort, the worst case scenario for performance is exponential -- O(n^2). At best though, Comb Sort -performs at O(n logn) time complexity. This creates a drastic improvement over Bubble Sort's performance. +Sort, the worst case scenario for performance is polynomial -- O(n^2). At best though, Comb Sort +performs at O(n logn) time complexity -- loglinear. This creates a drastic improvement over Bubble Sort's performance. Similar to Bubble Sort, the space complexity for Comb Sort is constant -- O(1). This is extremely space efficient as it sorts the array in place. diff --git a/Comb Sort/Tests/CombSortTests.swift b/Comb Sort/Tests/CombSortTests.swift index b9287f84a..1d83af002 100644 --- a/Comb Sort/Tests/CombSortTests.swift +++ b/Comb Sort/Tests/CombSortTests.swift @@ -9,20 +9,20 @@ import XCTest class CombSortTests: XCTestCase { - var sequence: [Int]! - let expectedSequence: [Int] = [-12, -10, -1, 2, 9, 32, 55, 67, 89, 101] + 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 60039196c..4582b3f2d 100644 --- a/Comb Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Comb Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -82,7 +82,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 056E92741E2483D300B30F52 = { @@ -142,15 +142,23 @@ 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_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_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; @@ -179,6 +187,7 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -191,15 +200,23 @@ 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_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_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; @@ -220,6 +237,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -233,7 +251,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "Swift-Algorithm-Club.Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -246,7 +264,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 = 4.2; }; name = Release; }; diff --git a/Comb Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Comb Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 55e697ad3..48faa2df7 100644 --- a/Comb Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Comb Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ =4.0) +print("Hello, Swift 4!") +#endif + /* Calculates n! */ func factorial(_ n: Int) -> Int { var n = n @@ -44,9 +49,9 @@ func permuteWirth(_ a: [T], _ n: Int) { var a = a permuteWirth(a, n - 1) for i in 0.. - - - - diff --git a/Combinatorics/README.markdown b/Combinatorics/README.markdown index b365b389b..a2432a680 100644 --- a/Combinatorics/README.markdown +++ b/Combinatorics/README.markdown @@ -99,17 +99,17 @@ Here's a recursive algorithm by Niklaus Wirth: ```swift func permuteWirth(_ a: [T], _ n: Int) { - if n == 0 { - print(a) // display the current permutation - } else { - var a = a - permuteWirth(a, n - 1) - for i in 0.. + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Convex Hull/Convex Hull.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Convex Hull/Convex Hull.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme new file mode 100644 index 000000000..8f04a0f51 --- /dev/null +++ b/Convex Hull/Convex Hull.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Convex Hull/Convex Hull/Assets.xcassets/AppIcon.appiconset/Contents.json b/Convex Hull/Convex Hull/Assets.xcassets/AppIcon.appiconset/Contents.json index 1d060ed28..d8db8d65f 100644 --- a/Convex Hull/Convex Hull/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Convex Hull/Convex Hull/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -84,6 +84,11 @@ "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/Convex Hull/Convex Hull/View.swift b/Convex Hull/Convex Hull/View.swift index 2f2c3e046..e75c73ed0 100644 --- a/Convex Hull/Convex Hull/View.swift +++ b/Convex Hull/Convex Hull/View.swift @@ -10,168 +10,169 @@ import UIKit class View: UIView { - let MAX_POINTS = 100 - - var points = [CGPoint]() - - var convexHull = [CGPoint]() - - override init(frame: CGRect) { - super.init(frame: frame) - - generatePoints() - quickHull(points: points) + let MAX_POINTS = 100 + var points = [CGPoint]() + var convexHull = [CGPoint]() + + override init(frame: CGRect) { + super.init(frame: frame) + generateRandomPoints() + quickHull(points: points) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func generateRandomPoints() { + for _ in 0.. Bool in + return a.x < b.x } + } - func generatePoints() { - for _ in 0.. Bool in - return a.x < b.x - } - } - - func quickHull(points: [CGPoint]) { - var pts = points + func quickHull(points: [CGPoint]) { + var pts = points - // Assume points has at least 2 points - // Assume list is ordered on x + // Assume points has at least 2 points + // Assume list is ordered on x - // left most point - let p1 = pts.removeFirst() - // right most point - let p2 = pts.removeLast() + // left most point + let p1 = pts.removeFirst() + // right most point + let p2 = pts.removeLast() - // p1 and p2 are outer most points and thus are part of the hull - convexHull.append(p1) - convexHull.append(p2) + // p1 and p2 are outer most points and thus are part of the hull + convexHull.append(p1) + convexHull.append(p2) - // points to the right of oriented line from p1 to p2 - var s1 = [CGPoint]() + // points to the right of oriented line from p1 to p2 + var s1 = [CGPoint]() - // points to the right of oriented line from p2 to p1 - var s2 = [CGPoint]() + // points to the right of oriented line from p2 to p1 + var s2 = [CGPoint]() - // p1 to p2 line - let lineVec1 = CGPoint(x: p2.x - p1.x, y: p2.y - p1.y) + // p1 to p2 line + let lineVec1 = CGPoint(x: p2.x - p1.x, y: p2.y - p1.y) - for p in pts { // per point check if point is to right or left of p1 to p2 line - let pVec1 = CGPoint(x: p.x - p1.x, y: p.y - p1.y) - let sign1 = lineVec1.x * pVec1.y - pVec1.x * lineVec1.y // cross product to check on which side of the line point p is. + for p in pts { // per point check if point is to right or left of p1 to p2 line + let pVec1 = CGPoint(x: p.x - p1.x, y: p.y - p1.y) + let sign1 = lineVec1.x * pVec1.y - pVec1.x * lineVec1.y // cross product to check on which side of the line point p is. - if sign1 > 0 { // right of p1 p2 line (in a normal xy coordinate system this would be < 0 but due to the weird iPhone screen coordinates this is > 0 - s1.append(p) - } else { // right of p2 p1 line - s2.append(p) - } - } - - // find new hull points - findHull(s1, p1, p2) - findHull(s2, p2, p1) + if sign1 > 0 { // right of p1 p2 line (in a normal xy coordinate system this would be < 0 but due to the weird iPhone screen coordinates this is > 0 + s1.append(p) + } else { // right of p2 p1 line + s2.append(p) + } } - func findHull(_ points: [CGPoint], _ p1: CGPoint, _ p2: CGPoint) { - // if set of points is empty there are no points to the right of this line so this line is part of the hull. - if points.isEmpty { - return - } - - var pts = points + // find new hull points + findHull(s1, p1, p2) + findHull(s2, p2, p1) + } - // calculate parameters of general line equation y = a * x + b - let a = (p1.y - p2.y) / (p1.x - p2.x) - let b = p1.y - a * p1.x - - // calculate normal line's growth factor - let a1 = -1 / a - - var maxDist: CGFloat = -1 - var maxPoint: CGPoint = pts.first! + func findHull(_ points: [CGPoint], _ p1: CGPoint, _ p2: CGPoint) { + // if set of points is empty there are no points to the right of this line so this line is part of the hull. + if points.isEmpty { + return + } - for p in pts { // for every point check the distance from our line - let b1 = p.y - a1 * p.x // calculate offset to line equation for given point p - let x = -(b - b1)/(a - a1) // calculate x where the two lines intersect - let y = a * x + b // calculate y value of this intersect point + var pts = points + var maxDist: CGFloat = -1 + var maxPoint: CGPoint = pts.first! - let dist = pow(x - p.x, 2) + pow(y - p.y, 2) // calculate distance squared between intersection point and point p - if dist > maxDist { // if distance is larger than current maxDist remember new point p - maxDist = dist - maxPoint = p - } - } + for p in pts { // for every point check the distance from our line + let dist = distance(from: p, to: (p1, p2)) + if dist > maxDist { // if distance is larger than current maxDist remember new point p + maxDist = dist + maxPoint = p + } + } - convexHull.insert(maxPoint, at: convexHull.index(of: p1)! + 1) // insert point with max distance from line in the convexHull after p1 + convexHull.insert(maxPoint, at: convexHull.index(of: p1)! + 1) // insert point with max distance from line in the convexHull after p1 - pts.remove(at: pts.index(of: maxPoint)!) // remove maxPoint from points array as we are going to split this array in points left and right of the line + pts.remove(at: pts.index(of: maxPoint)!) // remove maxPoint from points array as we are going to split this array in points left and right of the line - // points to the right of oriented line from p1 to maxPoint - var s1 = [CGPoint]() + // points to the right of oriented line from p1 to maxPoint + var s1 = [CGPoint]() - // points to the right of oriented line from maxPoint to p2 - var s2 = [CGPoint]() + // points to the right of oriented line from maxPoint to p2 + var s2 = [CGPoint]() - // p1 to maxPoint line - let lineVec1 = CGPoint(x: maxPoint.x - p1.x, y: maxPoint.y - p1.y) - // maxPoint to p2 line - let lineVec2 = CGPoint(x: p2.x - maxPoint.x, y: p2.y - maxPoint.y) + // p1 to maxPoint line + let lineVec1 = CGPoint(x: maxPoint.x - p1.x, y: maxPoint.y - p1.y) + // maxPoint to p2 line + let lineVec2 = CGPoint(x: p2.x - maxPoint.x, y: p2.y - maxPoint.y) - for p in pts { - let pVec1 = CGPoint(x: p.x - p1.x, y: p.y - p1.y) // vector from p1 to p - let sign1 = lineVec1.x * pVec1.y - pVec1.x * lineVec1.y // sign to check is p is to the right or left of lineVec1 + for p in pts { + let pVec1 = CGPoint(x: p.x - p1.x, y: p.y - p1.y) // vector from p1 to p + let sign1 = lineVec1.x * pVec1.y - pVec1.x * lineVec1.y // sign to check is p is to the right or left of lineVec1 - let pVec2 = CGPoint(x: p.x - maxPoint.x, y: p.y - maxPoint.y) // vector from p2 to p - let sign2 = lineVec2.x * pVec2.y - pVec2.x * lineVec2.y // sign to check is p is to the right or left of lineVec2 + let pVec2 = CGPoint(x: p.x - maxPoint.x, y: p.y - maxPoint.y) // vector from p2 to p + let sign2 = lineVec2.x * pVec2.y - pVec2.x * lineVec2.y // sign to check is p is to the right or left of lineVec2 - if sign1 > 0 { // right of p1 maxPoint line - s1.append(p) - } else if sign2 > 0 { // right of maxPoint p2 line - s2.append(p) - } - } + if sign1 > 0 { // right of p1 maxPoint line + s1.append(p) + } else if sign2 > 0 { // right of maxPoint p2 line + s2.append(p) + } + } - // find new hull points - findHull(s1, p1, maxPoint) - findHull(s2, maxPoint, p2) + // find new hull points + findHull(s1, p1, maxPoint) + findHull(s2, maxPoint, p2) + } + + func distance(from p: CGPoint, to line: (CGPoint, CGPoint)) -> CGFloat { + // If line.0 and line.1 are the same point, they don't define a line (and, besides, + // would cause division by zero in the distance formula). Return the distance between + // line.0 and point p instead. + if line.0 == line.1 { + return sqrt(pow(p.x - line.0.x, 2) + pow(p.y - line.0.y, 2)) } - override func draw(_ rect: CGRect) { + // from Deza, Michel Marie; Deza, Elena (2013), Encyclopedia of Distances (2nd ed.), Springer, p. 86, ISBN 9783642309588 + return abs((line.1.y - line.0.y) * p.x + - (line.1.x - line.0.x) * p.y + + line.1.x * line.0.y + - line.1.y * line.0.x) + / sqrt(pow(line.1.y - line.0.y, 2) + pow(line.1.x - line.0.x, 2)) + } + + override func draw(_ rect: CGRect) { - let context = UIGraphicsGetCurrentContext() + let context = UIGraphicsGetCurrentContext() - // Draw hull - let lineWidth: CGFloat = 2.0 + // Draw hull + let lineWidth: CGFloat = 2.0 - context!.setFillColor(UIColor.black.cgColor) - context!.setLineWidth(lineWidth) - context!.setStrokeColor(UIColor.red.cgColor) - context!.setFillColor(UIColor.black.cgColor) + context!.setFillColor(UIColor.black.cgColor) + context!.setLineWidth(lineWidth) + context!.setStrokeColor(UIColor.red.cgColor) + context!.setFillColor(UIColor.black.cgColor) - let firstPoint = convexHull.first! - context!.move(to: firstPoint) + let firstPoint = convexHull.first! + context!.move(to: firstPoint) - for p in convexHull.dropFirst() { - context!.addLine(to: p) - } - context!.addLine(to: firstPoint) + for p in convexHull.dropFirst() { + context!.addLine(to: p) + } + context!.addLine(to: firstPoint) - context!.strokePath() + context!.strokePath() - // Draw points - for p in points { - let radius: CGFloat = 5 - let circleRect = CGRect(x: p.x - radius, y: p.y - radius, width: 2 * radius, height: 2 * radius) - context!.fillEllipse(in: circleRect) - } + // Draw points + for p in points { + let radius: CGFloat = 5 + let circleRect = CGRect(x: p.x - radius, y: p.y - radius, width: 2 * radius, height: 2 * radius) + context!.fillEllipse(in: circleRect) } + } } diff --git a/Convex Hull/README.md b/Convex Hull/README.md index 2e1fbe6a3..3036a4138 100644 --- a/Convex Hull/README.md +++ b/Convex Hull/README.md @@ -1,14 +1,23 @@ # Convex Hull -There are multiple Convex Hull algorithms. This particular implementation uses the Quickhull algorithm. +Given a group of points on a plane. The Convex Hull algorithm calculates the shape (made up from the points itself) containing all these points. It can also be used on a collection of points of different dimensions. This implementation however covers points on a plane. It essentially calculates the lines between points which together contain all points. In comparing different solutions to this problem we can describe each algorithm in terms of it's big-O time complexity. -Given a group of points on a plane. The Convex Hull algorithm calculates the shape (made up from the points itself) containing all these points. It can also be used on a collection of points of different dimensions. This implementation however covers points on a plane. It essentially calculates the lines between points which together contain all points. +There are multiple Convex Hull algorithms but this solution is called Quickhull, is comes from the work of both W. Eddy in 1977 and also separately A. Bykat in 1978, this algorithm has an expected time complexity of O(n log n), but it's worst-case time-complexity can be O(n^2) . With average conditions the algorithm has ok efficiency, but it's time-complexity can start to become more exponential in cases of high symmetry or where there are points lying on the circumference of a circle for example. ## Quickhull The quickhull algorithm works as follows: -The algorithm takes an input of a collection of points. These points should be ordered on their x-coordinate value. We pick the two points A and B with the smallest(A) and the largest(B) x-coordinate. These of course have to be part of the hull. Imagine a line from point A to point B. All points to the right of this line are grouped in an array S1. Imagine now a line from point B to point A. (this is of course the same line as before just with opposite direction) Again all points to the right of this line are grouped in an array, S2 this time. -We now define the following recursive function: + +- The algorithm takes an input of a collection of points. These points should be ordered on their x-coordinate value. +- We first find the two points A and B with the minimum(A) and the maximum(B) x-coordinates (as these will obviously be part of the hull). +- Use the line formed by the two points to divide the set in two subsets of points, which will be processed recursively. +- Determine the point, on one side of the line, with the maximum distance from the line. The two points found before along with this one form a triangle. +- The points lying inside of that triangle cannot be part of the convex hull and can therefore be ignored in the next steps. +- Repeat the previous two steps on the two lines formed by the triangle (not the initial line). +- Keep on doing so on until no more points are left, the recursion has come to an end and the points selected constitute the convex hull. + + +Our function will have the following defininition: `findHull(points: [CGPoint], p1: CGPoint, p2: CGPoint)` @@ -18,6 +27,7 @@ findHull(S2, B, A) ``` What this function does is the following: + 1. If `points` is empty we return as there are no points to the right of our line to add to our hull. 2. Draw a line from `p1` to `p2`. 3. Find the point in `points` that is furthest away from this line. (`maxPoint`) diff --git a/Convex Hull/Tests/Info.plist b/Convex Hull/Tests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/Convex Hull/Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Convex Hull/Tests/Tests.swift b/Convex Hull/Tests/Tests.swift new file mode 100644 index 000000000..411424479 --- /dev/null +++ b/Convex Hull/Tests/Tests.swift @@ -0,0 +1,36 @@ +// +// Tests.swift +// Tests +// +// Created by Matthew Nespor on 10/7/17. +// Copyright © 2017 Workmoose. All rights reserved. +// + +import XCTest + +class Tests: XCTestCase { + func testHorizontalInitialLine() { + let view = View() + let excludedPoint = CGPoint(x: 146, y: 284) + let includedPoints = [ + CGPoint(x: 353, y: 22), + CGPoint(x: 22, y: 22), + CGPoint(x: 157, y: 447), + ] + + view.points = [CGPoint]() + view.convexHull = [CGPoint]() + view.points.append(contentsOf: includedPoints) + view.points.append(excludedPoint) + view.points.sort { (a: CGPoint, b: CGPoint) -> Bool in + return a.x < b.x + } + + view.quickHull(points: view.points) + + assert(includedPoints.filter({ view.convexHull.contains($0) }).count == 3, + "\(includedPoints) should have been included") + assert(!view.convexHull.contains(excludedPoint), + "\(excludedPoint) should have been excluded") + } +} diff --git a/Count Occurrences/CountOccurrences.playground/Contents.swift b/Count Occurrences/CountOccurrences.playground/Contents.swift index b4392d501..6ab4c00f3 100644 --- a/Count Occurrences/CountOccurrences.playground/Contents.swift +++ b/Count Occurrences/CountOccurrences.playground/Contents.swift @@ -1,12 +1,11 @@ -//: Playground - noun: a place where people can play -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 @@ -15,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 @@ -29,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) @@ -60,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.playground/timeline.xctimeline b/Count Occurrences/CountOccurrences.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Count Occurrences/CountOccurrences.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - 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/Depth-First Search/DepthFirstSearch.playground/Edge.o b/Depth-First Search/DepthFirstSearch.playground/Edge.o new file mode 100644 index 000000000..ab1e89b3a Binary files /dev/null and b/Depth-First Search/DepthFirstSearch.playground/Edge.o differ diff --git a/Depth-First Search/DepthFirstSearch.playground/Graph.o b/Depth-First Search/DepthFirstSearch.playground/Graph.o new file mode 100644 index 000000000..dde35fb0f Binary files /dev/null and b/Depth-First Search/DepthFirstSearch.playground/Graph.o differ diff --git a/Depth-First Search/DepthFirstSearch.playground/Node.o b/Depth-First Search/DepthFirstSearch.playground/Node.o new file mode 100644 index 000000000..1090b3a9c Binary files /dev/null and b/Depth-First Search/DepthFirstSearch.playground/Node.o differ diff --git a/Depth-First Search/DepthFirstSearch.playground/Pages/Simple Example.xcplaygroundpage/Contents.swift b/Depth-First Search/DepthFirstSearch.playground/Pages/Simple Example.xcplaygroundpage/Contents.swift index 1e32b4bbf..c83f52af8 100644 --- a/Depth-First Search/DepthFirstSearch.playground/Pages/Simple Example.xcplaygroundpage/Contents.swift +++ b/Depth-First Search/DepthFirstSearch.playground/Pages/Simple Example.xcplaygroundpage/Contents.swift @@ -1,13 +1,15 @@ -func depthFirstSearch(_ graph: Graph, source: Node) -> [String] { - var nodesExplored = [source.label] - source.visited = true +// last checked with Xcode 10.1 - for edge in source.neighbors { - if !edge.neighbor.visited { - nodesExplored += depthFirstSearch(graph, source: edge.neighbor) +func depthFirstSearch(_ graph: Graph, source: Node) -> [String] { + var nodesExplored = [source.label] + source.visited = true + + for edge in source.neighbors { + if !edge.neighbor.visited { + nodesExplored += depthFirstSearch(graph, source: edge.neighbor) + } } - } - return nodesExplored + return nodesExplored } let graph = Graph() diff --git a/Depth-First Search/DepthFirstSearch.playground/Sources/Edge.swift b/Depth-First Search/DepthFirstSearch.playground/Sources/Edge.swift index 7c841be30..40e769aae 100644 --- a/Depth-First Search/DepthFirstSearch.playground/Sources/Edge.swift +++ b/Depth-First Search/DepthFirstSearch.playground/Sources/Edge.swift @@ -1,11 +1,11 @@ public class Edge: Equatable { - public var neighbor: Node - - public init(_ neighbor: Node) { - self.neighbor = neighbor - } + public var neighbor: Node + + public init(_ neighbor: Node) { + self.neighbor = neighbor + } } public func == (_ lhs: Edge, rhs: Edge) -> Bool { - return lhs.neighbor == rhs.neighbor + return lhs.neighbor == rhs.neighbor } diff --git a/Depth-First Search/DepthFirstSearch.playground/Sources/Graph.swift b/Depth-First Search/DepthFirstSearch.playground/Sources/Graph.swift index 87e21897c..48b8feed5 100644 --- a/Depth-First Search/DepthFirstSearch.playground/Sources/Graph.swift +++ b/Depth-First Search/DepthFirstSearch.playground/Sources/Graph.swift @@ -1,24 +1,25 @@ public class Graph: CustomStringConvertible, Equatable { public private(set) var nodes: [Node] - + public init() { self.nodes = [] } - + + @discardableResult public func addNode(_ label: String) -> Node { let node = Node(label) nodes.append(node) return node } - + public func addEdge(_ source: Node, neighbor: Node) { let edge = Edge(neighbor) source.neighbors.append(edge) } - + public var description: String { var description = "" - + for node in nodes { if !node.neighbors.isEmpty { description += "[node: \(node.label) edges: \(node.neighbors.map { $0.neighbor.label})]" @@ -26,18 +27,18 @@ public class Graph: CustomStringConvertible, Equatable { } return description } - + public func findNodeWithLabel(_ label: String) -> Node { return nodes.filter { $0.label == label }.first! } - + public func duplicate() -> Graph { let duplicated = Graph() - + for node in nodes { duplicated.addNode(node.label) } - + for node in nodes { for edge in node.neighbors { let source = duplicated.findNodeWithLabel(node.label) @@ -45,7 +46,7 @@ public class Graph: CustomStringConvertible, Equatable { duplicated.addEdge(source, neighbor: neighbour) } } - + return duplicated } } diff --git a/Depth-First Search/DepthFirstSearch.playground/Sources/Node.swift b/Depth-First Search/DepthFirstSearch.playground/Sources/Node.swift index 48fc952e3..bab55c371 100644 --- a/Depth-First Search/DepthFirstSearch.playground/Sources/Node.swift +++ b/Depth-First Search/DepthFirstSearch.playground/Sources/Node.swift @@ -1,27 +1,27 @@ public class Node: CustomStringConvertible, Equatable { public var neighbors: [Edge] - + public private(set) var label: String public var distance: Int? public var visited: Bool - + public init(_ label: String) { self.label = label neighbors = [] visited = false } - + public var description: String { if let distance = distance { return "Node(label: \(label), distance: \(distance))" } return "Node(label: \(label), distance: infinity)" } - + public var hasDistance: Bool { return distance != nil } - + public func remove(_ edge: Edge) { neighbors.remove(at: neighbors.index { $0 === edge }!) } diff --git a/Depth-First Search/README.markdown b/Depth-First Search/README.markdown index 98a19e0cb..67030346b 100644 --- a/Depth-First Search/README.markdown +++ b/Depth-First Search/README.markdown @@ -1,5 +1,7 @@ # Depth-First Search +> This topic has been tutorialized [here](https://www.raywenderlich.com/157949/swift-algorithm-club-depth-first-search) + Depth-first search (DFS) is an algorithm for traversing or searching [tree](../Tree/) or [graph](../Graph/) data structures. It starts at a source node and explores as far as possible along each branch before backtracking. Depth-first search can be used on both directed and undirected graphs. diff --git a/Depth-First Search/Tests/DepthFirstSearchTests.swift b/Depth-First Search/Tests/DepthFirstSearchTests.swift index 2cb2556ce..5fb450190 100755 --- a/Depth-First Search/Tests/DepthFirstSearchTests.swift +++ b/Depth-First Search/Tests/DepthFirstSearchTests.swift @@ -1,85 +1,91 @@ import XCTest class DepthFirstSearchTests: XCTestCase { - - func testExploringTree() { - let tree = Graph() - - let nodeA = tree.addNode("a") - let nodeB = tree.addNode("b") - let nodeC = tree.addNode("c") - let nodeD = tree.addNode("d") - let nodeE = tree.addNode("e") - let nodeF = tree.addNode("f") - let nodeG = tree.addNode("g") - let nodeH = tree.addNode("h") - - tree.addEdge(nodeA, neighbor: nodeB) - tree.addEdge(nodeA, neighbor: nodeC) - tree.addEdge(nodeB, neighbor: nodeD) - tree.addEdge(nodeB, neighbor: nodeE) - tree.addEdge(nodeC, neighbor: nodeF) - tree.addEdge(nodeC, neighbor: nodeG) - tree.addEdge(nodeE, neighbor: nodeH) - - let nodesExplored = depthFirstSearch(tree, source: nodeA) - - XCTAssertEqual(nodesExplored, ["a", "b", "d", "e", "h", "c", "f", "g"]) - } - - func testExploringGraph() { - let graph = Graph() - - let nodeA = graph.addNode("a") - let nodeB = graph.addNode("b") - let nodeC = graph.addNode("c") - let nodeD = graph.addNode("d") - let nodeE = graph.addNode("e") - let nodeF = graph.addNode("f") - let nodeG = graph.addNode("g") - let nodeH = graph.addNode("h") - let nodeI = graph.addNode("i") - - graph.addEdge(nodeA, neighbor: nodeB) - graph.addEdge(nodeA, neighbor: nodeH) - graph.addEdge(nodeB, neighbor: nodeA) - graph.addEdge(nodeB, neighbor: nodeC) - graph.addEdge(nodeB, neighbor: nodeH) - graph.addEdge(nodeC, neighbor: nodeB) - graph.addEdge(nodeC, neighbor: nodeD) - graph.addEdge(nodeC, neighbor: nodeF) - graph.addEdge(nodeC, neighbor: nodeI) - graph.addEdge(nodeD, neighbor: nodeC) - graph.addEdge(nodeD, neighbor: nodeE) - graph.addEdge(nodeD, neighbor: nodeF) - graph.addEdge(nodeE, neighbor: nodeD) - graph.addEdge(nodeE, neighbor: nodeF) - graph.addEdge(nodeF, neighbor: nodeC) - graph.addEdge(nodeF, neighbor: nodeD) - graph.addEdge(nodeF, neighbor: nodeE) - graph.addEdge(nodeF, neighbor: nodeG) - graph.addEdge(nodeG, neighbor: nodeF) - graph.addEdge(nodeG, neighbor: nodeH) - graph.addEdge(nodeG, neighbor: nodeI) - graph.addEdge(nodeH, neighbor: nodeA) - graph.addEdge(nodeH, neighbor: nodeB) - graph.addEdge(nodeH, neighbor: nodeG) - graph.addEdge(nodeH, neighbor: nodeI) - graph.addEdge(nodeI, neighbor: nodeC) - graph.addEdge(nodeI, neighbor: nodeG) - graph.addEdge(nodeI, neighbor: nodeH) - - let nodesExplored = depthFirstSearch(graph, source: nodeA) - - XCTAssertEqual(nodesExplored, ["a", "b", "c", "d", "e", "f", "g", "h", "i"]) - } - - func testExploringGraphWithASingleNode() { - let graph = Graph() - let node = graph.addNode("a") - - let nodesExplored = depthFirstSearch(graph, source: node) - - XCTAssertEqual(nodesExplored, ["a"]) - } + + func testSwift4(){ + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + func testExploringTree() { + let tree = Graph() + + let nodeA = tree.addNode("a") + let nodeB = tree.addNode("b") + let nodeC = tree.addNode("c") + let nodeD = tree.addNode("d") + let nodeE = tree.addNode("e") + let nodeF = tree.addNode("f") + let nodeG = tree.addNode("g") + let nodeH = tree.addNode("h") + + tree.addEdge(nodeA, neighbor: nodeB) + tree.addEdge(nodeA, neighbor: nodeC) + tree.addEdge(nodeB, neighbor: nodeD) + tree.addEdge(nodeB, neighbor: nodeE) + tree.addEdge(nodeC, neighbor: nodeF) + tree.addEdge(nodeC, neighbor: nodeG) + tree.addEdge(nodeE, neighbor: nodeH) + + let nodesExplored = depthFirstSearch(tree, source: nodeA) + + XCTAssertEqual(nodesExplored, ["a", "b", "d", "e", "h", "c", "f", "g"]) + } + + func testExploringDigraph() { + let digraph = Graph() + + let nodeA = digraph.addNode("a") + let nodeB = digraph.addNode("b") + let nodeC = digraph.addNode("c") + let nodeD = digraph.addNode("d") + let nodeE = digraph.addNode("e") + let nodeF = digraph.addNode("f") + let nodeG = digraph.addNode("g") + let nodeH = digraph.addNode("h") + let nodeI = digraph.addNode("i") + + digraph.addEdge(nodeA, neighbor: nodeB) + digraph.addEdge(nodeA, neighbor: nodeH) + digraph.addEdge(nodeB, neighbor: nodeA) + digraph.addEdge(nodeB, neighbor: nodeC) + digraph.addEdge(nodeB, neighbor: nodeH) + digraph.addEdge(nodeC, neighbor: nodeB) + digraph.addEdge(nodeC, neighbor: nodeD) + digraph.addEdge(nodeC, neighbor: nodeF) + digraph.addEdge(nodeC, neighbor: nodeI) + digraph.addEdge(nodeD, neighbor: nodeC) + digraph.addEdge(nodeD, neighbor: nodeE) + digraph.addEdge(nodeD, neighbor: nodeF) + digraph.addEdge(nodeE, neighbor: nodeD) + digraph.addEdge(nodeE, neighbor: nodeF) + digraph.addEdge(nodeF, neighbor: nodeC) + digraph.addEdge(nodeF, neighbor: nodeD) + digraph.addEdge(nodeF, neighbor: nodeE) + digraph.addEdge(nodeF, neighbor: nodeG) + digraph.addEdge(nodeG, neighbor: nodeF) + digraph.addEdge(nodeG, neighbor: nodeH) + digraph.addEdge(nodeG, neighbor: nodeI) + digraph.addEdge(nodeH, neighbor: nodeA) + digraph.addEdge(nodeH, neighbor: nodeB) + digraph.addEdge(nodeH, neighbor: nodeG) + digraph.addEdge(nodeH, neighbor: nodeI) + digraph.addEdge(nodeI, neighbor: nodeC) + digraph.addEdge(nodeI, neighbor: nodeG) + digraph.addEdge(nodeI, neighbor: nodeH) + + let nodesExplored = depthFirstSearch(digraph, source: nodeA) + + XCTAssertEqual(nodesExplored, ["a", "b", "c", "d", "e", "f", "g", "h", "i"]) + } + + func testExploringDigraphWithASingleNode() { + let digraph = Graph() + let node = digraph.addNode("a") + + let nodesExplored = depthFirstSearch(digraph, source: node) + + XCTAssertEqual(nodesExplored, ["a"]) + } } diff --git a/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj b/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj index dbc44ecd4..2b433bd96 100644 --- a/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj +++ b/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj @@ -180,6 +180,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -219,6 +220,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -232,7 +234,7 @@ PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -245,7 +247,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 = 4.2; }; name = Release; }; diff --git a/Deque/Deque-Simple.swift b/Deque/Deque-Simple.swift index f4f5f0409..fc0fcb2b2 100644 --- a/Deque/Deque-Simple.swift +++ b/Deque/Deque-Simple.swift @@ -21,7 +21,7 @@ public struct Deque { } public mutating func enqueueFront(_ element: T) { - array.insert(element, atIndex: 0) + array.insert(element, at: 0) } public mutating func dequeue() -> T? { diff --git a/Deque/README.markdown b/Deque/README.markdown index fda1de5f8..f3172e16e 100644 --- a/Deque/README.markdown +++ b/Deque/README.markdown @@ -23,7 +23,7 @@ public struct Deque { } public mutating func enqueueFront(_ element: T) { - array.insert(element, atIndex: 0) + array.insert(element, at: 0) } public mutating func dequeue() -> T? { diff --git a/Dijkstra Algorithm/Dijkstra.playground/Sources/Dijkstra.swift b/Dijkstra Algorithm/Dijkstra.playground/Sources/Dijkstra.swift index 9c5961c71..8cda797dd 100644 --- a/Dijkstra Algorithm/Dijkstra.playground/Sources/Dijkstra.swift +++ b/Dijkstra Algorithm/Dijkstra.playground/Sources/Dijkstra.swift @@ -19,7 +19,7 @@ public class Dijkstra { var currentVertex: Vertex? = startVertex while let vertex = currentVertex { currentVertices.remove(vertex) - let filteredNeighbors = vertex.neighbors.filter { totalVertices.contains($0.0) } + let filteredNeighbors = vertex.neighbors.filter { currentVertices.contains($0.0) } for neighbor in filteredNeighbors { let neighborVertex = neighbor.0 let weight = neighbor.1 @@ -31,11 +31,11 @@ public class Dijkstra { neighborVertex.pathVerticesFromStart.append(neighborVertex) } } - if totalVertices.isEmpty { + if currentVertices.isEmpty { currentVertex = nil break } - currentVertex = totalVertices.min { $0.pathLengthFromStart < $1.pathLengthFromStart } + currentVertex = currentVertices.min { $0.pathLengthFromStart < $1.pathLengthFromStart } } } } diff --git a/Dijkstra Algorithm/Dijkstra.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Dijkstra Algorithm/Dijkstra.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Dijkstra Algorithm/Dijkstra.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Dijkstra Algorithm/README.md b/Dijkstra Algorithm/README.md index 5f8a0ce07..4bc719598 100644 --- a/Dijkstra Algorithm/README.md +++ b/Dijkstra Algorithm/README.md @@ -1,6 +1,6 @@ # Weighted graph general concepts -Every weighted graph should contain: +Every weighted graph should contain: 1. Vertices/Nodes (I will use "vertex" in this readme). @@ -11,11 +11,11 @@ Every weighted graph should contain: -3. Weights for every edge. +3. Weights for every edge. -Final result. +Final result. Directed weighted graph: @@ -44,7 +44,9 @@ When all vertices are marked as visited, the algorithm's job is done. Now, you c I have created **VisualizedDijkstra.playground** game/tutorial to improve your understanding of the algorithm's flow. Besides, below is step by step algorithm's description. -## Example +A short sidenote. The Swift Algorithm Club also contains the A* algorithm, which essentially is a faster version of Dijkstra's algorithm for which the only extra prerequisite is you have to know where the destination is located. + +## Example Let's imagine that you want to go to the shop. Your house is A vertex and there are 4 possible stores around your house. How to find the closest one/ones? Luckily, you have a graph that connects your house with all these stores. So, you know what to do :) ### Initialisation @@ -95,7 +97,7 @@ After this step graph has this state: ### Step 1 -Then we check all of its neighbours. +Then we check all of its neighbours. If checking vertex path length from start + edge weight is smaller than neighbour's path length from start then we set neighbour's path length from start new value and append to its pathVerticesFromStart array new vertex: checkingVertex. Repeat this action for every vertex. for clarity: @@ -120,7 +122,7 @@ And its state is here: ### Step 2 -From now we repeat all actions again and fill our table with new info! +From now we repeat all actions again and fill our table with new info! @@ -161,7 +163,7 @@ From now we repeat all actions again and fill our table with new info! | Path Vertices From Start | [A] | [A, B] | [A, B, C]| [A, D] | [A, D, E ] | -## Code implementation +## Code implementation First of all, let’s create class that will describe any Vertex in the graph. It is pretty simple ```swift @@ -169,12 +171,12 @@ open class Vertex { //Every vertex should be unique that's why we set up identifier open var identifier: String - - //For Dijkstra every vertex in the graph should be connected with at least one other vertex. But there can be some usecases + + //For Dijkstra every vertex in the graph should be connected with at least one other vertex. But there can be some usecases //when you firstly initialize all vertices without neighbours. And then on next iteration you set up their neighbours. So, initially neighbours is an empty array. //Array contains tuples (Vertex, Double). Vertex is a neighbour and Double is as edge weight to that neighbour. open var neighbours: [(Vertex, Double)] = [] - + //As it was mentioned in the algorithm description, default path length from start for all vertices should be as much as possible. //It is var because we will update it during the algorithm execution. open var pathLengthFromStart = Double.infinity @@ -215,7 +217,7 @@ We've created a base for our algorithm. Now let's create a house :) Dijkstra's realisation is really straightforward. ```swift public class Dijkstra { - //This is a storage for vertices in the graph. + //This is a storage for vertices in the graph. //Assuming that our vertices are unique we can use Set instead of array. This approach will bring some benefits later. private var totalVertices: Set @@ -223,69 +225,69 @@ public class Dijkstra { totalVertices = vertices } - //Remember clearCache function in the Vertex class implementation? + //Remember clearCache function in the Vertex class implementation? //This is just a wrapper that cleans cache for all stored vertices. private func clearCache() { totalVertices.forEach { $0.clearCache() } } public func findShortestPaths(from startVertex: Vertex) { - //Before we start searching the shortest path from startVertex, + //Before we start searching the shortest path from startVertex, //we need to clear vertices cache just to be sure that out graph is clean. - //Remember that every Vertex is a class and classes are passed by reference. + //Remember that every Vertex is a class and classes are passed by reference. //So whenever you change vertex outside of this class it will affect this vertex inside totalVertices Set clearCache() //Now all our vertices have Double.infinity pathLengthFromStart and an empty pathVerticesFromStart array. - + //The next step in the algorithm is to set startVertex pathLengthFromStart and pathVerticesFromStart startVertex.pathLengthFromStart = 0 startVertex.pathVerticesFromStart.append(startVertex) - + //Here starts the main part. We will use while loop to iterate through all vertices in the graph. //For this purpose we define currentVertex variable which we will change in the end of each while cycle. var currentVertex: Vertex? = startVertex - + while let vertex = currentVertex { - + //Next line of code is an implementation of setting vertex as visited. //As it has been said, we should check only unvisited vertices in the graph, //So why don't just delete it from the set? This approach let us skip checking for *"if !vertex.visited then"* totalVertices.remove(vertex) - + //filteredNeighbours is an array that contains current vertex neighbours which aren't yet visited let filteredNeighbours = vertex.neighbours.filter { totalVertices.contains($0.0) } - + //Let's iterate through them for neighbour in filteredNeighbours { //These variable are more representative, than neighbour.0 or neighbour.1 let neighbourVertex = neighbour.0 let weight = neighbour.1 - - //Here we calculate new weight, that we can offer to neighbour. + + //Here we calculate new weight, that we can offer to neighbour. let theoreticNewWeight = vertex.pathLengthFromStart + weight - + //If it is smaller than neighbour's current pathLengthFromStart //Then we perform this code if theoreticNewWeight < neighbourVertex.pathLengthFromStart { - + //set new pathLengthFromStart neighbourVertex.pathLengthFromStart = theoreticNewWeight - + //set new pathVerticesFromStart neighbourVertex.pathVerticesFromStart = vertex.pathVerticesFromStart - + //append current vertex to neighbour's pathVerticesFromStart neighbourVertex.pathVerticesFromStart.append(neighbourVertex) } } - + //If totalVertices is empty, i.e. all vertices are visited //Than break the loop if totalVertices.isEmpty { currentVertex = nil break } - + //If loop is not broken, than pick next vertex for checkin from not visited. //Next vertex pathLengthFromStart should be the smallest one. currentVertex = totalVertices.min { $0.pathLengthFromStart < $1.pathLengthFromStart } diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Contents.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Contents.swift index e4b3e6732..de6b49834 100644 --- a/Dijkstra Algorithm/VisualizedDijkstra.playground/Contents.swift +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Contents.swift @@ -8,6 +8,7 @@ */ import UIKit import PlaygroundSupport + /*: First of all, let's set up colors for our window and graph. The visited color will be applied to visited vertices. The checking color will be applied to an edge and an edge neighbor every time the algorithm is checking some vertex neighbors. And default colors are just initial colors for elements. */ diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Window.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Window.swift index 73c512e6b..9a169b8c7 100644 --- a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Window.swift +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Window.swift @@ -134,7 +134,7 @@ public class Window: UIView, GraphDelegate { let origin = CGPoint(x: center.x - 25, y: 100) let activityIndicatorFrame = CGRect(origin: origin, size: size) activityIndicator = UIActivityIndicatorView(frame: activityIndicatorFrame) - activityIndicator.activityIndicatorViewStyle = .whiteLarge + activityIndicator.style = .whiteLarge } @objc private func createGraphButtonTap() { diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Dijkstra Algorithm/VisualizedDijkstra.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/timeline.xctimeline b/Dijkstra Algorithm/VisualizedDijkstra.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Dijkstra Algorithm/VisualizedDijkstra.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/DiningPhilosophers/DiningPhilosophers.xcodeproj/project.pbxproj b/DiningPhilosophers/DiningPhilosophers.xcodeproj/project.pbxproj index 9743ff7d1..7699219de 100755 --- a/DiningPhilosophers/DiningPhilosophers.xcodeproj/project.pbxproj +++ b/DiningPhilosophers/DiningPhilosophers.xcodeproj/project.pbxproj @@ -1,250 +1,223 @@ // !$*UTF8*$! { - archiveVersion = "1"; - objectVersion = "46"; - objects = { - OBJ_1 = { - isa = "PBXProject"; - attributes = { - LastUpgradeCheck = "9999"; - }; - buildConfigurationList = OBJ_2; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = "English"; - hasScannedForEncodings = "0"; - knownRegions = ( - "en", - ); - mainGroup = OBJ_5; - productRefGroup = OBJ_11; - projectDirPath = "."; - targets = ( - OBJ_13, - ); - }; - OBJ_10 = { - isa = "PBXGroup"; - children = ( - ); - path = "Tests"; - sourceTree = ""; - }; - OBJ_11 = { - isa = "PBXGroup"; - children = ( - OBJ_12, - ); - name = "Products"; - path = ""; - sourceTree = "BUILT_PRODUCTS_DIR"; - }; - OBJ_12 = { - isa = "PBXFileReference"; - path = "DiningPhilosophers"; - sourceTree = "BUILT_PRODUCTS_DIR"; - }; - OBJ_13 = { - isa = "PBXNativeTarget"; - buildConfigurationList = OBJ_14; - buildPhases = ( - OBJ_17, - OBJ_19, - ); - dependencies = ( - ); - name = "DiningPhilosophers"; - productName = "DiningPhilosophers"; - productReference = OBJ_12; - productType = "com.apple.product-type.tool"; - }; - OBJ_14 = { - isa = "XCConfigurationList"; - buildConfigurations = ( - OBJ_15, - OBJ_16, - ); - defaultConfigurationIsVisible = "0"; - defaultConfigurationName = "Debug"; - }; - OBJ_15 = { - isa = "XCBuildConfiguration"; - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(PLATFORM_DIR)/Developer/Library/Frameworks", - ); - HEADER_SEARCH_PATHS = ( - ); - INFOPLIST_FILE = "DiningPhilosophers.xcodeproj/DiningPhilosophers_Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", - "@executable_path", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - ); - OTHER_SWIFT_FLAGS = ( - "$(inherited)", - ); - SUPPORTED_PLATFORMS = ( - "macosx", - ); - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE"; - SWIFT_FORCE_DYNAMIC_LINK_STDLIB = "YES"; - SWIFT_FORCE_STATIC_LINK_STDLIB = "NO"; - SWIFT_VERSION = "3.0"; - TARGET_NAME = "DiningPhilosophers"; - }; - name = "Debug"; - }; - OBJ_16 = { - isa = "XCBuildConfiguration"; - buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(PLATFORM_DIR)/Developer/Library/Frameworks", - ); - HEADER_SEARCH_PATHS = ( - ); - INFOPLIST_FILE = "DiningPhilosophers.xcodeproj/DiningPhilosophers_Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", - "@executable_path", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - ); - OTHER_SWIFT_FLAGS = ( - "$(inherited)", - ); - SUPPORTED_PLATFORMS = ( - "macosx", - ); - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE"; - SWIFT_FORCE_DYNAMIC_LINK_STDLIB = "YES"; - SWIFT_FORCE_STATIC_LINK_STDLIB = "NO"; - SWIFT_VERSION = "3.0"; - TARGET_NAME = "DiningPhilosophers"; - }; - name = "Release"; - }; - OBJ_17 = { - isa = "PBXSourcesBuildPhase"; - files = ( - OBJ_18, - ); - }; - OBJ_18 = { - isa = "PBXBuildFile"; - fileRef = OBJ_9; - }; - OBJ_19 = { - isa = "PBXFrameworksBuildPhase"; - files = ( - ); - }; - OBJ_2 = { - isa = "XCConfigurationList"; - buildConfigurations = ( - OBJ_3, - OBJ_4, - ); - defaultConfigurationIsVisible = "0"; - defaultConfigurationName = "Debug"; - }; - OBJ_3 = { - isa = "XCBuildConfiguration"; - buildSettings = { - COMBINE_HIDPI_IMAGES = "YES"; - COPY_PHASE_STRIP = "NO"; - DEBUG_INFORMATION_FORMAT = "dwarf"; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_NS_ASSERTIONS = "YES"; - GCC_OPTIMIZATION_LEVEL = "0"; - MACOSX_DEPLOYMENT_TARGET = "10.10"; - ONLY_ACTIVE_ARCH = "YES"; - OTHER_SWIFT_FLAGS = ( - "-DXcode", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = ( - "macosx", - "iphoneos", - "iphonesimulator", - "appletvos", - "appletvsimulator", - "watchos", - "watchsimulator", - ); - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - USE_HEADERMAP = "NO"; - }; - name = "Debug"; - }; - OBJ_4 = { - isa = "XCBuildConfiguration"; - buildSettings = { - COMBINE_HIDPI_IMAGES = "YES"; - COPY_PHASE_STRIP = "YES"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_OPTIMIZATION_LEVEL = "s"; - MACOSX_DEPLOYMENT_TARGET = "10.10"; - OTHER_SWIFT_FLAGS = ( - "-DXcode", - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = ( - "macosx", - "iphoneos", - "iphonesimulator", - "appletvos", - "appletvsimulator", - "watchos", - "watchsimulator", - ); - SWIFT_OPTIMIZATION_LEVEL = "-O"; - USE_HEADERMAP = "NO"; - }; - name = "Release"; - }; - OBJ_5 = { - isa = "PBXGroup"; - children = ( - OBJ_6, - OBJ_7, - OBJ_10, - OBJ_11, - ); - path = ""; - sourceTree = ""; - }; - OBJ_6 = { - isa = "PBXFileReference"; - explicitFileType = "sourcecode.swift"; - path = "Package.swift"; - sourceTree = ""; - }; - OBJ_7 = { - isa = "PBXGroup"; - children = ( - OBJ_8, - ); - path = "Sources"; - sourceTree = ""; - }; - OBJ_8 = { - isa = "PBXGroup"; - children = ( - OBJ_9, - ); - name = "DiningPhilosophers"; - path = "Sources"; - sourceTree = "SOURCE_ROOT"; - }; - OBJ_9 = { - isa = "PBXFileReference"; - path = "main.swift"; - sourceTree = ""; - }; - }; - rootObject = OBJ_1; + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + OBJ_18 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* main.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 232D7939216F76F700831A74 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + OBJ_12 /* DiningPhilosophers */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; path = DiningPhilosophers; sourceTree = BUILT_PRODUCTS_DIR; }; + OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; + OBJ_9 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + OBJ_19 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + OBJ_11 /* Products */ = { + isa = PBXGroup; + children = ( + OBJ_12 /* DiningPhilosophers */, + ); + name = Products; + sourceTree = BUILT_PRODUCTS_DIR; + }; + OBJ_5 = { + isa = PBXGroup; + children = ( + 232D7939216F76F700831A74 /* README.md */, + OBJ_6 /* Package.swift */, + OBJ_7 /* Sources */, + OBJ_11 /* Products */, + ); + sourceTree = ""; + }; + OBJ_7 /* Sources */ = { + isa = PBXGroup; + children = ( + OBJ_8 /* DiningPhilosophers */, + ); + path = Sources; + sourceTree = ""; + }; + OBJ_8 /* DiningPhilosophers */ = { + isa = PBXGroup; + children = ( + OBJ_9 /* main.swift */, + ); + name = DiningPhilosophers; + path = Sources; + sourceTree = SOURCE_ROOT; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + OBJ_13 /* DiningPhilosophers */ = { + isa = PBXNativeTarget; + buildConfigurationList = OBJ_14 /* Build configuration list for PBXNativeTarget "DiningPhilosophers" */; + buildPhases = ( + OBJ_17 /* Sources */, + OBJ_19 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DiningPhilosophers; + productName = DiningPhilosophers; + productReference = OBJ_12 /* DiningPhilosophers */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + OBJ_1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 9999; + TargetAttributes = { + OBJ_13 = { + LastSwiftMigration = 1000; + }; + }; + }; + buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "DiningPhilosophers" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = OBJ_5; + productRefGroup = OBJ_11 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + OBJ_13 /* DiningPhilosophers */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + OBJ_17 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 0; + files = ( + OBJ_18 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + OBJ_15 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = DiningPhilosophers.xcodeproj/DiningPhilosophers_Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx @executable_path"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + SUPPORTED_PLATFORMS = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; + SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES; + SWIFT_FORCE_STATIC_LINK_STDLIB = NO; + SWIFT_VERSION = 4.2; + TARGET_NAME = DiningPhilosophers; + }; + name = Debug; + }; + OBJ_16 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks"; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = DiningPhilosophers.xcodeproj/DiningPhilosophers_Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx @executable_path"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + SUPPORTED_PLATFORMS = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; + SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES; + SWIFT_FORCE_STATIC_LINK_STDLIB = NO; + SWIFT_VERSION = 4.2; + TARGET_NAME = DiningPhilosophers; + }; + name = Release; + }; + OBJ_3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + MACOSX_DEPLOYMENT_TARGET = 10.10; + ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-DXcode"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + USE_HEADERMAP = NO; + }; + name = Debug; + }; + OBJ_4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_OPTIMIZATION_LEVEL = s; + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_SWIFT_FLAGS = "-DXcode"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + USE_HEADERMAP = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + OBJ_14 /* Build configuration list for PBXNativeTarget "DiningPhilosophers" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + OBJ_15 /* Debug */, + OBJ_16 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + OBJ_2 /* Build configuration list for PBXProject "DiningPhilosophers" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + OBJ_3 /* Debug */, + OBJ_4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = OBJ_1 /* Project object */; } diff --git a/DiningPhilosophers/DiningPhilosophers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/DiningPhilosophers/DiningPhilosophers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/DiningPhilosophers/DiningPhilosophers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/DiningPhilosophers/README.md b/DiningPhilosophers/README.md index ae84cb63b..aed9dfe59 100755 --- a/DiningPhilosophers/README.md +++ b/DiningPhilosophers/README.md @@ -44,7 +44,7 @@ Based on the Chandy and Misra's analysis, a system of preference levels is deriv # Swift implementation -This Swift 3.0 implementation of the Chandy/Misra solution is based on the GCD and Semaphore tecnique that can be built on both macOS and Linux. +This Swift 3.0 implementation of the Chandy/Misra solution is based on the GCD and Semaphore technique that can be built on both macOS and Linux. The code is based on a ForkPair struct used for holding an array of DispatchSemaphore and a Philosopher struct for associate a couple of forks to each Philosopher. @@ -57,3 +57,4 @@ A background DispatchQueue is then used to let any Philosopher run asyncrounosly Dining Philosophers on Wikipedia https://en.wikipedia.org/wiki/Dining_philosophers_problem Written for Swift Algorithm Club by Jacopo Mangiavacchi +Swift 4.2 check by Bruno Scheele diff --git a/DiningPhilosophers/Sources/main.swift b/DiningPhilosophers/Sources/main.swift index 32c3af979..f473e76fb 100755 --- a/DiningPhilosophers/Sources/main.swift +++ b/DiningPhilosophers/Sources/main.swift @@ -6,6 +6,11 @@ // // +// last checked with Xcode 9.0b4 +#if swift(>=4.0) + print("Hello, Swift 4!") +#endif + import Dispatch let numberOfPhilosophers = 4 diff --git a/Egg Drop Problem/EggDrop.playground/Contents.swift b/Egg Drop Problem/EggDrop.playground/Contents.swift new file mode 100644 index 000000000..decaacdca --- /dev/null +++ b/Egg Drop Problem/EggDrop.playground/Contents.swift @@ -0,0 +1,8 @@ +drop(numberOfEggs: 2, numberOfFloors: 2) //expected answer: 2 +drop(numberOfEggs: 2, numberOfFloors: 4) //expected answer: 3 +drop(numberOfEggs: 2, numberOfFloors: 6) //expected answer: 3 +drop(numberOfEggs: 5, numberOfFloors: 5) //expected answer: 2 +drop(numberOfEggs: 5, numberOfFloors: 20) //expected answer: 4 + +drop(numberOfEggs: 2, numberOfFloors: 36) +drop(numberOfEggs: 2, numberOfFloors: 10) diff --git a/Egg Drop Problem/EggDrop.playground/Sources/EggDrop.swift b/Egg Drop Problem/EggDrop.playground/Sources/EggDrop.swift new file mode 100644 index 000000000..f8bb0a420 --- /dev/null +++ b/Egg Drop Problem/EggDrop.playground/Sources/EggDrop.swift @@ -0,0 +1,27 @@ +public func drop(numberOfEggs: Int, numberOfFloors: Int) -> Int { + guard numberOfEggs != 0 && numberOfFloors != 0 else { return 0 } + guard numberOfEggs != 1 && numberOfFloors != 1 else { return 1 } + + var eggFloor: [[Int]] = .init(repeating: .init(repeating: 0, count: numberOfFloors + 1), count: numberOfEggs + 1) + var attempts = 0 + + for floorNumber in stride(from: 0, through: numberOfFloors, by: 1) { + eggFloor[1][floorNumber] = floorNumber + } + eggFloor[2][1] = 1 + + for eggNumber in stride(from: 2, through: numberOfEggs, by: 1) { + for floorNumber in stride(from: 2, through: numberOfFloors, by: 1) { + eggFloor[eggNumber][floorNumber] = Int.max + for visitingFloor in stride(from: 1, through: floorNumber, by: 1) { + attempts = 1 + max(eggFloor[eggNumber - 1][visitingFloor - 1], eggFloor[eggNumber][floorNumber - visitingFloor]) + + if attempts < eggFloor[eggNumber][floorNumber] { + eggFloor[eggNumber][floorNumber] = attempts + } + } + } + } + + return eggFloor[numberOfEggs][numberOfFloors] +} diff --git a/Egg Drop Problem/EggDrop.playground/contents.xcplayground b/Egg Drop Problem/EggDrop.playground/contents.xcplayground new file mode 100644 index 000000000..63b6dd8df --- /dev/null +++ b/Egg Drop Problem/EggDrop.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/contents.xcworkspacedata b/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/Egg Drop Problem/EggDrop.playground/playground.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/Egg Drop Problem/EggDrop.swift b/Egg Drop Problem/EggDrop.swift new file mode 100644 index 000000000..db1e48b95 --- /dev/null +++ b/Egg Drop Problem/EggDrop.swift @@ -0,0 +1,39 @@ +public func eggDrop(numberOfEggs: Int, numberOfFloors: Int) -> Int { + if numberOfEggs == 0 || numberOfFloors == 0{ //edge case: When either number of eggs or number of floors is 0, answer is 0 + return 0 + } + if numberOfEggs == 1 || numberOfFloors == 1{ //edge case: When either number of eggs or number of floors is 1, answer is 1 + return 1 + } + + var eggFloor = [[Int]](repeating: [Int](repeating: 0, count: numberOfFloors+1), count: numberOfEggs+1) //egg(rows) floor(cols) array to store the solutions + var attempts: Int = 0 + + for var floorNumber in (0..<(numberOfFloors+1)){ + eggFloor[1][floorNumber] = floorNumber //base case: if there's only one egg, it takes 'numberOfFloors' attempts + } + eggFloor[2][1] = 1 //base case: if there are two eggs and one floor, it takes one attempt + + for var eggNumber in (2..<(numberOfEggs+1)){ + for var floorNumber in (2..<(numberOfFloors+1)){ + eggFloor[eggNumber][floorNumber] = Int.max //setting the final result a high number to find out minimum + for var visitingFloor in (1..<(floorNumber+1)){ + //there are two cases + //case 1: egg breaks. meaning we'll have one less egg, and we'll have to go downstairs -> visitingFloor-1 + //case 2: egg doesn't break. meaning we'll still have 'eggs' number of eggs, and we'll go upstairs -> floorNumber-visitingFloor + attempts = 1 + max(eggFloor[eggNumber-1][visitingFloor-1], eggFloor[eggNumber][floorNumber-visitingFloor])//we add one taking into account the attempt we're taking at the moment + + if attempts < eggFloor[eggNumber][floorNumber]{ //finding the min + eggFloor[eggNumber][floorNumber] = attempts; + } + } + } + } + + return eggFloor[numberOfEggs][numberOfFloors] +} + +//Helper function to find max of two integers +public func max(_ x1: Int, _ x2: Int) -> Int{ + return x1 > x2 ? x1 : x2 +} diff --git a/Egg Drop Problem/README.markdown b/Egg Drop Problem/README.markdown new file mode 100644 index 000000000..416984193 --- /dev/null +++ b/Egg Drop Problem/README.markdown @@ -0,0 +1,72 @@ +# Egg Drop + +The *egg drop* problem is an interview question popularized by Google. The premise is simple; You're given a task to evaluate the *shatter resistance* of unknown objects by dropping them at a certain height. For simplicity, you test this by going inside a multi-story building and performing tests by dropping the objects out the window and onto the ground: + +![building with eggs being dropped](images/eggdrop.png) + +Your goal is to find out the **minimum** height that causes the object to shatter. Consider the trivial case you're given **1** object to obtain the results with. Since you've only got one sample for testing, you need to play it safe by performing drop tests starting with the bottom floor and working your way up: + +![dropping from first floor](images/eggdrop2.png) + +If the object is incredibly resilient, and you may need to do the testing on the world's tallest building - the [Burj Khalifa](https://en.wikipedia.org/wiki/Burj_Khalifa). With **163** floors, that's a lot of climbing. Let's assume you complain, and your employer hears your plight. You are now given *several* samples to work with. How can you make use of these extra samples to expedite your testing process? The problem for this situation is popularized as the **egg drop** problem. + +## Description + +You're in a building with **m** floors and you are given **n** eggs. What is the minimum number of attempts it will take to find out the floor that breaks the egg? + +For convenience, here are a few rules to keep in mind: + +- An egg that survives a fall can be used again. +- A broken egg must be discarded. +- The effect of a fall is the same for all eggs. +- If an egg breaks, then it would break if dropped from a higher floor. +- If an egg survives, then it would survive a shorter fall. + +## Solution + +- eggNumber -> Number of eggs at the moment +- floorNumber -> Floor number at the moment +- visitingFloor -> Floor being visited at the moment +- attempts -> Minimum number of attempts it will take to find out from which floor egg will break + +We store all the solutions in a 2D array. Where rows represents number of eggs and columns represent number of floors. + +First, we set base cases: +1) If there's only one egg, it takes as many attempts as number of floors +2) If there are two eggs and one floor, it takes one attempt + +```swift +for var floorNumber in (0..<(numberOfFloors+1)){ +eggFloor[1][floorNumber] = floorNumber //base case 1: if there's only one egg, it takes 'numberOfFloors' attempts +} + +eggFloor[2][1] = 1 //base case 2: if there are two eggs and one floor, it takes one attempt +``` + +When we drop an egg from a floor 'floorNumber', there can be two cases (1) The egg breaks (2) The egg doesn’t break. + +1) If the egg breaks after dropping from 'visitingFloorth' floor, then we only need to check for floors lower than 'visitingFloor' with remaining eggs; so the problem reduces to 'visitingFloor'-1 floors and 'eggNumber'-1 eggs. +2) If the egg doesn’t break after dropping from the 'visitingFloorth' floor, then we only need to check for floors higher than 'visitingFloor'; so the problem reduces to floors-'visitingFloor' floors and 'eggNumber' eggs. + +Since we need to minimize the number of trials in worst case, we take the maximum of two cases. We consider the max of above two cases for every floor and choose the floor which yields minimum number of trials. + +We find the answer based on the base cases and previously found answers as follows. +```swift +attempts = 1 + max(eggFloor[eggNumber-1][floors-1], eggFloor[eggNumber][floorNumber-floors])//we add one taking into account the attempt we're taking at the moment + +if attempts < eggFloor[eggNumber][floorNumber]{ //finding the min + eggFloor[eggNumber][floorNumber] = attempts; +} +``` +## Example +Let's assume we have 2 eggs and 2 floors. +1) We drop one egg from the first floor. If it breaks, then we get the answer. If it doesn't we'll have 2 eggs and 1 floors to work with. + attempts = 1 + maximum of 0(got the answer) and eggFloor[2][1] (base case 2 which gives us 1) + attempts = 1 + 1 = 2 +2) We drop one egg from the second floor. If it breaks, we'll have 1 egg and 1 floors to work with. If it doesn't, we'll get the answer. + attempts = 1 + maximum of eggFloor[1][1](base case 1 which gives us 1) and 0(got the answer) + attempts = 1 + 1 = 2 +3) Finding the minimum of 2 and 2 gives us 2, so the answer is 2. + 2 is the minimum number of attempts it will take to find out from which floor egg will break. + +*Written for the Swift Algorithm Club by Arkalyk Akash. Revisions and additions by Kelvin Lau* diff --git a/Egg Drop Problem/images/eggdrop.png b/Egg Drop Problem/images/eggdrop.png new file mode 100644 index 000000000..9acfc2b76 Binary files /dev/null and b/Egg Drop Problem/images/eggdrop.png differ diff --git a/Egg Drop Problem/images/eggdrop2.png b/Egg Drop Problem/images/eggdrop2.png new file mode 100644 index 000000000..b342321bb Binary files /dev/null and b/Egg Drop Problem/images/eggdrop2.png differ diff --git a/Encode and Decode Tree/EncodeAndDecodeTree.playground/Contents.swift b/Encode and Decode Tree/EncodeAndDecodeTree.playground/Contents.swift new file mode 100644 index 000000000..44c2440d5 --- /dev/null +++ b/Encode and Decode Tree/EncodeAndDecodeTree.playground/Contents.swift @@ -0,0 +1,46 @@ +//: Playground - noun: a place where people can play + +func printTree(_ root: BinaryNode?) { + guard let root = root else { + return + } + + let leftVal = root.left == nil ? "nil" : root.left!.val + let rightVal = root.right == nil ? "nil" : root.right!.val + + print("val: \(root.val) left: \(leftVal) right: \(rightVal)") + + printTree(root.left) + printTree(root.right) +} + +let coder = BinaryNodeCoder() + +let node1 = BinaryNode("a") +let node2 = BinaryNode("b") +let node3 = BinaryNode("c") +let node4 = BinaryNode("d") +let node5 = BinaryNode("e") + +node1.left = node2 +node1.right = node3 +node3.left = node4 +node3.right = node5 + +let encodeStr = try coder.encode(node1) +print(encodeStr) +// "a b # # c d # # e # #" + + +let root: BinaryNode = coder.decode(from: encodeStr)! +print("Tree:") +printTree(root) +/* + Tree: + val: a left: b right: c + val: b left: nil right: nil + val: c left: d right: e + val: d left: nil right: nil + val: e left: nil right: nil + */ + diff --git a/Encode and Decode Tree/EncodeAndDecodeTree.playground/Sources/EncodeAndDecodeTree.swift b/Encode and Decode Tree/EncodeAndDecodeTree.playground/Sources/EncodeAndDecodeTree.swift new file mode 100644 index 000000000..10bb277f5 --- /dev/null +++ b/Encode and Decode Tree/EncodeAndDecodeTree.playground/Sources/EncodeAndDecodeTree.swift @@ -0,0 +1,95 @@ +// +// EncodeAndDecodeTree.swift +// +// +// Created by Kai Chen on 19/07/2017. +// +// + +import Foundation + +protocol BinaryNodeEncoder { + func encode(_ node: BinaryNode?) throws -> String +} + +protocol BinaryNodeDecoder { + func decode(from string: String) -> BinaryNode? +} + +public class BinaryNodeCoder: BinaryNodeEncoder, BinaryNodeDecoder { + + // MARK: Private + + private let separator: Character = "," + private let nilNode = "X" + + private func decode(from array: inout [String]) -> BinaryNode? { + guard !array.isEmpty else { + return nil + } + + let value = array.removeLast() + + guard value != nilNode, let val = value as? T else { + return nil + } + + let node = BinaryNode(val) + node.left = decode(from: &array) + node.right = decode(from: &array) + + return node + } + + // MARK: Public + + public init() {} + + public func encode(_ node: BinaryNode?) throws -> String { + var str = "" + node?.preOrderTraversal { data in + if let data = data { + let string = String(describing: data) + str.append(string) + } else { + str.append(nilNode) + } + str.append(separator) + } + + return str + } + + public func decode(from string: String) -> BinaryNode? { + var components = string.split(separator: separator).reversed().map(String.init) + return decode(from: &components) + } +} + +public class BinaryNode { + public var val: Element + public var left: BinaryNode? + public var right: BinaryNode? + + public init(_ val: Element, left: BinaryNode? = nil, right: BinaryNode? = nil) { + self.val = val + self.left = left + self.right = right + } + + public func preOrderTraversal(visit: (Element?) throws -> ()) rethrows { + try visit(val) + + if let left = left { + try left.preOrderTraversal(visit: visit) + } else { + try visit(nil) + } + + if let right = right { + try right.preOrderTraversal(visit: visit) + } else { + try visit(nil) + } + } +} diff --git a/Encode and Decode Tree/EncodeAndDecodeTree.playground/contents.xcplayground b/Encode and Decode Tree/EncodeAndDecodeTree.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Encode and Decode Tree/EncodeAndDecodeTree.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Encode and Decode Tree/EncodeAndDecodeTree.swift b/Encode and Decode Tree/EncodeAndDecodeTree.swift new file mode 100644 index 000000000..6ec8a3046 --- /dev/null +++ b/Encode and Decode Tree/EncodeAndDecodeTree.swift @@ -0,0 +1,95 @@ +// +// EncodeAndDecodeTree.swift +// +// +// Created by Kai Chen on 19/07/2017. +// +// + +import Foundation + +protocol BinaryNodeEncoder { + func encode(_ node: BinaryNode?) throws -> String +} + +protocol BinaryNodeDecoder { + func decode(from string: String) -> BinaryNode? +} + +public class BinaryNodeCoder: BinaryNodeEncoder, BinaryNodeDecoder { + + // MARK: Private + + private let separator: Character = "," + private let nilNode = "X" + + private func decode(from array: inout [String]) -> BinaryNode? { + guard !array.isEmpty else { + return nil + } + + let value = array.removeLast() + + guard value != nilNode, let val = value as? T else { + return nil + } + + let node = BinaryNode(val) + node.left = decode(from: &array) + node.right = decode(from: &array) + + return node + } + + // MARK: Public + + public init() {} + + public func encode(_ node: BinaryNode?) throws -> String { + var str = "" + node?.preOrderTraversal { data in + if let data = data { + let string = String(describing: data) + str.append(string) + } else { + str.append(nilNode) + } + str.append(separator) + } + + return str + } + + public func decode(from string: String) -> BinaryNode? { + var components = string.split(separator: separator).reversed().map(String.init) + return decode(from: &components) + } +} + +public class BinaryNode { + public var val: Element + public var left: BinaryNode? + public var right: BinaryNode? + + public init(_ val: Element, left: BinaryNode? = nil, right: BinaryNode? = nil) { + self.val = val + self.left = left + self.right = right + } + + public func preOrderTraversal(visit: (Element?) throws -> ()) rethrows { + try visit(val) + + if let left = left { + try left.preOrderTraversal(visit: visit) + } else { + try visit(nil) + } + + if let right = right { + try right.preOrderTraversal(visit: visit) + } else { + try visit(nil) + } + } +} diff --git a/Encode and Decode Tree/readme.md b/Encode and Decode Tree/readme.md new file mode 100644 index 000000000..b60e0a504 --- /dev/null +++ b/Encode and Decode Tree/readme.md @@ -0,0 +1,199 @@ +# Encode and Decode Binary Tree + +> **Note**: The prerequisite for this article is an understanding of how [binary trees](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Binary%20Tree) work. + +Trees are complex structures. Unlike linear collections such as arrays or linked lists, trees are *non-linear* and each element in a tree has positional information such as the *parent-child* relationship between nodes. When you want to send a tree structure to your backend, you need to send the data of each node, and a way to represent the parent-child relationship for each node. + +Your strategy in how you choose to represent this information is called your **encoding** strategy. The opposite of that - changing your encoded data back to its original form - is your **decoding** strategy. + +There are many ways to encode a tree and decode a tree. The important thing to keep in mind is that encoding and decoding strategies are closely related. The way you choose to encode a tree directly affects how you might decode a tree. + +Encoding and decoding are synonyms to *serializing* and *deserializing* trees. + +As a reference, the following code represents the typical `Node` type of a binary tree: + +```swift +class BinaryNode { + var data: Element + var leftChild: BinaryNode? + var rightChild: BinaryNode? + + // ... (rest of the implementation) +} +``` + +Your encoding and decoding methods will reside in the `BinaryNodeEncoder` and `BinaryNodeDecoder` classes: + +```swift +class BinaryNodeCoder { + + // transforms nodes into string representation + func encode(_ node: BinaryNode) throws -> String where T: Encodable { + + } + + // transforms string into `BinaryNode` representation + func decode(from string: String) + throws -> BinaryNode where T: Decodable { + + } +} +``` + +## Encoding + +As mentioned before, there are different ways to do encoding. For no particular reason, you'll opt for the following rules: + +1. The result of the encoding will be a `String` object. +2. You'll encode using *pre-order* traversal. + +Here's an example of this operation in code: + +```swift +fileprivate extension BinaryNode { + + // 1 + func preOrderTraversal(visit: (Element?) throws -> ()) rethrows { + try visit(data) + + if let leftChild = leftChild { + try leftChild.preOrderTraversal(visit: visit) + } else { + try visit(nil) + } + + if let rightChild = rightChild { + try rightChild.preOrderTraversal(visit: visit) + } else { + try visit(nil) + } + } +} + +class BinaryNodeCoder { + + // 2 + private var separator: String { return "," } + + // 3 + private var nilNode: String { return "X" } + + // 4 + func encode(_ node: BinaryNode) -> String { + var str = "" + node.preOrderTraversal { data in + if let data = data { + let string = String(describing: data) + str.append(string) + } else { + str.append(nilNode) + } + str.append(separator) + } + return str + } + + // ... +} +``` + +Here's a high level overview of the above code: + +2. `separator` is a way to distinguish the nodes in a string. To illustrate its importance, consider the following encoded string "banana". How did the tree structure look like before encoding? Without the `separator`, you can't tell. + +3. `nilNode` is used to identify empty children. This a necesssary piece of information to retain in order to rebuild the tree later. + +4. `encode` returns a `String` representation of the `BinaryNode`. For example: "ba,nana,nil" represents a tree with two nodes - "ba" and "nana" - in pre-order format. + +## Decoding + +Your decoding strategy is the exact opposite of your encoding strategy. You'll take an encoded string, and turn it back into your binary tree. + +Your encoding strategy followed the following rules: + +1. The result of the encoding will be a `String` object. +2. You'll encode using *pre-order* traversal. + +The implementation also added a few important details: + +* node values are separated by `,` +* `nil` children are denoted by the `nil` string + +These details will shape your `decode` operation. Here's a possible implementation: + +```swift + +class BinaryNodeCoder { + + // ... + + // 1 + func decode(_ string: String) -> BinaryNode? { + let components = encoded.lazy.split(separator: separator).reversed().map(String.init) + return decode(from: components) + } + + // 2 + private func decode(from array: inout [String]) -> BinaryNode? { + guard !array.isEmpty else { return nil } + let value = array.removeLast() + guard value != "\(nilNode)" else { return nil } + + let node = AVLNode(value: value) + node.leftChild = decode(from: &array) + node.rightChild = decode(from: &array) + return node + } +} +``` + +Here's a high level overview of the above code: + +1. Takes a `String`, and uses `split` to partition the contents of `string` into an array based on the `separator` defined in the encoding step. The result is first `reversed`, and then mapped to a `String`. The `reverse` step is an optimization for the next function, allowing us to use `array.removeLast()` instead of `array.removeFirst()`. + +2. Using an array as a stack, you recursively decode each node. The array keeps track of sequence of nodes and progress. + +Here's an example output of a tree undergoing the encoding and decoding process: + +``` +Original Tree + + ┌──8423 + ┌──8391 + │ └──nil +┌──7838 +│ │ ┌──4936 +│ └──3924 +│ └──2506 +830 +│ ┌──701 +└──202 + └──169 + +Encoded tree: 830,202,169,X,X,701,X,X,7838,3924,2506,X,X,4936,X,X,8391,X,8423,X,X, + +Decoded tree + + ┌──8423 + ┌──8391 + │ └──nil +┌──7838 +│ │ ┌──4936 +│ └──3924 +│ └──2506 +830 +│ ┌──701 +└──202 + └──169 + ``` + +Notice the original tree and decoded tree are identical. + +## Further Reading & References + +- [LeetCode](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/) + +*Written for the Swift Algorithm Club by Kai Chen & Kelvin Lau* + + + diff --git a/Fixed Size Array/FixedSizeArray.playground/Contents.swift b/Fixed Size Array/FixedSizeArray.playground/Contents.swift index 8ef545e45..616f8443d 100644 --- a/Fixed Size Array/FixedSizeArray.playground/Contents.swift +++ b/Fixed Size Array/FixedSizeArray.playground/Contents.swift @@ -53,3 +53,4 @@ array.append(2) array[1] array.removeAt(index: 0) array.removeAll() + diff --git a/Fizz Buzz/FizzBuzz.playground/Contents.swift b/Fizz Buzz/FizzBuzz.playground/Contents.swift index ba2f83b15..2ab13b362 100644 --- a/Fizz Buzz/FizzBuzz.playground/Contents.swift +++ b/Fizz Buzz/FizzBuzz.playground/Contents.swift @@ -1,21 +1,23 @@ -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) - } } -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/GCD/GCD.playground/Contents.swift b/GCD/GCD.playground/Contents.swift index 2e264be93..bb8d49055 100644 --- a/GCD/GCD.playground/Contents.swift +++ b/GCD/GCD.playground/Contents.swift @@ -1,39 +1,21 @@ -//: Playground - noun: a place where people can play - -// Recursive version -func gcd(_ a: Int, _ b: Int) -> Int { - let r = a % b - if r != 0 { - return gcd(b, r) - } else { - return b - } -} - -/* -// Iterative version -func gcd(m: Int, _ n: Int) -> Int { - var a = 0 - var b = max(m, n) - var r = min(m, n) - - while r != 0 { - a = b - b = r - r = a % b - } - return b -} -*/ - -func lcm(_ m: Int, _ n: Int) -> Int { - return m*n / gcd(m, n) -} - gcd(52, 39) // 13 gcd(228, 36) // 12 gcd(51357, 3819) // 57 gcd(841, 299) // 1 -lcm(2, 3) // 6 -lcm(10, 8) // 40 +gcd(52, 39, using: gcdRecursiveEuklid) // 13 +gcd(228, 36, using: gcdRecursiveEuklid) // 12 +gcd(51357, 3819, using: gcdRecursiveEuklid) // 57 +gcd(841, 299, using: gcdRecursiveEuklid) // 1 + +gcd(52, 39, using: gcdBinaryRecursiveStein) // 13 +gcd(228, 36, using: gcdBinaryRecursiveStein) // 12 +gcd(51357, 3819, using: gcdBinaryRecursiveStein) // 57 +gcd(841, 299, using: gcdBinaryRecursiveStein) // 1 + +do { + try lcm(2, 3) // 6 + try lcm(10, 8, using: gcdRecursiveEuklid) // 40 +} catch { + dump(error) +} diff --git a/GCD/GCD.playground/Sources/GCD.swift b/GCD/GCD.playground/Sources/GCD.swift new file mode 100644 index 000000000..4eb0c7621 --- /dev/null +++ b/GCD/GCD.playground/Sources/GCD.swift @@ -0,0 +1,143 @@ +/* + Finds the largest positive integer that divides both + m and n without a remainder. + + - Parameter m: First natural number + - Parameter n: Second natural number + - Parameter using: The used algorithm to calculate the gcd. + If nothing provided, the Iterative Euclidean + algorithm is used. + - Returns: The natural gcd of m and n. + */ +public func gcd(_ m: Int, _ n: Int, using gcdAlgorithm: (Int, Int) -> (Int) = gcdIterativeEuklid) -> Int { + return gcdAlgorithm(m, n) +} + +/* + Iterative approach based on the Euclidean algorithm. + The Euclidean algorithm is based on the principle that the greatest + common divisor of two numbers does not change if the larger number + is replaced by its difference with the smaller number. + - Parameter m: First natural number + - Parameter n: Second natural number + - Returns: The natural gcd of m and n. + */ +public func gcdIterativeEuklid(_ m: Int, _ n: Int) -> Int { + var a: Int = 0 + var b: Int = max(m, n) + var r: Int = min(m, n) + + while r != 0 { + a = b + b = r + r = a % b + } + return b +} + +/* + Recursive approach based on the Euclidean algorithm. + + - Parameter m: First natural number + - Parameter n: Second natural number + - Returns: The natural gcd of m and n. + - Note: The recursive version makes only tail recursive calls. + Most compilers for imperative languages do not optimize these. + The swift compiler as well as the obj-c compiler is able to do + optimizations for tail recursive calls, even though it still ends + up to be the same in terms of complexity. That said, tail call + elimination is not mutually exclusive to recursion. + */ +public func gcdRecursiveEuklid(_ m: Int, _ n: Int) -> Int { + let r: Int = m % n + if r != 0 { + return gcdRecursiveEuklid(n, r) + } else { + return n + } +} + +/* + The binary GCD algorithm, also known as Stein's algorithm, + is an algorithm that computes the greatest common divisor of two + nonnegative integers. Stein's algorithm uses simpler arithmetic + operations than the conventional Euclidean algorithm; it replaces + division with arithmetic shifts, comparisons, and subtraction. + + - Parameter m: First natural number + - Parameter n: Second natural number + - Returns: The natural gcd of m and n + - Complexity: worst case O(n^2), where n is the number of bits + in the larger of the two numbers. Although each step reduces + at least one of the operands by at least a factor of 2, + the subtract and shift operations take linear time for very + large integers + */ +public func gcdBinaryRecursiveStein(_ m: Int, _ n: Int) -> Int { + if let easySolution = findEasySolution(m, n) { return easySolution } + + if (m & 1) == 0 { + // m is even + if (n & 1) == 1 { + // and n is odd + return gcdBinaryRecursiveStein(m >> 1, n) + } else { + // both m and n are even + return gcdBinaryRecursiveStein(m >> 1, n >> 1) << 1 + } + } else if (n & 1) == 0 { + // m is odd, n is even + return gcdBinaryRecursiveStein(m, n >> 1) + } else if (m > n) { + // reduce larger argument + return gcdBinaryRecursiveStein((m - n) >> 1, n) + } else { + // reduce larger argument + return gcdBinaryRecursiveStein((n - m) >> 1, m) + } +} + +/* + Finds an easy solution for the gcd. + - Parameter m: First natural number + - Parameter n: Second natural number + - Returns: A natural gcd of m and n if possible. + - Note: It might be relevant for different usecases to + try finding an easy solution for the GCD calculation + before even starting more difficult operations. + */ +func findEasySolution(_ m: Int, _ n: Int) -> Int? { + if m == n { + return m + } + if m == 0 { + return n + } + if n == 0 { + return m + } + return nil +} + + +public enum LCMError: Error { + case divisionByZero +} + +/* + Calculates the lcm for two given numbers using a specified gcd algorithm. + + - Parameter m: First natural number. + - Parameter n: Second natural number. + - Parameter using: The used gcd algorithm to calculate the lcm. + If nothing provided, the Iterative Euclidean + algorithm is used. + - Throws: Can throw a `divisionByZero` error if one of the given + attributes turns out to be zero or less. + - Returns: The least common multiplier of the two attributes as + an unsigned integer + */ +public func lcm(_ m: Int, _ n: Int, using gcdAlgorithm: (Int, Int) -> (Int) = gcdIterativeEuklid) throws -> Int { + guard m & n != 0 else { throw LCMError.divisionByZero } + return m / gcdAlgorithm(m, n) * n +} diff --git a/GCD/GCD.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/GCD/GCD.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/GCD/GCD.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/GCD/GCD.playground/timeline.xctimeline b/GCD/GCD.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/GCD/GCD.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/GCD/GCD.swift b/GCD/GCD.swift deleted file mode 100644 index ce7e7fa78..000000000 --- a/GCD/GCD.swift +++ /dev/null @@ -1,34 +0,0 @@ -/* - Euclid's algorithm for finding the greatest common divisor -*/ -func gcd(_ m: Int, _ n: Int) -> Int { - var a = 0 - var b = max(m, n) - var r = min(m, n) - - while r != 0 { - a = b - b = r - r = a % b - } - return b -} - -/* -// Recursive version -func gcd(_ a: Int, _ b: Int) -> Int { - let r = a % b - if r != 0 { - return gcd(b, r) - } else { - return b - } -} -*/ - -/* - Returns the least common multiple of two numbers. -*/ -func lcm(_ m: Int, _ n: Int) -> Int { - return m*n / gcd(m, n) -} diff --git a/GCD/README.markdown b/GCD/README.markdown index 07d2de1ca..4c459eff0 100644 --- a/GCD/README.markdown +++ b/GCD/README.markdown @@ -1,14 +1,19 @@ # Greatest Common Divisor -The *greatest common divisor* (or Greatest Common Factor) of two numbers `a` and `b` is the largest positive integer that divides both `a` and `b` without a remainder. +The *greatest common divisor* (or Greatest Common Factor) of two numbers `a` and `b` is the largest positive integer that divides both `a` and `b` without a remainder. The GCD.swift file contains three different algorithms of how to calculate the greatest common divisor. +For example, `gcd(39, 52) = 13` because 13 divides 39 (`39 / 13 = 3`) as well as 52 (`52 / 13 = 4`). But there is no larger number than 13 that divides them both. +You've probably had to learn about this in school at some point. :-) -For example, `gcd(39, 52) = 13` because 13 divides 39 (`39/13 = 3`) as well as 52 (`52/13 = 4`). But there is no larger number than 13 that divides them both. +You probably won't need to use the GCD or LCM in any real-world problems, but it's cool to play around with this ancient algorithm. It was first described by Euklid in his [Elements](http://publicdomainreview.org/collections/the-first-six-books-of-the-elements-of-euclid-1847/) around 300 BC. Rumor has it that he discovered this algorithm while he was hacking on his Commodore 64. -You've probably had to learn about this in school at some point. :-) +## Different Algorithms -The laborious way to find the GCD of two numbers is to first figure out the factors of both numbers, then take the greatest number they have in common. The problem is that factoring numbers is quite difficult, especially when they get larger. (On the plus side, that difficulty is also what keeps your online payments secure.) +This example includes three different algorithms to find the same result. + +### Iterative Euklidean -There is a smarter way to calculate the GCD: Euclid's algorithm. The big idea here is that, +The laborious way to find the GCD of two numbers is to first figure out the factors of both numbers, then take the greatest number they have in common. The problem is that factoring numbers is quite difficult, especially when they get larger. (On the plus side, that difficulty is also what keeps your online payments secure.) +There is a smarter way to calculate the GCD: Euklid's algorithm. The big idea here is that, gcd(a, b) = gcd(b, a % b) @@ -17,29 +22,58 @@ where `a % b` calculates the remainder of `a` divided by `b`. Here is an implementation of this idea in Swift: ```swift -func gcd(_ a: Int, _ b: Int) -> Int { - let r = a % b - if r != 0 { - return gcd(b, r) - } else { +func gcdIterativeEuklid(_ m: Int, _ n: Int) -> Int { + var a: Int = 0 + var b: Int = max(m, n) + var r: Int = min(m, n) + + while r != 0 { + a = b + b = r + r = a % b + } return b - } } ``` Put it in a playground and try it out with these examples: ```swift -gcd(52, 39) // 13 -gcd(228, 36) // 12 -gcd(51357, 3819) // 57 +gcd(52, 39, using: gcdIterativeEuklid) // 13 +gcd(228, 36, using: gcdIterativeEuklid) // 12 +gcd(51357, 3819, using: gcdIterativeEuklid) // 57 +``` + +### Recursive Euklidean + +Here is a slightly different implementation of Euklid's algorithm. Unlike the first version this doesn't use a `while` loop, but leverages recursion. + +```swift +func gcdRecursiveEuklid(_ m: Int, _ n: Int) -> Int { + let r: Int = m % n + if r != 0 { + return gcdRecursiveEuklid(n, r) + } else { + return n + } +} +``` + +Put it in a playground and compare it with the results of the iterative Eulidean gcd. They should return the same results: + +```swift +gcd(52, 39, using: gcdRecursiveEuklid) // 13 +gcd(228, 36, using: gcdRecursiveEuklid) // 12 +gcd(51357, 3819, using: gcdRecursiveEuklid) // 57 ``` -Let's step through the third example: +The `max()` and `min()` at the top of the function make sure we always divide the larger number by the smaller one. + +Let's step through the third example using the *recursive Euklidean algorithm* here: gcd(51357, 3819) -According to Euclid's rule, this is equivalent to, +According to Euklid's rule, this is equivalent to, gcd(3819, 51357 % 3819) = gcd(3819, 1710) @@ -47,17 +81,17 @@ because the remainder of `51357 % 3819` is `1710`. If you work out this division So `gcd(51357, 3819)` is the same as `gcd(3819, 1710)`. That's useful because we can keep simplifying: - gcd(3819, 1710) = gcd(1710, 3819 % 1710) = - gcd(1710, 399) = gcd(399, 1710 % 399) = - gcd(399, 114) = gcd(114, 399 % 114) = - gcd(114, 57) = gcd(57, 114 % 57) = + gcd(3819, 1710) = gcd(1710, 3819 % 1710) = + gcd(1710, 399) = gcd(399, 1710 % 399) = + gcd(399, 114) = gcd(114, 399 % 114) = + gcd(114, 57) = gcd(57, 114 % 57) = gcd(57, 0) And now can't divide any further. The remainder of `114 / 57` is zero because `114 = 57 * 2` exactly. That means we've found the answer: gcd(3819, 51357) = gcd(57, 0) = 57 -So in each step of Euclid's algorithm the numbers become smaller and at some point it ends when one of them becomes zero. +So in each step of Euklid's algorithm the numbers become smaller and at some point it ends when one of them becomes zero. By the way, it's also possible that two numbers have a GCD of 1. They are said to be *relatively prime*. This happens when there is no number that divides them both, for example: @@ -65,44 +99,82 @@ By the way, it's also possible that two numbers have a GCD of 1. They are said t gcd(841, 299) // 1 ``` -Here is a slightly different implementation of Euclid's algorithm. Unlike the first version this doesn't use recursion but only a basic `while` loop. +### Binary Recursive Stein + +The binary GCD algorithm, also known as Stein's algorithm, is an algorithm that computes the greatest common divisor of two nonnegative integers. Stein's algorithm is very similar to the recursive Eulidean algorithm, but uses arithmetical operations, which are simpler for computers to perform, than the conventional Euclidean algorithm does. It replaces division with arithmetic shifts, comparisons, and subtraction. ```swift -func gcd(_ m: Int, _ n: Int) -> Int { - var a = 0 - var b = max(m, n) - var r = min(m, n) - - while r != 0 { - a = b - b = r - r = a % b - } - return b +func gcdBinaryRecursiveStein(_ m: Int, _ n: Int) -> Int { + if let easySolution = findEasySolution(m, n) { return easySolution } + + if (m & 1) == 0 { + // m is even + if (n & 1) == 1 { + // and n is odd + return gcdBinaryRecursiveStein(m >> 1, n) + } else { + // both m and n are even + return gcdBinaryRecursiveStein(m >> 1, n >> 1) << 1 + } + } else if (n & 1) == 0 { + // m is odd, n is even + return gcdBinaryRecursiveStein(m, n >> 1) + } else if (m > n) { + // reduce larger argument + return gcdBinaryRecursiveStein((m - n) >> 1, n) + } else { + // reduce larger argument + return gcdBinaryRecursiveStein((n - m) >> 1, m) + } } ``` -The `max()` and `min()` at the top of the function make sure we always divide the larger number by the smaller one. +Depending on your application and your input expectations, it might be reasonable to also search for an "easy solution" using the other gcd implementations: -## Least Common Multiple +```swift +func findEasySolution(_ m: Int, _ n: Int) -> Int? { + if m == n { + return m + } + if m == 0 { + return n + } + if n == 0 { + return m + } + return nil +} -An idea related to the GCD is the *least common multiple* or LCM. +``` -The least common multiple of two numbers `a` and `b` is the smallest positive integer that is a multiple of both. In other words, the LCM is evenly divisible by `a` and `b`. +Put it in a playground and compare it with the results of the other gcd implementations: -For example: `lcm(2, 3) = 6` because 6 can be divided by 2 and also by 3. +```swift +gcd(52, 39, using: gcdBinaryRecursiveStein) // 13 +gcd(228, 36, using: gcdBinaryRecursiveStein) // 12 +gcd(51357, 3819, using: gcdBinaryRecursiveStein) // 57 +``` + +### Least Common Multiple + +Another algorithm related to the GCD is the *least common multiple* or LCM. + +The least common multiple of two numbers `a` and `b` is the smallest positive integer that is a multiple of both. In other words, the LCM is evenly divisible by `a` and `b`. The example implementation of the LCM takes two numbers and an optional specification which GCD algorithm is used. -We can calculate the LCM using Euclid's algorithm too: +For example: `lcm(2, 3, using: gcdRecursiveEuklid) = 6` , which tells us that 6 is the smallest number that can be devided by 2 as well as 3. - a * b - lcm(a, b) = --------- - gcd(a, b) +We can calculate the LCM using Euklid's algorithm too: + +a * b +lcm(a, b) = --------- +gcd(a, b) In code: ```swift -func lcm(_ m: Int, _ n: Int) -> Int { - return m*n / gcd(m, n) +func lcm(_ m: Int, _ n: Int, using gcdAlgorithm: (Int, Int) -> (Int) = gcdIterativeEuklid) throws -> Int { +guard (m & n) != 0 else { throw LCMError.divisionByZero } +return m / gcdAlgorithm(m, n) * n } ``` @@ -112,6 +184,11 @@ And to try it out in a playground: lcm(10, 8) // 40 ``` -You probably won't need to use the GCD or LCM in any real-world problems, but it's cool to play around with this ancient algorithm. It was first described by Euclid in his [Elements](http://publicdomainreview.org/collections/the-first-six-books-of-the-elements-of-euclid-1847/) around 300 BC. Rumor has it that he discovered this algorithm while he was hacking on his Commodore 64. +## Discussion + +While these algorithms all calculate the same result, comparing their plane complexity might not be enough to decide for one of them, though. The original iterative Euklidean algorithm is easier to understand. The recursive Euklidean and Stein's algorithm, while being generally faster, their runtime is heavily dependend on the environment they are running on. +_If a fast calculation of the gcd is necessary, a runtime comparison for the specific platform and compiler optimization level should be done for the rekursive Euklidean and Stein's algorithm._ *Written for Swift Algorithm Club by Matthijs Hollemans* + +*Extended by Simon C. Krüger* 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.. + + + \ No newline at end of file diff --git a/Genetic/gen.playground/playground.xcworkspace/contents.xcworkspacedata b/Genetic/gen.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Genetic/gen.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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.playground/timeline.xctimeline b/Graph/Graph.playground/timeline.xctimeline index 688276d32..59717856a 100644 --- a/Graph/Graph.playground/timeline.xctimeline +++ b/Graph/Graph.playground/timeline.xctimeline @@ -3,7 +3,7 @@ version = "3.0"> diff --git a/Graph/Graph.xcodeproj/project.pbxproj b/Graph/Graph.xcodeproj/project.pbxproj index 4822d80bb..790f6ac18 100644 --- a/Graph/Graph.xcodeproj/project.pbxproj +++ b/Graph/Graph.xcodeproj/project.pbxproj @@ -158,7 +158,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 49BFA2FC1CDF886B00522D66 = { @@ -251,12 +251,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; @@ -289,6 +291,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -307,12 +310,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; @@ -338,6 +343,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -362,7 +368,7 @@ SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -384,7 +390,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Graph/Graph.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme b/Graph/Graph.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme index a12d85dad..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/Graph/README.markdown b/Graph/README.markdown index d3c8b89e5..1331a07d3 100644 --- a/Graph/README.markdown +++ b/Graph/README.markdown @@ -1,5 +1,8 @@ # Graph +> This topic has been tutorialized [here](https://www.raywenderlich.com/152046/swift-algorithm-club-graphs-adjacency-list) + + A graph looks like the following picture: ![A graph](Images/Graph.png) diff --git a/Hash Set/HashSet.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Hash Set/HashSet.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Hash Set/HashSet.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Hash Set/HashSet.playground/timeline.xctimeline b/Hash Set/HashSet.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Hash Set/HashSet.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Hash Table/HashTable.playground/Sources/HashTable.swift b/Hash Table/HashTable.playground/Sources/HashTable.swift index 2383b8192..f2a1be040 100644 --- a/Hash Table/HashTable.playground/Sources/HashTable.swift +++ b/Hash Table/HashTable.playground/Sources/HashTable.swift @@ -30,7 +30,7 @@ Insert: O(1) O(n) Delete: O(1) O(n) */ -public struct HashTable: CustomStringConvertible { +public struct HashTable { private typealias Element = (key: Key, value: Value) private typealias Bucket = [Element] private var buckets: [Bucket] @@ -41,23 +41,6 @@ public struct HashTable: CustomStringConvertible { /// A Boolean value that indicates whether the hash table is empty. public var isEmpty: Bool { return count == 0 } - /// A string that represents the contents of the hash table. - public var description: String { - let pairs = buckets.flatMap { b in b.map { e in "\(e.key) = \(e.value)" } } - return pairs.joined(separator: ", ") - } - - /// A string that represents the contents of - /// the hash table, suitable for debugging. - public var debugDescription: String { - var str = "" - for (i, bucket) in buckets.enumerated() { - let pairs = bucket.map { e in "\(e.key) = \(e.value)" } - str += "bucket \(i): " + pairs.joined(separator: ", ") + "\n" - } - return str - } - /** Create a hash table with the given capacity. */ @@ -148,6 +131,25 @@ public struct HashTable: CustomStringConvertible { 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) + } +} + +extension HashTable: CustomStringConvertible { + /// A string that represents the contents of the hash table. + public var description: String { + let pairs = buckets.flatMap { b in b.map { e in "\(e.key) = \(e.value)" } } + return pairs.joined(separator: ", ") + } + + /// A string that represents the contents of + /// the hash table, suitable for debugging. + public var debugDescription: String { + var str = "" + for (i, bucket) in buckets.enumerated() { + let pairs = bucket.map { e in "\(e.key) = \(e.value)" } + str += "bucket \(i): " + pairs.joined(separator: ", ") + "\n" + } + return str } } diff --git a/Hash Table/HashTable.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Hash Table/HashTable.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Hash Table/HashTable.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + 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 new file mode 100644 index 000000000..106cdacb0 --- /dev/null +++ b/Hashed Heap/HashedHeap.swift @@ -0,0 +1,250 @@ +// Written by Alejandro Isaza. Adapted from heap implementation written by Kevin Randrup and Matthijs Hollemans. + +/// Heap with an index hash map (dictionary) to speed up lookups by value. +/// +/// A heap keeps elements ordered in a binary tree without the use of pointers. A hashed heap does that as well as +/// having amortized constant lookups by value. This is used in the A* and other heuristic search algorithms to achieve +/// optimal performance. +public struct HashedHeap { + /// The array that stores the heap's nodes. + private(set) var elements = [T]() + + /// Hash mapping from elements to indices in the `elements` array. + private(set) var indices = [T: Int]() + + /// Determines whether this is a max-heap (>) or min-heap (<). + fileprivate var isOrderedBefore: (T, T) -> Bool + + /// Creates an empty hashed heap. + /// + /// The sort function determines whether this is a min-heap or max-heap. For integers, > makes a max-heap, < makes + /// a min-heap. + public init(sort: @escaping (T, T) -> Bool) { + isOrderedBefore = sort + } + + /// Creates a hashed heap from an array. + /// + /// The order of the array does not matter; the elements are inserted into the heap in the order determined by the + /// sort function. + /// + /// - Complexity: O(n) + public init(array: [T], sort: @escaping (T, T) -> Bool) { + isOrderedBefore = sort + build(from: array) + } + + /// Converts an array to a max-heap or min-heap in a bottom-up manner. + /// + /// - Complexity: O(n) + private mutating func build(from array: [T]) { + elements = array + for index in elements.indices { + indices[elements[index]] = index + } + + for i in stride(from: (elements.count/2 - 1), through: 0, by: -1) { + shiftDown(i, heapSize: elements.count) + } + } + + /// Whether the heap is empty. + public var isEmpty: Bool { + return elements.isEmpty + } + + /// The number of elements in the heap. + 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 + /// would take O(n), but for the hashed heap this takes amortized constatn time. + /// + /// - Complexity: Amortized constant + public func index(of element: T) -> Int? { + return indices[element] + } + + /// Returns the maximum value in the heap (for a max-heap) or the minimum value (for a min-heap). + /// + /// - Complexity: O(1) + public func peek() -> T? { + return elements.first + } + + /// Adds a new value to the heap. + /// + /// This reorders the heap so that the max-heap or min-heap property still holds. + /// + /// - Complexity: O(log n) + public mutating func insert(_ value: T) { + elements.append(value) + indices[value] = elements.count - 1 + shiftUp(elements.count - 1) + } + + /// Adds new values to the heap. + public mutating func insert(_ sequence: S) where S.Iterator.Element == T { + for value in sequence { + insert(value) + } + } + + /// Replaces an element in the hash. + /// + /// In a max-heap, the new element should be larger than the old one; in a min-heap it should be smaller. + public mutating func replace(_ value: T, at index: Int) { + guard index < elements.count else { return } + + assert(isOrderedBefore(value, elements[index])) + set(value, at: index) + shiftUp(index) + } + + /// Removes the root node from the heap. + /// + /// For a max-heap, this is the maximum value; for a min-heap it is the minimum value. + /// + /// - Complexity: O(log n) + @discardableResult + public mutating func remove() -> T? { + if elements.isEmpty { + return nil + } else if elements.count == 1 { + return removeLast() + } else { + // Use the last node to replace the first one, then fix the heap by + // shifting this new first node into its proper position. + let value = elements[0] + set(removeLast(), at: 0) + shiftDown() + return value + } + } + + /// Removes an arbitrary node from the heap. + /// + /// You need to know the node's index, which may actually take O(n) steps to find. + /// + /// - Complexity: O(log n). + public mutating func remove(at index: Int) -> T? { + guard index < elements.count else { return nil } + + let size = elements.count - 1 + if index != size { + swapAt(index, size) + shiftDown(index, heapSize: size) + shiftUp(index) + } + return removeLast() + } + + /// Removes all elements from the heap. + public mutating func removeAll() { + elements.removeAll() + indices.removeAll() + } + + /// Removes the last element from the heap. + /// + /// - Complexity: O(1) + public mutating func removeLast() -> T { + guard let value = elements.last else { + preconditionFailure("Trying to remove element from empty heap") + } + indices[value] = nil + return elements.removeLast() + } + + /// Takes a child node and looks at its parents; if a parent is not larger (max-heap) or not smaller (min-heap) + /// than the child, we exchange them. + mutating func shiftUp(_ index: Int) { + var childIndex = index + let child = elements[childIndex] + var parentIndex = self.parentIndex(of: childIndex) + + while childIndex > 0 && isOrderedBefore(child, elements[parentIndex]) { + set(elements[parentIndex], at: childIndex) + childIndex = parentIndex + parentIndex = self.parentIndex(of: childIndex) + } + + set(child, at: childIndex) + } + + mutating func shiftDown() { + shiftDown(0, heapSize: elements.count) + } + + /// Looks at a parent node and makes sure it is still larger (max-heap) or smaller (min-heap) than its childeren. + mutating func shiftDown(_ index: Int, heapSize: Int) { + var parentIndex = index + + while true { + let leftChildIndex = self.leftChildIndex(of: parentIndex) + let rightChildIndex = leftChildIndex + 1 + + // Figure out which comes first if we order them by the sort function: + // the parent, the left child, or the right child. If the parent comes + // first, we're done. If not, that element is out-of-place and we make + // it "float down" the tree until the heap property is restored. + var first = parentIndex + if leftChildIndex < heapSize && isOrderedBefore(elements[leftChildIndex], elements[first]) { + first = leftChildIndex + } + if rightChildIndex < heapSize && isOrderedBefore(elements[rightChildIndex], elements[first]) { + first = rightChildIndex + } + if first == parentIndex { return } + + swapAt(parentIndex, first) + parentIndex = first + } + } + + /// Replaces an element in the heap and updates the indices hash. + private mutating func set(_ newValue: T, at index: Int) { + indices[elements[index]] = nil + elements[index] = newValue + indices[newValue] = index + } + + /// Swap two elements in the heap and update the indices hash. + private mutating func swapAt(_ i: Int, _ j: Int) { + elements.swapAt(i, j) + indices[elements[i]] = i + indices[elements[j]] = j + } + + /// Returns the index of the parent of the element at index i. + /// + /// - Note: The element at index 0 is the root of the tree and has no parent. + @inline(__always) + func parentIndex(of index: Int) -> Int { + return (index - 1) / 2 + } + + /// Returns the index of the left child of the element at index i. + /// + /// - Note: this index can be greater than the heap size, in which case there is no left child. + @inline(__always) + func leftChildIndex(of index: Int) -> Int { + return 2*index + 1 + } + + /// Returns the index of the right child of the element at index i. + /// + /// - Note: this index can be greater than the heap size, in which case there is no right child. + @inline(__always) + func rightChildIndex(of index: Int) -> Int { + return 2*index + 2 + } +} diff --git a/Hashed Heap/README.markdown b/Hashed Heap/README.markdown new file mode 100644 index 000000000..8a4fece02 --- /dev/null +++ b/Hashed Heap/README.markdown @@ -0,0 +1,13 @@ +# Hashed Heap + +A hashed heap is a [heap](../Heap/) with a hash map (also known as a dictionary) to speed up lookup of elements by value. This combination doesn't compromize on time performance but requires extra storage for the hash map. This is mainly used for heuristic search algorihms, in particular A*. + +## The code + +See [HashedHeap.swift](HashedHeap.swift) for the implementation. See [Heap](../Heap/) for a detailed explanation of the basic heap implementation. + +## See also + +[Heap on Wikipedia](https://en.wikipedia.org/wiki/Heap_%28data_structure%29) + +*Written for the Swift Algorithm Club by [Alejandro Isaza](https://github.com/aleph7)* diff --git a/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/project.pbxproj b/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/project.pbxproj new file mode 100644 index 000000000..01f5fae50 --- /dev/null +++ b/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/project.pbxproj @@ -0,0 +1,284 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 7B80C3FC1C77A658003CECC7 /* HashedHeap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B80C3FB1C77A658003CECC7 /* HashedHeap.swift */; }; + 7B80C3FE1C77A65E003CECC7 /* HashedHeapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B80C3FD1C77A65E003CECC7 /* HashedHeapTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 7B2BBC801C779D720067B71D /* Hashed Heap Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Hashed Heap Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B2BBC941C779E7B0067B71D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; + 7B80C3FB1C77A658003CECC7 /* HashedHeap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HashedHeap.swift; path = ../HashedHeap.swift; sourceTree = SOURCE_ROOT; }; + 7B80C3FD1C77A65E003CECC7 /* HashedHeapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HashedHeapTests.swift; 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 /* Hashed Heap Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 7B2BBC831C779D720067B71D /* Tests */ = { + isa = PBXGroup; + children = ( + 7B80C3FB1C77A658003CECC7 /* HashedHeap.swift */, + 7B80C3FD1C77A65E003CECC7 /* HashedHeapTests.swift */, + 7B2BBC941C779E7B0067B71D /* Info.plist */, + ); + name = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7B2BBC7F1C779D720067B71D /* Hashed Heap Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "Hashed Heap Tests" */; + buildPhases = ( + 7B2BBC7C1C779D720067B71D /* Sources */, + 7B2BBC7D1C779D720067B71D /* Frameworks */, + 7B2BBC7E1C779D720067B71D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Hashed Heap Tests"; + productName = TestsTests; + productReference = 7B2BBC801C779D720067B71D /* Hashed Heap Tests.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 "Hashed Heap Tests" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7B2BBC681C779D710067B71D; + productRefGroup = 7B2BBC721C779D710067B71D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7B2BBC7F1C779D720067B71D /* Hashed Heap Tests */, + ); + }; +/* 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 = ( + 7B80C3FE1C77A65E003CECC7 /* HashedHeapTests.swift in Sources */, + 7B80C3FC1C77A658003CECC7 /* 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 = { + 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 = Debug; + }; + 7B2BBC8E1C779D720067B71D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + 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 "Hashed Heap Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7B2BBC871C779D720067B71D /* Debug */, + 7B2BBC881C779D720067B71D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "Hashed Heap Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7B2BBC8D1C779D720067B71D /* Debug */, + 7B2BBC8E1C779D720067B71D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7B2BBC691C779D710067B71D /* Project object */; +} diff --git a/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..6c0ea8493 --- /dev/null +++ b/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/xcshareddata/xcschemes/Hashed Heap Tests.xcscheme b/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/xcshareddata/xcschemes/Hashed Heap Tests.xcscheme new file mode 100644 index 000000000..3f298101d --- /dev/null +++ b/Hashed Heap/Tests/Hashed Heap Tests.xcodeproj/xcshareddata/xcschemes/Hashed Heap Tests.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Hashed Heap/Tests/HashedHeapTests.swift b/Hashed Heap/Tests/HashedHeapTests.swift new file mode 100755 index 000000000..895a21c63 --- /dev/null +++ b/Hashed Heap/Tests/HashedHeapTests.swift @@ -0,0 +1,96 @@ +import Foundation +import XCTest + +private struct Message: Hashable { + let text: String + let priority: Int + + var hashValue: Int { + return text.hashValue + } + + static func == (lhs: Message, rhs: Message) -> Bool { + return lhs.text == rhs.text + } + + static func < (m1: Message, m2: Message) -> Bool { + return m1.priority < m2.priority + } +} + +class HashedHeapTest: XCTestCase { + override func setUp() { + super.setUp() + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + + func testEmpty() { + var queue = HashedHeap(sort: <) + XCTAssertTrue(queue.isEmpty) + XCTAssertEqual(queue.count, 0) + XCTAssertNil(queue.peek()) + XCTAssertNil(queue.remove()) + } + + func testOneElement() { + var queue = HashedHeap(sort: <) + + queue.insert(Message(text: "hello", priority: 100)) + XCTAssertFalse(queue.isEmpty) + XCTAssertEqual(queue.count, 1) + XCTAssertEqual(queue.peek()!.priority, 100) + + let result = queue.remove() + XCTAssertEqual(result!.priority, 100) + XCTAssertTrue(queue.isEmpty) + XCTAssertEqual(queue.count, 0) + XCTAssertNil(queue.peek()) + } + + func testTwoElementsInOrder() { + var queue = HashedHeap(sort: <) + + queue.insert(Message(text: "hello", priority: 100)) + queue.insert(Message(text: "world", priority: 200)) + XCTAssertFalse(queue.isEmpty) + XCTAssertEqual(queue.count, 2) + XCTAssertEqual(queue.peek()!.priority, 100) + + let result1 = queue.remove() + XCTAssertEqual(result1!.priority, 100) + XCTAssertFalse(queue.isEmpty) + XCTAssertEqual(queue.count, 1) + XCTAssertEqual(queue.peek()!.priority, 200) + + let result2 = queue.remove() + XCTAssertEqual(result2!.priority, 200) + XCTAssertTrue(queue.isEmpty) + XCTAssertEqual(queue.count, 0) + XCTAssertNil(queue.peek()) + } + + func testTwoElementsOutOfOrder() { + var queue = HashedHeap(sort: <) + + queue.insert(Message(text: "world", priority: 200)) + queue.insert(Message(text: "hello", priority: 100)) + XCTAssertFalse(queue.isEmpty) + XCTAssertEqual(queue.count, 2) + XCTAssertEqual(queue.peek()!.priority, 100) + + let result1 = queue.remove() + XCTAssertEqual(result1!.priority, 100) + XCTAssertFalse(queue.isEmpty) + XCTAssertEqual(queue.count, 1) + XCTAssertEqual(queue.peek()!.priority, 200) + + let result2 = queue.remove() + XCTAssertEqual(result2!.priority, 200) + XCTAssertTrue(queue.isEmpty) + XCTAssertEqual(queue.count, 0) + XCTAssertNil(queue.peek()) + } +} diff --git a/Hashed Heap/Tests/Info.plist b/Hashed Heap/Tests/Info.plist new file mode 100644 index 000000000..ba72822e8 --- /dev/null +++ b/Hashed Heap/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/HaversineDistance/HaversineDistance.playground/Contents.swift b/HaversineDistance/HaversineDistance.playground/Contents.swift index 449fa366d..ed89b4462 100644 --- a/HaversineDistance/HaversineDistance.playground/Contents.swift +++ b/HaversineDistance/HaversineDistance.playground/Contents.swift @@ -1,25 +1,25 @@ 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 } - + let ahaversin = { (angle: Double) -> Double in return 2*asin(sqrt(angle)) } - + // Converts from degrees to radians let dToR = { (angle: Double) -> Double in - return (angle / 360) * 2 * M_PI + return (angle / 360) * 2 * .pi } - + let lat1 = dToR(la1) let lon1 = dToR(lo1) let lat2 = dToR(la2) let lon2 = dToR(lo2) - + return radius * ahaversin(haversin(lat2 - lat1) + cos(lat1) * cos(lat2) * haversin(lon2 - lon1)) } @@ -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/HeapSort.swift b/Heap Sort/HeapSort.swift index 3fbaab14d..0354817a6 100644 --- a/Heap Sort/HeapSort.swift +++ b/Heap Sort/HeapSort.swift @@ -1,17 +1,17 @@ extension Heap { public mutating func sort() -> [T] { - for i in stride(from: (elements.count - 1), through: 1, by: -1) { - swap(&elements[0], &elements[i]) - shiftDown(0, heapSize: i) + for i in stride(from: (nodes.count - 1), through: 1, by: -1) { + nodes.swapAt(0, i) + shiftDown(from: 0, until: i) } - return elements + return nodes } } /* - Sorts an array using a heap. - Heapsort can be performed in-place, but it is not a stable sort. -*/ + Sorts an array using a heap. + Heapsort can be performed in-place, but it is not a stable sort. + */ public func heapsort(_ a: [T], _ sort: @escaping (T, T) -> Bool) -> [T] { let reverseOrder = { i1, i2 in sort(i2, i1) } var h = Heap(array: a, sort: reverseOrder) 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 Sort/Tests/HeapSortTests.swift b/Heap Sort/Tests/HeapSortTests.swift index e5e847a74..dae1f8a68 100644 --- a/Heap Sort/Tests/HeapSortTests.swift +++ b/Heap Sort/Tests/HeapSortTests.swift @@ -1,25 +1,26 @@ import XCTest class HeapSortTests: XCTestCase { + func testSort() { var h1 = Heap(array: [5, 13, 2, 25, 7, 17, 20, 8, 4], sort: >) let a1 = h1.sort() XCTAssertEqual(a1, [2, 4, 5, 7, 8, 13, 17, 20, 25]) - + let a1_ = heapsort([5, 13, 2, 25, 7, 17, 20, 8, 4], <) XCTAssertEqual(a1_, [2, 4, 5, 7, 8, 13, 17, 20, 25]) - + var h2 = Heap(array: [16, 14, 10, 8, 7, 8, 3, 2, 4, 1], sort: >) let a2 = h2.sort() XCTAssertEqual(a2, [1, 2, 3, 4, 7, 8, 8, 10, 14, 16]) - + let a2_ = heapsort([16, 14, 10, 8, 7, 8, 3, 2, 4, 1], <) XCTAssertEqual(a2_, [1, 2, 3, 4, 7, 8, 8, 10, 14, 16]) - + var h3 = Heap(array: [1, 2, 3, 4, 5, 6], sort: <) let a3 = h3.sort() XCTAssertEqual(a3, [6, 5, 4, 3, 2, 1]) - + let a3_ = heapsort([1, 2, 3, 4, 5, 6], >) XCTAssertEqual(a3_, [6, 5, 4, 3, 2, 1]) } diff --git a/Heap Sort/Tests/Tests.xcodeproj/project.pbxproj b/Heap Sort/Tests/Tests.xcodeproj/project.pbxproj index f7f101cd6..62e6d4e3f 100644 --- a/Heap Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Heap Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -86,17 +86,17 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0820; + LastSwiftMigration = 1010; }; }; }; buildConfigurationList = 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "Tests" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 10.0"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( @@ -145,14 +145,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; @@ -191,14 +199,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; @@ -218,7 +234,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; @@ -227,10 +244,14 @@ buildSettings = { COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + 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 = 4.2; }; name = Debug; }; @@ -239,10 +260,14 @@ buildSettings = { COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + 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 = 4.2; }; name = Release; }; diff --git a/Heap Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Heap Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Heap Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Heap Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Heap Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index dfcf6de42..1608804f9 100644 --- a/Heap Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Heap Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ { + /** The array that stores the heap's nodes. */ - var elements = [T]() - - /** Determines whether this is a max-heap (>) or min-heap (<). */ - fileprivate var isOrderedBefore: (T, T) -> Bool - + var nodes = [T]() + + /** + * Determines how to compare two nodes in the heap. + * Use '>' for a max-heap or '<' for a min-heap, + * or provide a comparing method if the heap is made + * of custom elements, for example tuples. + */ + private var orderCriteria: (T, T) -> Bool + /** * Creates an empty heap. * The sort function determines whether this is a min-heap or max-heap. - * For integers, > makes a max-heap, < makes a min-heap. + * For comparable data types, > makes a max-heap, < makes a min-heap. */ public init(sort: @escaping (T, T) -> Bool) { - self.isOrderedBefore = sort + self.orderCriteria = sort } - + /** * Creates a heap from an array. The order of the array does not matter; * the elements are inserted into the heap in the order determined by the - * sort function. + * sort function. For comparable data types, '>' makes a max-heap, + * '<' makes a min-heap. */ public init(array: [T], sort: @escaping (T, T) -> Bool) { - self.isOrderedBefore = sort - buildHeap(fromArray: array) + self.orderCriteria = sort + configureHeap(from: array) } - - /* - // This version has O(n log n) performance. - private mutating func buildHeap(array: [T]) { - elements.reserveCapacity(array.count) - for value in array { - insert(value) - } - } - */ - + /** - * Converts an array to a max-heap or min-heap in a bottom-up manner. + * Configures the max-heap or min-heap from an array, in a bottom-up manner. * Performance: This runs pretty much in O(n). */ - fileprivate mutating func buildHeap(fromArray array: [T]) { - elements = array - for i in stride(from: (elements.count/2 - 1), through: 0, by: -1) { - shiftDown(i, heapSize: elements.count) + private mutating func configureHeap(from array: [T]) { + nodes = array + for i in stride(from: (nodes.count/2-1), through: 0, by: -1) { + shiftDown(i) } } - + public var isEmpty: Bool { - return elements.isEmpty + return nodes.isEmpty } - + public var count: Int { - return elements.count + return nodes.count } - + /** * Returns the index of the parent of the element at index i. * The element at index 0 is the root of the tree and has no parent. */ - @inline(__always) func parentIndex(ofIndex i: Int) -> Int { + @inline(__always) internal func parentIndex(ofIndex i: Int) -> Int { return (i - 1) / 2 } - + /** * Returns the index of the left child of the element at index i. * Note that this index can be greater than the heap size, in which case * there is no left child. */ - @inline(__always) func leftChildIndex(ofIndex i: Int) -> Int { + @inline(__always) internal func leftChildIndex(ofIndex i: Int) -> Int { return 2*i + 1 } - + /** * Returns the index of the right child of the element at index i. * Note that this index can be greater than the heap size, in which case * there is no right child. */ - @inline(__always) func rightChildIndex(ofIndex i: Int) -> Int { + @inline(__always) internal func rightChildIndex(ofIndex i: Int) -> Int { return 2*i + 2 } - + /** * Returns the maximum value in the heap (for a max-heap) or the minimum * value (for a min-heap). */ public func peek() -> T? { - return elements.first + return nodes.first } - + /** * Adds a new value to the heap. This reorders the heap so that the max-heap * or min-heap property still holds. Performance: O(log n). */ public mutating func insert(_ value: T) { - elements.append(value) - shiftUp(elements.count - 1) + nodes.append(value) + shiftUp(nodes.count - 1) } - + + /** + * Adds a sequence of values to the heap. This reorders the heap so that + * the max-heap or min-heap property still holds. Performance: O(log n). + */ public mutating func insert(_ sequence: S) where S.Iterator.Element == T { for value in sequence { insert(value) } } - + /** - * Allows you to change an element. In a max-heap, the new element should be - * larger than the old one; in a min-heap it should be smaller. + * Allows you to change an element. This reorders the heap so that + * the max-heap or min-heap property still holds. */ public mutating func replace(index i: Int, value: T) { - guard i < elements.count else { return } - - assert(isOrderedBefore(value, elements[i])) - elements[i] = value - shiftUp(i) + guard i < nodes.count else { return } + + remove(at: i) + insert(value) } - + /** * Removes the root node from the heap. For a max-heap, this is the maximum * value; for a min-heap it is the minimum value. Performance: O(log n). */ @discardableResult public mutating func remove() -> T? { - if elements.isEmpty { - return nil - } else if elements.count == 1 { - return elements.removeLast() + guard !nodes.isEmpty else { return nil } + + if nodes.count == 1 { + return nodes.removeLast() } else { // Use the last node to replace the first one, then fix the heap by // shifting this new first node into its proper position. - let value = elements[0] - elements[0] = elements.removeLast() - shiftDown() + let value = nodes[0] + nodes[0] = nodes.removeLast() + shiftDown(0) return value } } - + /** - * Removes an arbitrary node from the heap. Performance: O(log n). You need - * to know the node's index, which may actually take O(n) steps to find. + * Removes an arbitrary node from the heap. Performance: O(log n). + * Note that you need to know the node's index. */ - public mutating func removeAt(_ index: Int) -> T? { - guard index < elements.count else { return nil } - - let size = elements.count - 1 + @discardableResult public mutating func remove(at index: Int) -> T? { + guard index < nodes.count else { return nil } + + let size = nodes.count - 1 if index != size { - swap(&elements[index], &elements[size]) - shiftDown(index, heapSize: size) + nodes.swapAt(index, size) + shiftDown(from: index, until: size) shiftUp(index) } - return elements.removeLast() + return nodes.removeLast() } - + /** * Takes a child node and looks at its parents; if a parent is not larger * (max-heap) or not smaller (min-heap) than the child, we exchange them. */ - mutating func shiftUp(_ index: Int) { + internal mutating func shiftUp(_ index: Int) { var childIndex = index - let child = elements[childIndex] + let child = nodes[childIndex] var parentIndex = self.parentIndex(ofIndex: childIndex) - - while childIndex > 0 && isOrderedBefore(child, elements[parentIndex]) { - elements[childIndex] = elements[parentIndex] + + while childIndex > 0 && orderCriteria(child, nodes[parentIndex]) { + nodes[childIndex] = nodes[parentIndex] childIndex = parentIndex parentIndex = self.parentIndex(ofIndex: childIndex) } - - elements[childIndex] = child + + nodes[childIndex] = child } - - mutating func shiftDown() { - shiftDown(0, heapSize: elements.count) - } - + /** * Looks at a parent node and makes sure it is still larger (max-heap) or * smaller (min-heap) than its childeren. */ - mutating func shiftDown(_ index: Int, heapSize: Int) { - var parentIndex = index - - while true { - let leftChildIndex = self.leftChildIndex(ofIndex: parentIndex) - let rightChildIndex = leftChildIndex + 1 - - // Figure out which comes first if we order them by the sort function: - // the parent, the left child, or the right child. If the parent comes - // first, we're done. If not, that element is out-of-place and we make - // it "float down" the tree until the heap property is restored. - var first = parentIndex - if leftChildIndex < heapSize && isOrderedBefore(elements[leftChildIndex], elements[first]) { - first = leftChildIndex - } - if rightChildIndex < heapSize && isOrderedBefore(elements[rightChildIndex], elements[first]) { - first = rightChildIndex - } - if first == parentIndex { return } - - swap(&elements[parentIndex], &elements[first]) - parentIndex = first + internal mutating func shiftDown(from index: Int, until endIndex: Int) { + let leftChildIndex = self.leftChildIndex(ofIndex: index) + let rightChildIndex = leftChildIndex + 1 + + // Figure out which comes first if we order them by the sort function: + // the parent, the left child, or the right child. If the parent comes + // first, we're done. If not, that element is out-of-place and we make + // it "float down" the tree until the heap property is restored. + var first = index + if leftChildIndex < endIndex && orderCriteria(nodes[leftChildIndex], nodes[first]) { + first = leftChildIndex } + if rightChildIndex < endIndex && orderCriteria(nodes[rightChildIndex], nodes[first]) { + first = rightChildIndex + } + if first == index { return } + + nodes.swapAt(index, first) + shiftDown(from: first, until: endIndex) + } + + internal mutating func shiftDown(_ index: Int) { + shiftDown(from: index, until: nodes.count) } + } // MARK: - Searching extension Heap where T: Equatable { - /** - * Searches the heap for the given element. Performance: O(n). - */ - public func index(of element: T) -> Int? { - return index(of: element, 0) - } - - fileprivate func index(of element: T, _ i: Int) -> Int? { - if i >= count { return nil } - if isOrderedBefore(element, elements[i]) { return nil } - if element == elements[i] { return i } - if let j = index(of: element, self.leftChildIndex(ofIndex: i)) { return j } - if let j = index(of: element, self.rightChildIndex(ofIndex: i)) { return j } + + /** Get the index of a node in the heap. Performance: O(n). */ + public func index(of node: T) -> Int? { + return nodes.index(where: { $0 == node }) + } + + /** 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) + } return nil } + } diff --git a/Heap/README.markdown b/Heap/README.markdown old mode 100644 new mode 100755 index bb7a785a5..fee881129 --- a/Heap/README.markdown +++ b/Heap/README.markdown @@ -1,5 +1,7 @@ # Heap +> This topic has been tutorialized [here](https://www.raywenderlich.com/160631/swift-algorithm-club-heap-and-priority-queue-data-structure) + A heap is a [binary tree](../Binary%20Tree/) inside an array, so it does not use parent/child pointers. A heap is sorted based on the "heap property" that determines the order of the nodes in the tree. Common uses for heap: @@ -25,9 +27,9 @@ As a result of this heap property, a max-heap always stores its largest item at > **Note:** The root of the heap has the maximum or minimum element, but the sort order of other elements are not predictable. For example, the maximum element is always at index 0 in a max-heap, but the minimum element isn’t necessarily the last one. -- the only guarantee you have is that it is one of the leaf nodes, but not which one. -## How does a heap can be compared to regular trees? +## 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. @@ -36,7 +38,7 @@ A heap is not a replacement for a binary search tree, and there are similarities **Balancing.** A binary search tree must be "balanced" so that most operations have **O(log n)** performance. You can either insert and delete your data in a random order or use something like an [AVL tree](../AVL%20Tree/) or [red-black tree](../Red-Black%20Tree/), but with heaps we don't actually need the entire tree to be sorted. We just want the heap property to be fulfilled, so balancing isn't an issue. Because of the way the heap is structured, heaps can guarantee **O(log n)** performance. -**Searching.** Whereas searching is fast in a binary tree, it is slow in a heap. Searching isn't a top priority in a heap since the purpose of a heap is to put the largest (or smallest) node at the front and to allow relatively fast inserts and deletes. +**Searching.** Whereas searching is fast in a binary tree, it is slow in a heap. Searching isn't a top priority in a heap since the purpose of a heap is to put the largest (or smallest) node at the front and to allow relatively fast inserts and deletes. ## The tree inside an array @@ -80,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: @@ -146,7 +148,7 @@ There are two primitive operations necessary to make sure the heap is a valid ma Shifting up or down is a recursive procedure that takes **O(log n)** time. -Here are other operations that are built on primitive operations: +Here are other operations that are built on primitive operations: - `insert(value)`: Adds the new element to the end of the heap and then uses `shiftUp()` to fix the heap. @@ -162,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)**. @@ -188,7 +190,7 @@ The `(16)` was added to the first available space on the last row. Unfortunately, the heap property is no longer satisfied because `(2)` is above `(16)`, and we want higher numbers above lower numbers. (This is a max-heap.) -To restore the heap property, we swap `(16)` and `(2)`. +To restore the heap property, we swap `(16)` and `(2)`. ![The heap before insertion](Images/Insert2.png) @@ -212,7 +214,7 @@ What happens to the empty spot at the top? ![The root is gone](Images/Remove1.png) -When inserting, we put the new value at the end of the array. Here, we do the opposite: we take the last object we have, stick it up on top of the tree, and restore the heap property. +When inserting, we put the new value at the end of the array. Here, we do the opposite: we take the last object we have, stick it up on top of the tree, and restore the heap property. ![The last node goes to the root](Images/Remove2.png) @@ -224,7 +226,7 @@ Keep shifting down until the node does not have any children or it is larger tha ![The last node goes to the root](Images/Remove4.png) -The time required for shifting all the way down is proportional to the height of the tree which takes **O(log n)** time. +The time required for shifting all the way down is proportional to the height of the tree which takes **O(log n)** time. > **Note:** `shiftUp()` and `shiftDown()` can only fix one out-of-place element at a time. If there are multiple elements in the wrong place, you need to call these functions once for each of those elements. @@ -277,7 +279,7 @@ In code: ```swift private mutating func buildHeap(fromArray array: [T]) { elements = array - for i in (elements.count/2 - 1).stride(through: 0, by: -1) { + for i in stride(from: (nodes.count/2-1), through: 0, by: -1) { shiftDown(index: i, heapSize: elements.count) } } diff --git a/Heap/Tests/HeapTests.swift b/Heap/Tests/HeapTests.swift old mode 100644 new mode 100755 index cac934406..ecf143778 --- a/Heap/Tests/HeapTests.swift +++ b/Heap/Tests/HeapTests.swift @@ -6,31 +6,31 @@ import XCTest class HeapTests: XCTestCase { - + fileprivate func verifyMaxHeap(_ h: Heap) -> Bool { for i in 0.. 0 && h.elements[parent] < h.elements[i] { return false } + if left < h.count && h.nodes[i] < h.nodes[left] { return false } + if right < h.count && h.nodes[i] < h.nodes[right] { return false } + if i > 0 && h.nodes[parent] < h.nodes[i] { return false } } return true } - + fileprivate func verifyMinHeap(_ h: Heap) -> Bool { for i in 0.. h.elements[left] { return false } - if right < h.count && h.elements[i] > h.elements[right] { return false } - if i > 0 && h.elements[parent] > h.elements[i] { return false } + if left < h.count && h.nodes[i] > h.nodes[left] { return false } + if right < h.count && h.nodes[i] > h.nodes[right] { return false } + if i > 0 && h.nodes[parent] > h.nodes[i] { return false } } return true } - + fileprivate func isPermutation(_ array1: [Int], _ array2: [Int]) -> Bool { var a1 = array1 var a2 = array2 @@ -45,7 +45,7 @@ class HeapTests: XCTestCase { } return a2.count == 0 } - + func testEmptyHeap() { var heap = Heap(sort: <) XCTAssertTrue(heap.isEmpty) @@ -53,7 +53,7 @@ class HeapTests: XCTestCase { XCTAssertNil(heap.peek()) XCTAssertNil(heap.remove()) } - + func testIsEmpty() { var heap = Heap(sort: >) XCTAssertTrue(heap.isEmpty) @@ -62,14 +62,14 @@ class HeapTests: XCTestCase { heap.remove() XCTAssertTrue(heap.isEmpty) } - + func testCount() { var heap = Heap(sort: >) XCTAssertEqual(0, heap.count) heap.insert(1) XCTAssertEqual(1, heap.count) } - + func testMaxHeapOneElement() { let heap = Heap(array: [10], sort: >) XCTAssertTrue(verifyMaxHeap(heap)) @@ -78,89 +78,89 @@ class HeapTests: XCTestCase { XCTAssertEqual(heap.count, 1) XCTAssertEqual(heap.peek()!, 10) } - + func testCreateMaxHeap() { let h1 = Heap(array: [1, 2, 3, 4, 5, 6, 7], sort: >) XCTAssertTrue(verifyMaxHeap(h1)) XCTAssertFalse(verifyMinHeap(h1)) - XCTAssertEqual(h1.elements, [7, 5, 6, 4, 2, 1, 3]) + XCTAssertEqual(h1.nodes, [7, 5, 6, 4, 2, 1, 3]) XCTAssertFalse(h1.isEmpty) XCTAssertEqual(h1.count, 7) XCTAssertEqual(h1.peek()!, 7) - + let h2 = Heap(array: [7, 6, 5, 4, 3, 2, 1], sort: >) XCTAssertTrue(verifyMaxHeap(h2)) XCTAssertFalse(verifyMinHeap(h2)) - XCTAssertEqual(h2.elements, [7, 6, 5, 4, 3, 2, 1]) + XCTAssertEqual(h2.nodes, [7, 6, 5, 4, 3, 2, 1]) XCTAssertFalse(h2.isEmpty) XCTAssertEqual(h2.count, 7) XCTAssertEqual(h2.peek()!, 7) - + let h3 = Heap(array: [4, 1, 3, 2, 16, 9, 10, 14, 8, 7], sort: >) XCTAssertTrue(verifyMaxHeap(h3)) XCTAssertFalse(verifyMinHeap(h3)) - XCTAssertEqual(h3.elements, [16, 14, 10, 8, 7, 9, 3, 2, 4, 1]) + XCTAssertEqual(h3.nodes, [16, 14, 10, 8, 7, 9, 3, 2, 4, 1]) XCTAssertFalse(h3.isEmpty) XCTAssertEqual(h3.count, 10) XCTAssertEqual(h3.peek()!, 16) - + let h4 = Heap(array: [27, 17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0], sort: >) XCTAssertTrue(verifyMaxHeap(h4)) XCTAssertFalse(verifyMinHeap(h4)) - XCTAssertEqual(h4.elements, [27, 17, 10, 16, 13, 9, 1, 5, 7, 12, 4, 8, 3, 0]) + XCTAssertEqual(h4.nodes, [27, 17, 10, 16, 13, 9, 1, 5, 7, 12, 4, 8, 3, 0]) XCTAssertFalse(h4.isEmpty) XCTAssertEqual(h4.count, 14) XCTAssertEqual(h4.peek()!, 27) } - + func testCreateMinHeap() { let h1 = Heap(array: [1, 2, 3, 4, 5, 6, 7], sort: <) XCTAssertTrue(verifyMinHeap(h1)) XCTAssertFalse(verifyMaxHeap(h1)) - XCTAssertEqual(h1.elements, [1, 2, 3, 4, 5, 6, 7]) + XCTAssertEqual(h1.nodes, [1, 2, 3, 4, 5, 6, 7]) XCTAssertFalse(h1.isEmpty) XCTAssertEqual(h1.count, 7) XCTAssertEqual(h1.peek()!, 1) - + let h2 = Heap(array: [7, 6, 5, 4, 3, 2, 1], sort: <) XCTAssertTrue(verifyMinHeap(h2)) XCTAssertFalse(verifyMaxHeap(h2)) - XCTAssertEqual(h2.elements, [1, 3, 2, 4, 6, 7, 5]) + XCTAssertEqual(h2.nodes, [1, 3, 2, 4, 6, 7, 5]) XCTAssertFalse(h2.isEmpty) XCTAssertEqual(h2.count, 7) XCTAssertEqual(h2.peek()!, 1) - + let h3 = Heap(array: [4, 1, 3, 2, 16, 9, 10, 14, 8, 7], sort: <) XCTAssertTrue(verifyMinHeap(h3)) XCTAssertFalse(verifyMaxHeap(h3)) - XCTAssertEqual(h3.elements, [1, 2, 3, 4, 7, 9, 10, 14, 8, 16]) + XCTAssertEqual(h3.nodes, [1, 2, 3, 4, 7, 9, 10, 14, 8, 16]) XCTAssertFalse(h3.isEmpty) XCTAssertEqual(h3.count, 10) XCTAssertEqual(h3.peek()!, 1) - + let h4 = Heap(array: [27, 17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0], sort: <) XCTAssertTrue(verifyMinHeap(h4)) XCTAssertFalse(verifyMaxHeap(h4)) - XCTAssertEqual(h4.elements, [0, 4, 1, 5, 12, 8, 3, 16, 7, 17, 13, 10, 9, 27]) + XCTAssertEqual(h4.nodes, [0, 4, 1, 5, 12, 8, 3, 16, 7, 17, 13, 10, 9, 27]) XCTAssertFalse(h4.isEmpty) XCTAssertEqual(h4.count, 14) XCTAssertEqual(h4.peek()!, 0) } - - func testCreateMaxHeapEqualElements() { + + func testCreateMaxHeapEqualnodes() { let heap = Heap(array: [1, 1, 1, 1, 1], sort: >) XCTAssertTrue(verifyMaxHeap(heap)) XCTAssertTrue(verifyMinHeap(heap)) - XCTAssertEqual(heap.elements, [1, 1, 1, 1, 1]) + XCTAssertEqual(heap.nodes, [1, 1, 1, 1, 1]) } - - func testCreateMinHeapEqualElements() { + + func testCreateMinHeapEqualnodes() { let heap = Heap(array: [1, 1, 1, 1, 1], sort: <) XCTAssertTrue(verifyMinHeap(heap)) XCTAssertTrue(verifyMaxHeap(heap)) - XCTAssertEqual(heap.elements, [1, 1, 1, 1, 1]) + XCTAssertEqual(heap.nodes, [1, 1, 1, 1, 1]) } - + fileprivate func randomArray(_ n: Int) -> [Int] { var a = [Int]() for _ in 0..) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [100, 50, 70, 10, 20, 60, 65]) - + XCTAssertEqual(h.nodes, [100, 50, 70, 10, 20, 60, 65]) + //test index out of bounds - let v = h.removeAt(10) + let v = h.remove(at: 10) XCTAssertEqual(v, nil) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [100, 50, 70, 10, 20, 60, 65]) - - let v1 = h.removeAt(5) + XCTAssertEqual(h.nodes, [100, 50, 70, 10, 20, 60, 65]) + + let v1 = h.remove(at: 5) XCTAssertEqual(v1, 60) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [100, 50, 70, 10, 20, 65]) - - let v2 = h.removeAt(4) + XCTAssertEqual(h.nodes, [100, 50, 70, 10, 20, 65]) + + let v2 = h.remove(at: 4) XCTAssertEqual(v2, 20) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [100, 65, 70, 10, 50]) - - let v3 = h.removeAt(4) + XCTAssertEqual(h.nodes, [100, 65, 70, 10, 50]) + + let v3 = h.remove(at: 4) XCTAssertEqual(v3, 50) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [100, 65, 70, 10]) - - let v4 = h.removeAt(0) + XCTAssertEqual(h.nodes, [100, 65, 70, 10]) + + let v4 = h.remove(at: 0) XCTAssertEqual(v4, 100) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [70, 65, 10]) - + XCTAssertEqual(h.nodes, [70, 65, 10]) + XCTAssertEqual(h.peek()!, 70) let v5 = h.remove() XCTAssertEqual(v5, 70) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [65, 10]) - + XCTAssertEqual(h.nodes, [65, 10]) + XCTAssertEqual(h.peek()!, 65) let v6 = h.remove() XCTAssertEqual(v6, 65) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [10]) - + XCTAssertEqual(h.nodes, [10]) + XCTAssertEqual(h.peek()!, 10) let v7 = h.remove() XCTAssertEqual(v7, 10) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, []) - + XCTAssertEqual(h.nodes, []) + XCTAssertNil(h.peek()) } - + func testRemoveEmpty() { var heap = Heap(sort: >) let removed = heap.remove() XCTAssertNil(removed) } - + func testRemoveRoot() { var h = Heap(array: [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1], sort: >) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1]) + XCTAssertEqual(h.nodes, [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1]) XCTAssertEqual(h.peek()!, 15) let v = h.remove() XCTAssertEqual(v, 15) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [13, 12, 9, 5, 6, 8, 7, 4, 0, 1, 2]) + XCTAssertEqual(h.nodes, [13, 12, 9, 5, 6, 8, 7, 4, 0, 1, 2]) } - + func testRemoveRandomItems() { for n in 1...40 { var a = randomArray(n) var h = Heap(array: a, sort: >) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertTrue(isPermutation(a, h.elements)) - + XCTAssertTrue(isPermutation(a, h.nodes)) + let m = (n + 1)/2 for k in 1...m { let i = Int(arc4random_uniform(UInt32(n - k + 1))) - let v = h.removeAt(i)! + let v = h.remove(at: i)! let j = a.index(of: v)! a.remove(at: j) - + XCTAssertTrue(verifyMaxHeap(h)) XCTAssertEqual(h.count, a.count) XCTAssertEqual(h.count, n - k) - XCTAssertTrue(isPermutation(a, h.elements)) + XCTAssertTrue(isPermutation(a, h.nodes)) } } } - + func testInsert() { var h = Heap(array: [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1], sort: >) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1]) - + XCTAssertEqual(h.nodes, [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1]) + h.insert(10) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [15, 13, 10, 5, 12, 9, 7, 4, 0, 6, 2, 1, 8]) + XCTAssertEqual(h.nodes, [15, 13, 10, 5, 12, 9, 7, 4, 0, 6, 2, 1, 8]) } - + func testInsertArrayAndRemove() { var heap = Heap(sort: >) heap.insert([1, 3, 2, 7, 5, 9]) - XCTAssertEqual(heap.elements, [9, 5, 7, 1, 3, 2]) - + XCTAssertEqual(heap.nodes, [9, 5, 7, 1, 3, 2]) + XCTAssertEqual(9, heap.remove()) XCTAssertEqual(7, heap.remove()) XCTAssertEqual(5, heap.remove()) @@ -305,19 +305,17 @@ class HeapTests: XCTestCase { XCTAssertEqual(1, heap.remove()) XCTAssertNil(heap.remove()) } - + func testReplace() { var h = Heap(array: [16, 14, 10, 8, 7, 9, 3, 2, 4, 1], sort: >) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [16, 14, 10, 8, 7, 9, 3, 2, 4, 1]) - + h.replace(index: 5, value: 13) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [16, 14, 13, 8, 7, 10, 3, 2, 4, 1]) - + //test index out of bounds h.replace(index: 20, value: 2) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [16, 14, 13, 8, 7, 10, 3, 2, 4, 1]) } + } diff --git a/Heap/Tests/Tests.xcodeproj/project.pbxproj b/Heap/Tests/Tests.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index 22112fa60..0ab48ba3b --- a/Heap/Tests/Tests.xcodeproj/project.pbxproj +++ b/Heap/Tests/Tests.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -83,17 +83,17 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0820; + LastSwiftMigration = 1010; }; }; }; buildConfigurationList = 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "Tests" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 10.0"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( @@ -141,14 +141,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; @@ -187,14 +195,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; @@ -214,7 +230,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; @@ -223,10 +240,14 @@ buildSettings = { COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + 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 = 4.2; }; name = Debug; }; @@ -235,10 +256,14 @@ buildSettings = { COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + 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 = 4.2; }; name = Release; }; diff --git a/Heap/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Heap/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata old mode 100644 new mode 100755 diff --git a/Heap/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Heap/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Heap/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Heap/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Heap/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme old mode 100644 new mode 100755 index 14f27f777..1608804f9 --- a/Heap/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Heap/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ { public mutating func removeAtIndex(i: Int) -> T? { let size = elements.count - 1 if i != size { - swap(&elements[i], &elements[size]) + elements.swapAt(i, size) shiftDown(index: i, heapSize: size) shiftUp(index: i) } @@ -196,7 +196,7 @@ public struct Heap { } if first == parentIndex { return } - swap(&elements[parentIndex], &elements[first]) + elements.swapAt(parentIndex, first) parentIndex = first } } diff --git a/Huffman Coding/Huffman.playground/timeline.xctimeline b/Huffman Coding/Huffman.playground/timeline.xctimeline index 2b2555bb1..5588b0fdd 100644 --- a/Huffman Coding/Huffman.playground/timeline.xctimeline +++ b/Huffman Coding/Huffman.playground/timeline.xctimeline @@ -3,12 +3,12 @@ version = "3.0"> diff --git a/Images/DataStructuresAndAlgorithmsInSwiftBook.png b/Images/DataStructuresAndAlgorithmsInSwiftBook.png new file mode 100644 index 000000000..f8de0ba72 Binary files /dev/null and b/Images/DataStructuresAndAlgorithmsInSwiftBook.png differ diff --git a/Insertion Sort/InsertionSort.playground/Contents.swift b/Insertion Sort/InsertionSort.playground/Contents.swift index df1822b8f..e1d3a312e 100644 --- a/Insertion Sort/InsertionSort.playground/Contents.swift +++ b/Insertion Sort/InsertionSort.playground/Contents.swift @@ -1,19 +1,46 @@ //: Playground - noun: a place where people can play +/// Performs the Insertion sort algorithm to a given array +/// +/// - Parameters: +/// - array: the array of elements to be sorted +/// - 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] { - 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 } + + 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 +/// - Returns: a sorted array containing the same elements +func insertionSort(_ array: [T]) -> [T] { + var sortedArray = array + for index in 1.. 0 && temp < sortedArray[currentIndex - 1] { + sortedArray[currentIndex] = sortedArray[currentIndex - 1] + currentIndex -= 1 + } + sortedArray[currentIndex] = temp + } + return sortedArray } let list = [ 10, -1, 3, 9, 2, 27, 8, 5, 1, 3, 0, 26 ] -insertionSort(list, <) -insertionSort(list, >) +print(insertionSort(list)) +print(insertionSort(list, <)) +print(insertionSort(list, >)) diff --git a/Insertion Sort/InsertionSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Insertion Sort/InsertionSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Insertion Sort/InsertionSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Insertion Sort/InsertionSort.playground/timeline.xctimeline b/Insertion Sort/InsertionSort.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Insertion Sort/InsertionSort.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Insertion Sort/InsertionSort.swift b/Insertion Sort/InsertionSort.swift index 5d8a9565b..5f0b6c2b4 100644 --- a/Insertion Sort/InsertionSort.swift +++ b/Insertion Sort/InsertionSort.swift @@ -1,15 +1,40 @@ +/// Performs the Insertion sort algorithm to a given array +/// +/// - Parameters: +/// - array: the array of elements to be sorted +/// - 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 } + 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 + } + return sortedArray +} + +/// Performs the Insertion sort algorithm to a given array +/// +/// - 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 && isOrderedBefore(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 + } + sortedArray[index] = temp } - a[y] = temp - } - return a + return sortedArray } diff --git a/Insertion Sort/README.markdown b/Insertion Sort/README.markdown index 286d92ed1..f7b933b92 100644 --- a/Insertion Sort/README.markdown +++ b/Insertion Sort/README.markdown @@ -91,16 +91,18 @@ 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 - swap(&a[y - 1], &a[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 } + + ``` Put this code in a playground and test it like so: @@ -112,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. @@ -152,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/Insertion Sort/Tests/Tests.xcodeproj/project.pbxproj b/Insertion Sort/Tests/Tests.xcodeproj/project.pbxproj index ee3ca1cae..e9ea3bc53 100644 --- a/Insertion Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Insertion Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -86,12 +86,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 1000; }; }; }; @@ -145,14 +145,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; @@ -191,14 +199,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; @@ -230,7 +246,8 @@ 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_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -242,7 +259,8 @@ 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_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Insertion Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Insertion Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Insertion Sort/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Insertion Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Insertion Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 14f27f777..afd69e6a7 100644 --- a/Insertion Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Insertion Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ (_ elements: inout [T], _ index: Int, _ range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + let countToIndex = elements.distance(from: range.lowerBound, to: index) + let countFromIndex = elements.distance(from: index, to: range.upperBound) + + guard countToIndex + 1 < countFromIndex else { return } + + let left = elements.index(index, offsetBy: countToIndex + 1) + var largest = index + if areInIncreasingOrder(elements[largest], elements[left]) { + largest = left + } + + if countToIndex + 2 < countFromIndex { + let right = elements.index(after: left) + if areInIncreasingOrder(elements[largest], elements[right]) { + largest = right + } + } + + if largest != index { + elements.swapAt(index, largest) + shiftDown(&elements, largest, range, by: areInIncreasingOrder) + } + +} + +private func heapify(_ list: inout [T], _ range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + let root = range.lowerBound + var node = list.index(root, offsetBy: list.distance(from: range.lowerBound, to: range.upperBound)/2) + + while node != root { + list.formIndex(before: &node) + shiftDown(&list, node, range, by: areInIncreasingOrder) + } +} + +public func heapsort(for array: inout [T], range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + var hi = range.upperBound + let lo = range.lowerBound + heapify(&array, range, by: areInIncreasingOrder) + array.formIndex(before: &hi) + + while hi != lo { + array.swapAt(lo, hi) + shiftDown(&array, lo, lo..(for array: inout [T], range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + guard !range.isEmpty else { return } + + let start = range.lowerBound + var sortedEnd = start + + array.formIndex(after: &sortedEnd) + while sortedEnd != range.upperBound { + let x = array[sortedEnd] + + var i = sortedEnd + repeat { + let predecessor = array[array.index(before: i)] + + guard areInIncreasingOrder(x, predecessor) else { break } + array[i] = predecessor + array.formIndex(before: &i) + } while i != start + + if i != sortedEnd { + array[i] = x + } + array.formIndex(after: &sortedEnd) + } + +} diff --git a/Introsort/IntroSort.swift b/Introsort/IntroSort.swift new file mode 100644 index 000000000..866b2603f --- /dev/null +++ b/Introsort/IntroSort.swift @@ -0,0 +1,32 @@ +import Foundation + +public func introsort(_ array: inout [T], by areInIncreasingOrder: (T, T) -> Bool) { + //The depth limit is as best practice 2 * log( n ) + let depthLimit = 2 * floor(log2(Double(array.count))) + + introSortImplementation(for: &array, range: 0..(for array: inout [T], range: Range, depthLimit: Int, by areInIncreasingOrder: (T, T) -> Bool) { + if array.distance(from: range.lowerBound, to: range.upperBound) < 20 { + //if the partition count is less than 20 we can sort it using insertion sort. This algorithm in fact performs well on collections + //of this size, plus, at this point is quite probable that the quisksort part of the algorithm produced a partition which is + //nearly sorted. As we knoe insertion sort tends to O( n ) if this is the case. + insertionSort(for: &array, range: range, by: areInIncreasingOrder) + } else if depthLimit == 0 { + //If we reached the depth limit for this recursion branch, it's possible that we are hitting quick sort's worst case. + //Since quicksort degrades to O( n^2 ) in its worst case we stop using quicksort for this recursion branch and we switch to heapsort. + //Our preference remains quicksort, and we hope to be rare to see this condition to be true + heapsort(for: &array, range: range, by: areInIncreasingOrder) + } else { + //By default we use quicksort to sort our collection. The partition index method chose a pivot, and puts all the + //elements less than pivot on the left, and the ones bigger than pivot on the right. At the end of the operation the + //position of the pivot in the array is returned so that we can form the two partitions. + let partIdx = partitionIndex(for: &array, subRange: range, by: areInIncreasingOrder) + + //We can recursively call introsort implementation, decreasing the depthLimit for the left partition and the right partition. + introSortImplementation(for: &array, range: range.lowerBound..(_ array: inout [T], by areInIncreasingOrder: (T, T) -> Bool) { + //The depth limit is as best practice 2 * log( n ) + let depthLimit = 2 * floor(log2(Double(array.count))) + + introSortImplementation(for: &array, range: 0..(for array: inout [T], range: Range, depthLimit: Int, by areInIncreasingOrder: (T, T) -> Bool) { + if array.distance(from: range.lowerBound, to: range.upperBound) < 20 { + //if the partition count is less than 20 we can sort it using insertion sort. This algorithm in fact performs well on collections + //of this size, plus, at this point is quite probable that the quisksort part of the algorithm produced a partition which is + //nearly sorted. As we knoe insertion sort tends to O( n ) if this is the case. + insertionSort(for: &array, range: range, by: areInIncreasingOrder) + } else if depthLimit == 0 { + //If we reached the depth limit for this recursion branch, it's possible that we are hitting quick sort's worst case. + //Since quicksort degrades to O( n^2 ) in its worst case we stop using quicksort for this recursion branch and we switch to heapsort. + //Our preference remains quicksort, and we hope to be rare to see this condition to be true + heapsort(for: &array, range: range, by: areInIncreasingOrder) + } else { + //By default we use quicksort to sort our collection. The partition index method chose a pivot, and puts all the + //elements less than pivot on the left, and the ones bigger than pivot on the right. At the end of the operation the + //position of the pivot in the array is returned so that we can form the two partitions. + let partIdx = partitionIndex(for: &array, subRange: range, by: areInIncreasingOrder) + + //We can recursively call introsort implementation, decreasing the depthLimit for the left partition and the right partition. + introSortImplementation(for: &array, range: range.lowerBound..(_ elements: inout [T], _ index: Int, _ range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + let countToIndex = elements.distance(from: range.lowerBound, to: index) + let countFromIndex = elements.distance(from: index, to: range.upperBound) + + guard countToIndex + 1 < countFromIndex else { return } + + let left = elements.index(index, offsetBy: countToIndex + 1) + var largest = index + if areInIncreasingOrder(elements[largest], elements[left]) { + largest = left + } + + if countToIndex + 2 < countFromIndex { + let right = elements.index(after: left) + if areInIncreasingOrder(elements[largest], elements[right]) { + largest = right + } + } + + if largest != index { + elements.swapAt(index, largest) + shiftDown(&elements, largest, range, by: areInIncreasingOrder) + } + +} + +private func heapify(_ list: inout [T], _ range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + let root = range.lowerBound + var node = list.index(root, offsetBy: list.distance(from: range.lowerBound, to: range.upperBound)/2) + + while node != root { + list.formIndex(before: &node) + shiftDown(&list, node, range, by: areInIncreasingOrder) + } +} + +public func heapsort(for array: inout [T], range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + var hi = range.upperBound + let lo = range.lowerBound + heapify(&array, range, by: areInIncreasingOrder) + array.formIndex(before: &hi) + + while hi != lo { + array.swapAt(lo, hi) + shiftDown(&array, lo, lo..(for array: inout [T], range: Range, by areInIncreasingOrder: (T, T) -> Bool) { + guard !range.isEmpty else { return } + + let start = range.lowerBound + var sortedEnd = start + + array.formIndex(after: &sortedEnd) + while sortedEnd != range.upperBound { + let x = array[sortedEnd] + + var i = sortedEnd + repeat { + let predecessor = array[array.index(before: i)] + + guard areInIncreasingOrder(x, predecessor) else { break } + array[i] = predecessor + array.formIndex(before: &i) + } while i != start + + if i != sortedEnd { + array[i] = x + } + array.formIndex(after: &sortedEnd) + } + +} diff --git a/Introsort/Introsort.playground/Sources/Partition.swift b/Introsort/Introsort.playground/Sources/Partition.swift new file mode 100644 index 000000000..e24636da7 --- /dev/null +++ b/Introsort/Introsort.playground/Sources/Partition.swift @@ -0,0 +1,43 @@ +import Foundation + +public func partitionIndex(for elements: inout [T], subRange range: Range, by areInIncreasingOrder: (T, T) -> Bool) -> Int { + var lo = range.lowerBound + var hi = elements.index(before: range.upperBound) + + // Sort the first, middle, and last elements, then use the middle value + // as the pivot for the partition. + let half = elements.distance(from: lo, to: hi) / 2 + let mid = elements.index(lo, offsetBy: half) + + sort3(in: &elements, a: lo, b: mid, c: hi, by: areInIncreasingOrder) + let pivot = elements[mid] + + while true { + elements.formIndex(after: &lo) + guard findLo(in: elements, pivot: pivot, from: &lo, to: hi, by: areInIncreasingOrder) else { break } + elements.formIndex(before: &hi) + guard findHi(in: elements, pivot: pivot, from: lo, to: &hi, by: areInIncreasingOrder) else { break } + elements.swapAt(lo, hi) + } + + + return lo +} + +private func findLo(in array: [T], pivot: T, from lo: inout Int, to hi: Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool { + while lo != hi { + if !areInIncreasingOrder(array[lo], pivot) { + return true + } + array.formIndex(after: &lo) + } + return false +} + +private func findHi(in array: [T], pivot: T, from lo: Int, to hi: inout Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool { + while hi != lo { + if areInIncreasingOrder(array[hi], pivot) { return true } + array.formIndex(before: &hi) + } + return false +} diff --git a/Introsort/Introsort.playground/Sources/Randomize.swift b/Introsort/Introsort.playground/Sources/Randomize.swift new file mode 100644 index 000000000..6d1d57132 --- /dev/null +++ b/Introsort/Introsort.playground/Sources/Randomize.swift @@ -0,0 +1,9 @@ +import Foundation + +public func randomize(n: Int) -> [Int] { + var unsorted = [Int]() + for _ in 0..(in array: inout [T], a: Int, b: Int, c: Int, by areInIncreasingOrder: (T, T) -> Bool) { + switch (areInIncreasingOrder(array[b], array[a]), + areInIncreasingOrder(array[c], array[b])) { + case (false, false): break + case (true, true): array.swapAt(a, c) + case (true, false): + array.swapAt(a, b) + + if areInIncreasingOrder(array[c], array[b]) { + array.swapAt(b, c) + } + case (false, true): + array.swapAt(b, c) + + if areInIncreasingOrder(array[b], array[a]) { + array.swapAt(a, b) + } + } +} diff --git a/Introsort/Introsort.playground/contents.xcplayground b/Introsort/Introsort.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Introsort/Introsort.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Introsort/Partition.swift b/Introsort/Partition.swift new file mode 100644 index 000000000..e24636da7 --- /dev/null +++ b/Introsort/Partition.swift @@ -0,0 +1,43 @@ +import Foundation + +public func partitionIndex(for elements: inout [T], subRange range: Range, by areInIncreasingOrder: (T, T) -> Bool) -> Int { + var lo = range.lowerBound + var hi = elements.index(before: range.upperBound) + + // Sort the first, middle, and last elements, then use the middle value + // as the pivot for the partition. + let half = elements.distance(from: lo, to: hi) / 2 + let mid = elements.index(lo, offsetBy: half) + + sort3(in: &elements, a: lo, b: mid, c: hi, by: areInIncreasingOrder) + let pivot = elements[mid] + + while true { + elements.formIndex(after: &lo) + guard findLo(in: elements, pivot: pivot, from: &lo, to: hi, by: areInIncreasingOrder) else { break } + elements.formIndex(before: &hi) + guard findHi(in: elements, pivot: pivot, from: lo, to: &hi, by: areInIncreasingOrder) else { break } + elements.swapAt(lo, hi) + } + + + return lo +} + +private func findLo(in array: [T], pivot: T, from lo: inout Int, to hi: Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool { + while lo != hi { + if !areInIncreasingOrder(array[lo], pivot) { + return true + } + array.formIndex(after: &lo) + } + return false +} + +private func findHi(in array: [T], pivot: T, from lo: Int, to hi: inout Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool { + while hi != lo { + if areInIncreasingOrder(array[hi], pivot) { return true } + array.formIndex(before: &hi) + } + return false +} diff --git a/Introsort/README.markdown b/Introsort/README.markdown new file mode 100644 index 000000000..280950928 --- /dev/null +++ b/Introsort/README.markdown @@ -0,0 +1,113 @@ +# IntroSort + +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 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. + +Here's an implementation in pseudocode: + +``` +procedure sort(A : array): + let maxdepth = ⌊log(length(A))⌋ × 2 + introSort(A, maxdepth) + +procedure introsort(A, maxdepth): + n ← length(A) + if n < 20: + insertionsort(A) + else if maxdepth = 0: + heapsort(A) + else: + p ← partition(A) // the pivot is selected using median of 3 + introsort(A[0:p], maxdepth - 1) + introsort(A[p+1:n], maxdepth - 1) +``` + +## An example + +Let's walk through the example. The array is initially: + + [ 10, 0, 3, 9, 2, 14, 8, 27, 1, 5, 8, -1, 26 ] + + +For this example let's assume that `maxDepth` is **2** and that the size of the partition for the insertionSort to kick in is **5** + +The first iteration of introsort begins by attempting to use insertionSort. The collection has 13 elements, so it tries to do heapsort instead. The condition for heapsort to occur is if `maxdepth == 0` evaluates true. Since `maxdepth` is currently **2** for the first iteration, introsort will default to quicksort. + +The `partition` method picks the first element, the median and the last, it sorts them and uses the new median as pivot. + + [ 10, 8, 26 ] -> [ 8, 10, 26 ] + +Our array is now + + [ 8, 0, 3, 9, 2, 14, 10, 27, 1, 5, 8, -1, 26 ] + +**10** is the pivot. After the choice of the pivot, the `partition` method swaps elements to get all the elements less than pivot on the left, and all the elements more or equal than pivot on the right. + + [ 8, 0, 3, 9, 2, 1, 5, 8, -1, 10, 27, 14, 26 ] + +Because of the swaps, the index of of pivot is now changed and returned. The next step of introsort is to call recursively itself for the two sub arrays: + + less: [ 8, 0, 3, 9, 2, 1, 5, 8, -1, 10 ] + greater: [ 27, 14, 26 ] + +## maxDepth: 1, branch: less + + [ 8, 0, 3, 9, 2, 1, 5, 8, -1, 10 ] + +The count of the array is still more than 5 so we don't meet yet the conditions for insertion sort to kick in. At this iteration maxDepth is decreased by one but it is still more than zero, so heapsort will not act. + +Just like in the previous iteration quicksort wins and the `partition` method choses a pivot and sorts the elemets less than pivot on the left and the elements more or equeal than pivot on the right. + + array: [ 8, 0, 3, 9, 2, 1, 5, 8, -1, 10 ] + pivot candidates: [ 8, 1, 10] -> [ 1, 8, 10] + pivot: 8 + before partition: [ 1, 0, 3, 9, 2, 8, 5, 8, -1, 10 ] + after partition: [ 1, 0, 3, -1, 2, 5, 8, 8, 9, 10 ] + + less: [ 1, 0, 3, -1, 2, 5, 8 ] + greater: [ 8, 9, 10 ] + +## maxDepth: 0, branch: less + + [ 1, 0, 3, -1, 2, 5, 8 ] + +Just like before, introsort is recursively executed for `less` and greater. This time `less`has a count more than **5** so it will not be sorted with insertion sort, but the maxDepth decreased again by 1 is now 0 and heapsort takes over sorting the array. + + heapsort -> [ -1, 0, 1, 2, 3, 5, 8 ] + +## maxDepth: 0, branch: greater + + [ 8, 9, 10 ] + +following greater in this recursion, the count of elements is 3, which is less than 5, so this partition is sorted using insertionSort. + + insertionSort -> [ 8, 9 , 10] + + +## back to maxDepth = 1, branch: greater + + [ 27, 14, 26 ] + +At this point the original array has mutated to be + + [ -1, 0, 1, 2, 3, 5, 8, 8, 9, 10, 27, 14, 26 ] + +now the `less` partition is sorted and since the count of the `greater` partition is 3 it will be sorted with insertion sort `[ 14, 26, 27 ]` + +The array is now successfully sorted + + [ -1, 0, 1, 2, 3, 5, 8, 8, 9, 10, 14, 26, 27 ] + + +## See also + +[Introsort on Wikipedia](https://en.wikipedia.org/wiki/Introsort) +[Introsort comparison with other sorting algorithms](http://agostini.tech/2017/12/18/swift-sorting-algorithm/) +[Introsort implementation from the Swift standard library](https://github.com/apple/swift/blob/09f77ff58d250f5d62855ea359fc304f40b531df/stdlib/public/core/Sort.swift.gyb) + +*Written for Swift Algorithm Club by Giuseppe Lanza* diff --git a/Introsort/Randomize.swift b/Introsort/Randomize.swift new file mode 100644 index 000000000..6d1d57132 --- /dev/null +++ b/Introsort/Randomize.swift @@ -0,0 +1,9 @@ +import Foundation + +public func randomize(n: Int) -> [Int] { + var unsorted = [Int]() + for _ in 0..(in array: inout [T], a: Int, b: Int, c: Int, by areInIncreasingOrder: (T, T) -> Bool) { + switch (areInIncreasingOrder(array[b], array[a]), + areInIncreasingOrder(array[c], array[b])) { + case (false, false): break + case (true, true): array.swapAt(a, c) + case (true, false): + array.swapAt(a, b) + + if areInIncreasingOrder(array[c], array[b]) { + array.swapAt(b, c) + } + case (false, true): + array.swapAt(b, c) + + if areInIncreasingOrder(array[b], array[a]) { + array.swapAt(a, b) + } + } +} diff --git a/K-Means/KMeans.swift b/K-Means/KMeans.swift index 4a89a3fae..bccc6efd3 100644 --- a/K-Means/KMeans.swift +++ b/K-Means/KMeans.swift @@ -12,7 +12,7 @@ class KMeans { } private func indexOfNearestCenter(_ x: Vector, centers: [Vector]) -> Int { - var nearestDist = DBL_MAX + var nearestDist = Double.greatestFiniteMagnitude var minIndex = 0 for (idx, center) in centers.enumerated() { diff --git a/K-Means/Tests/Tests.xcodeproj/project.pbxproj b/K-Means/Tests/Tests.xcodeproj/project.pbxproj index 08b64a8f9..1cbd139e5 100644 --- a/K-Means/Tests/Tests.xcodeproj/project.pbxproj +++ b/K-Means/Tests/Tests.xcodeproj/project.pbxproj @@ -85,11 +85,11 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1000; TargetAttributes = { B80894DF1C852D100018730E = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 0820; + LastSwiftMigration = 1000; }; }; }; @@ -137,12 +137,20 @@ B80894D91C852CDC0018730E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + 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_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_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; @@ -162,12 +170,20 @@ B80894DA1C852CDC0018730E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + 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_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_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; @@ -229,7 +245,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -271,7 +287,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.alvahouse322.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/K-Means/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/K-Means/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/K-Means/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/K-Means/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/K-Means/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 2b401e86e..73ba8f37d 100644 --- a/K-Means/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/K-Means/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ Int { // Long Multiplication - O(n^2) func multiply(_ num1: Int, by num2: Int, base: Int = 10) -> Int { - let num1Array = String(num1).characters.reversed().map { Int(String($0))! } - let num2Array = String(num2).characters.reversed().map { Int(String($0))! } - + let num1Array = String(num1).reversed().map { Int(String($0))! } + let num2Array = String(num2).reversed().map { Int(String($0))! } + var product = Array(repeating: 0, count: num1Array.count + num2Array.count) - + for i in num1Array.indices { var carry = 0 for j in num2Array.indices { @@ -29,32 +29,32 @@ func multiply(_ num1: Int, by num2: Int, base: Int = 10) -> Int { } product[i + num2Array.count] += carry } - + return Int(product.reversed().map { String($0) }.reduce("", +))! } // Karatsuba Multiplication - O(n^log2(3)) func karatsuba(_ num1: Int, by num2: Int) -> Int { - let num1Array = String(num1).characters - let num2Array = String(num2).characters - - guard num1Array.count > 1 && num2Array.count > 1 else { + let num1String = String(num1) + let num2String = String(num2) + + guard num1String.count > 1 && num2String.count > 1 else { return multiply(num1, by: num2) } - - let n = max(num1Array.count, num2Array.count) + + let n = max(num1String.count, num2String.count) let nBy2 = n / 2 - + let a = num1 / 10^^nBy2 let b = num1 % 10^^nBy2 let c = num2 / 10^^nBy2 let d = num2 % 10^^nBy2 - + let ac = karatsuba(a, by: c) let bd = karatsuba(b, by: d) let adPlusbc = karatsuba(a+b, by: c+d) - ac - bd - + let product = ac * 10^^(2 * nBy2) + adPlusbc * 10^^nBy2 + bd - + return product } diff --git a/Karatsuba Multiplication/KaratsubaMultiplication.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Karatsuba Multiplication/KaratsubaMultiplication.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Karatsuba Multiplication/KaratsubaMultiplication.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Karatsuba Multiplication/KaratsubaMultiplication.swift b/Karatsuba Multiplication/KaratsubaMultiplication.swift index 0f0e8ed12..3d14a243a 100644 --- a/Karatsuba Multiplication/KaratsubaMultiplication.swift +++ b/Karatsuba Multiplication/KaratsubaMultiplication.swift @@ -19,15 +19,36 @@ func ^^ (radix: Int, power: Int) -> Int { return Int(pow(Double(radix), Double(power))) } +// Long Multiplication - O(n^2) +func multiply(_ num1: Int, by num2: Int, base: Int = 10) -> Int { + let num1Array = String(num1).reversed().map { Int(String($0))! } + let num2Array = String(num2).reversed().map { Int(String($0))! } + + var product = Array(repeating: 0, count: num1Array.count + num2Array.count) + + for i in num1Array.indices { + var carry = 0 + for j in num2Array.indices { + product[i + j] += carry + num1Array[i] * num2Array[j] + carry = product[i + j] / base + product[i + j] %= base + } + product[i + num2Array.count] += carry + } + + return Int(product.reversed().map { String($0) }.reduce("", +))! +} + +// Karatsuba Multiplication - O(n^log2(3)) func karatsuba(_ num1: Int, by num2: Int) -> Int { - let num1Array = String(num1).characters - let num2Array = String(num2).characters + let num1String = String(num1) + let num2String = String(num2) - guard num1Array.count > 1 && num2Array.count > 1 else { - return num1 * num2 + guard num1String.count > 1 && num2String.count > 1 else { + return multiply(num1, by: num2) } - let n = max(num1Array.count, num2Array.count) + let n = max(num1String.count, num2String.count) let nBy2 = n / 2 let a = num1 / 10^^nBy2 diff --git a/Karatsuba Multiplication/README.markdown b/Karatsuba Multiplication/README.markdown index c86f69831..02e54a1bf 100644 --- a/Karatsuba Multiplication/README.markdown +++ b/Karatsuba Multiplication/README.markdown @@ -72,14 +72,14 @@ Here's the full implementation. Note that the recursive algorithm is most effici ```swift // Karatsuba Multiplication func karatsuba(_ num1: Int, by num2: Int) -> Int { - let num1Array = String(num1).characters - let num2Array = String(num2).characters + let num1String = String(num1) + let num2String = String(num2) - guard num1Array.count > 1 && num2Array.count > 1 else { - return num1*num2 + guard num1String.count > 1 && num2String.count > 1 else { + return multiply(num1, by: num2) } - let n = max(num1Array.count, num2Array.count) + let n = max(num1String.count, num2String.count) let nBy2 = n / 2 let a = num1 / 10^^nBy2 @@ -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/Karatsuba Multiplication/Tests/KaratsubaMultiplicationTests.swift b/Karatsuba Multiplication/Tests/KaratsubaMultiplicationTests.swift new file mode 100644 index 000000000..379c9e812 --- /dev/null +++ b/Karatsuba Multiplication/Tests/KaratsubaMultiplicationTests.swift @@ -0,0 +1,17 @@ +// +// KaratsubaMultiplicationTests.swift +// Tests +// +// Created by Afonso Graça on 8/10/18. +// + +import XCTest + +final class KaratsubaMultiplicationTests: XCTestCase { + + func testReadmeExample() { + let subject = karatsuba(1234, by: 5678) + + XCTAssertEqual(subject, 7006652) + } +} diff --git a/Karatsuba Multiplication/Tests/Tests.xcodeproj/project.pbxproj b/Karatsuba Multiplication/Tests/Tests.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ef23067ee --- /dev/null +++ b/Karatsuba Multiplication/Tests/Tests.xcodeproj/project.pbxproj @@ -0,0 +1,305 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 6AF099AC216B54E200F69B16 /* KaratsubaMultiplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AF099AB216B54E200F69B16 /* KaratsubaMultiplication.swift */; }; + 6AF099AE216B55A100F69B16 /* KaratsubaMultiplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AF099AD216B55A100F69B16 /* KaratsubaMultiplicationTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 6AF099A2216B54D500F69B16 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6AF099A7216B54D500F69B16 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = SOURCE_ROOT; }; + 6AF099AB216B54E200F69B16 /* KaratsubaMultiplication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KaratsubaMultiplication.swift; path = ../KaratsubaMultiplication.swift; sourceTree = ""; }; + 6AF099AD216B55A100F69B16 /* KaratsubaMultiplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KaratsubaMultiplicationTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6AF0999F216B54D500F69B16 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6AF09997216B545D00F69B16 = { + isa = PBXGroup; + children = ( + 6AF099AB216B54E200F69B16 /* KaratsubaMultiplication.swift */, + 6AF099A4216B54D500F69B16 /* Tests */, + 6AF099A3216B54D500F69B16 /* Products */, + ); + sourceTree = ""; + }; + 6AF099A3216B54D500F69B16 /* Products */ = { + isa = PBXGroup; + children = ( + 6AF099A2216B54D500F69B16 /* Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 6AF099A4216B54D500F69B16 /* Tests */ = { + isa = PBXGroup; + children = ( + 6AF099AD216B55A100F69B16 /* KaratsubaMultiplicationTests.swift */, + 6AF099A7216B54D500F69B16 /* Info.plist */, + ); + name = Tests; + sourceTree = SOURCE_ROOT; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6AF099A1216B54D500F69B16 /* Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6AF099A8216B54D500F69B16 /* Build configuration list for PBXNativeTarget "Tests" */; + buildPhases = ( + 6AF0999E216B54D500F69B16 /* Sources */, + 6AF0999F216B54D500F69B16 /* Frameworks */, + 6AF099A0216B54D500F69B16 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Tests; + productName = Tests; + productReference = 6AF099A2216B54D500F69B16 /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6AF09998216B545D00F69B16 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1000; + LastUpgradeCheck = 1000; + TargetAttributes = { + 6AF099A1216B54D500F69B16 = { + CreatedOnToolsVersion = 10.0; + }; + }; + }; + buildConfigurationList = 6AF0999B216B545D00F69B16 /* Build configuration list for PBXProject "Tests" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 6AF09997216B545D00F69B16; + productRefGroup = 6AF099A3216B54D500F69B16 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6AF099A1216B54D500F69B16 /* Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6AF099A0216B54D500F69B16 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6AF0999E216B54D500F69B16 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6AF099AE216B55A100F69B16 /* KaratsubaMultiplicationTests.swift in Sources */, + 6AF099AC216B54E200F69B16 /* KaratsubaMultiplication.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 6AF0999C216B545D00F69B16 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + 6AF0999D216B545D00F69B16 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + 6AF099A9216B54D500F69B16 /* 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_ENABLE_OBJC_WEAK = 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_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_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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + 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; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; + }; + name = Debug; + }; + 6AF099AA216B54D500F69B16 /* 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_ENABLE_OBJC_WEAK = 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_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_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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + 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; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 4.2; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6AF0999B216B545D00F69B16 /* Build configuration list for PBXProject "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6AF0999C216B545D00F69B16 /* Debug */, + 6AF0999D216B545D00F69B16 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6AF099A8216B54D500F69B16 /* Build configuration list for PBXNativeTarget "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6AF099A9216B54D500F69B16 /* Debug */, + 6AF099AA216B54D500F69B16 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6AF09998216B545D00F69B16 /* Project object */; +} diff --git a/Karatsuba Multiplication/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Karatsuba Multiplication/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme new file mode 100644 index 000000000..1288fd9a8 --- /dev/null +++ b/Karatsuba Multiplication/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Karatsuba Multiplication/Tests/Tests/Info.plist b/Karatsuba Multiplication/Tests/Tests/Info.plist new file mode 100644 index 000000000..6c40a6cd0 --- /dev/null +++ b/Karatsuba Multiplication/Tests/Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Knuth-Morris-Pratt/KnuthMorrisPratt.playground/Contents.swift b/Knuth-Morris-Pratt/KnuthMorrisPratt.playground/Contents.swift index a27726648..9b4a50fa0 100644 --- a/Knuth-Morris-Pratt/KnuthMorrisPratt.playground/Contents.swift +++ b/Knuth-Morris-Pratt/KnuthMorrisPratt.playground/Contents.swift @@ -1,115 +1,115 @@ //: Playground - noun: a place where people can play func ZetaAlgorithm(ptnr: String) -> [Int]? { + + let pattern = Array(ptnr) + let patternLength: Int = pattern.count + + guard patternLength > 0 else { + return nil + } + + var zeta: [Int] = [Int](repeating: 0, count: patternLength) + + var left: Int = 0 + var right: Int = 0 + var k_1: Int = 0 + var betaLength: Int = 0 + var textIndex: Int = 0 + var patternIndex: Int = 0 + + for k in 1 ..< patternLength { + if k > right { + patternIndex = 0 + + while k + patternIndex < patternLength && + pattern[k + patternIndex] == pattern[patternIndex] { + patternIndex = patternIndex + 1 + } + + zeta[k] = patternIndex + + if zeta[k] > 0 { + left = k + right = k + zeta[k] - 1 + } + } else { + k_1 = k - left + 1 + betaLength = right - k + 1 + + if zeta[k_1 - 1] < betaLength { + zeta[k] = zeta[k_1 - 1] + } else if zeta[k_1 - 1] >= betaLength { + textIndex = betaLength + patternIndex = right + 1 + + while patternIndex < patternLength && pattern[textIndex] == pattern[patternIndex] { + textIndex = textIndex + 1 + patternIndex = patternIndex + 1 + } + zeta[k] = patternIndex - k + left = k + right = patternIndex - 1 + } + } + } + return zeta +} +extension String { + + func indexesOf(ptnr: String) -> [Int]? { + + let text = Array(self) let pattern = Array(ptnr.characters) + + let textLength: Int = text.count let patternLength: Int = pattern.count - + guard patternLength > 0 else { - return nil + return nil } - - var zeta: [Int] = [Int](repeating: 0, count: patternLength) - - var left: Int = 0 - var right: Int = 0 - var k_1: Int = 0 - var betaLength: Int = 0 + + var suffixPrefix: [Int] = [Int](repeating: 0, count: patternLength) var textIndex: Int = 0 var patternIndex: Int = 0 - - for k in 1 ..< patternLength { - if k > right { - patternIndex = 0 - - while k + patternIndex < patternLength && - pattern[k + patternIndex] == pattern[patternIndex] { - patternIndex = patternIndex + 1 - } - - zeta[k] = patternIndex - - if zeta[k] > 0 { - left = k - right = k + zeta[k] - 1 - } - } else { - k_1 = k - left + 1 - betaLength = right - k + 1 - - if zeta[k_1 - 1] < betaLength { - zeta[k] = zeta[k_1 - 1] - } else if zeta[k_1 - 1] >= betaLength { - textIndex = betaLength - patternIndex = right + 1 - - while patternIndex < patternLength && pattern[textIndex] == pattern[patternIndex] { - textIndex = textIndex + 1 - patternIndex = patternIndex + 1 - } - zeta[k] = patternIndex - k - left = k - right = patternIndex - 1 - } - } + var indexes: [Int] = [Int]() + + /* Pre-processing stage: computing the table for the shifts (through Z-Algorithm) */ + let zeta = ZetaAlgorithm(ptnr: ptnr) + + for patternIndex in (1 ..< patternLength).reversed() { + textIndex = patternIndex + zeta![patternIndex] - 1 + suffixPrefix[textIndex] = zeta![patternIndex] } - return zeta -} - -extension String { - - func indexesOf(ptnr: String) -> [Int]? { - - let text = Array(self.characters) - let pattern = Array(ptnr.characters) - - let textLength: Int = text.count - let patternLength: Int = pattern.count - - guard patternLength > 0 else { - return nil - } - - var suffixPrefix: [Int] = [Int](repeating: 0, count: patternLength) - var textIndex: Int = 0 - var patternIndex: Int = 0 - var indexes: [Int] = [Int]() - - /* Pre-processing stage: computing the table for the shifts (through Z-Algorithm) */ - let zeta = ZetaAlgorithm(ptnr: ptnr) - - for patternIndex in (1 ..< patternLength).reversed() { - textIndex = patternIndex + zeta![patternIndex] - 1 - suffixPrefix[textIndex] = zeta![patternIndex] - } - - /* Search stage: scanning the text for pattern matching */ - textIndex = 0 - patternIndex = 0 - - while textIndex + (patternLength - patternIndex - 1) < textLength { - - while patternIndex < patternLength && text[textIndex] == pattern[patternIndex] { - textIndex = textIndex + 1 - patternIndex = patternIndex + 1 - } - - if patternIndex == patternLength { - indexes.append(textIndex - patternIndex) - } - - if patternIndex == 0 { - textIndex = textIndex + 1 - } else { - patternIndex = suffixPrefix[patternIndex - 1] - } - } - - guard !indexes.isEmpty else { - return nil - } - return indexes + + /* Search stage: scanning the text for pattern matching */ + textIndex = 0 + patternIndex = 0 + + while textIndex + (patternLength - patternIndex - 1) < textLength { + + while patternIndex < patternLength && text[textIndex] == pattern[patternIndex] { + textIndex = textIndex + 1 + patternIndex = patternIndex + 1 + } + + if patternIndex == patternLength { + indexes.append(textIndex - patternIndex) + } + + if patternIndex == 0 { + textIndex = textIndex + 1 + } else { + patternIndex = suffixPrefix[patternIndex - 1] + } + } + + guard !indexes.isEmpty else { + return nil } + return indexes + } } /* Examples */ diff --git a/Knuth-Morris-Pratt/KnuthMorrisPratt.swift b/Knuth-Morris-Pratt/KnuthMorrisPratt.swift index 45d9812b7..be0cd961d 100644 --- a/Knuth-Morris-Pratt/KnuthMorrisPratt.swift +++ b/Knuth-Morris-Pratt/KnuthMorrisPratt.swift @@ -1,65 +1,65 @@ /* Knuth-Morris-Pratt algorithm for pattern/string matching - - The code is based on the book: - "Algorithms on String, Trees and Sequences: Computer Science and Computational Biology" - by Dan Gusfield - Cambridge University Press, 1997 -*/ + + The code is based on the book: + "Algorithms on String, Trees and Sequences: Computer Science and Computational Biology" + by Dan Gusfield + Cambridge University Press, 1997 + */ import Foundation extension String { - - func indexesOf(ptnr: String) -> [Int]? { - - let text = Array(self.characters) - let pattern = Array(ptnr.characters) - - let textLength: Int = text.count - let patternLength: Int = pattern.count - - guard patternLength > 0 else { - return nil - } - - var suffixPrefix: [Int] = [Int](repeating: 0, count: patternLength) - var textIndex: Int = 0 - var patternIndex: Int = 0 - var indexes: [Int] = [Int]() - - /* Pre-processing stage: computing the table for the shifts (through Z-Algorithm) */ - let zeta = ZetaAlgorithm(ptnr: ptnr) - - for patternIndex in (1 ..< patternLength).reversed() { - textIndex = patternIndex + zeta![patternIndex] - 1 - suffixPrefix[textIndex] = zeta![patternIndex] - } - - /* Search stage: scanning the text for pattern matching */ - textIndex = 0 - patternIndex = 0 - - while textIndex + (patternLength - patternIndex - 1) < textLength { - - while patternIndex < patternLength && text[textIndex] == pattern[patternIndex] { - textIndex = textIndex + 1 - patternIndex = patternIndex + 1 - } - - if patternIndex == patternLength { - indexes.append(textIndex - patternIndex) - } - - if patternIndex == 0 { - textIndex = textIndex + 1 - } else { - patternIndex = suffixPrefix[patternIndex - 1] - } - } - - guard !indexes.isEmpty else { - return nil - } - return indexes + + func indexesOf(ptnr: String) -> [Int]? { + + let text = Array(self) + let pattern = Array(ptnr) + + let textLength: Int = text.count + let patternLength: Int = pattern.count + + guard patternLength > 0 else { + return nil + } + + var suffixPrefix: [Int] = [Int](repeating: 0, count: patternLength) + var textIndex: Int = 0 + var patternIndex: Int = 0 + var indexes: [Int] = [Int]() + + /* Pre-processing stage: computing the table for the shifts (through Z-Algorithm) */ + let zeta = ZetaAlgorithm(ptnr: ptnr) + + for patternIndex in (1 ..< patternLength).reversed() { + textIndex = patternIndex + zeta![patternIndex] - 1 + suffixPrefix[textIndex] = zeta![patternIndex] + } + + /* Search stage: scanning the text for pattern matching */ + textIndex = 0 + patternIndex = 0 + + while textIndex + (patternLength - patternIndex - 1) < textLength { + + while patternIndex < patternLength && text[textIndex] == pattern[patternIndex] { + textIndex = textIndex + 1 + patternIndex = patternIndex + 1 + } + + if patternIndex == patternLength { + indexes.append(textIndex - patternIndex) + } + + if patternIndex == 0 { + textIndex = textIndex + 1 + } else { + patternIndex = suffixPrefix[patternIndex - 1] + } + } + + guard !indexes.isEmpty else { + return nil } + return indexes + } } 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/README.markdown b/Kth Largest Element/README.markdown index 53bec1c1c..b23d22b74 100644 --- a/Kth Largest Element/README.markdown +++ b/Kth Largest Element/README.markdown @@ -12,7 +12,7 @@ The following solution is semi-naive. Its time complexity is **O(n log n)** sinc func kthLargest(a: [Int], k: Int) -> Int? { let len = a.count if k > 0 && k <= len { - let sorted = a.sort() + let sorted = a.sorted() return sorted[len - k] } else { return nil @@ -40,7 +40,7 @@ Now, all we must do is take the value at index `a.count - k`: a[a.count - k] = a[8 - 4] = a[4] = 9 ``` -Of course, if you were looking for the k-th *smallest* element, you'd use `a[k]`. +Of course, if you were looking for the k-th *smallest* element, you'd use `a[k-1]`. ## A faster solution @@ -84,29 +84,29 @@ The index of pivot `9` is 4, and that's exactly the *k* we're looking for. We're The following function implements these ideas: ```swift -public func randomizedSelect(array: [T], order k: Int) -> T { +public func randomizedSelect(_ array: [T], order k: Int) -> T { var a = array - func randomPivot(inout a: [T], _ low: Int, _ high: Int) -> T { + func randomPivot(_ a: inout [T], _ low: Int, _ high: Int) -> T { let pivotIndex = random(min: low, max: high) - swap(&a, pivotIndex, high) + a.swapAt(pivotIndex, high) return a[high] } - func randomizedPartition(inout a: [T], _ low: Int, _ high: Int) -> Int { + func randomizedPartition(_ a: inout [T], _ low: Int, _ high: Int) -> Int { let pivot = randomPivot(&a, low, high) var i = low for j in low..(inout a: [T], _ low: Int, _ high: Int, _ k: Int) -> T { + func randomizedSelect(_ a: inout [T], _ low: Int, _ high: Int, _ k: Int) -> T { if low < high { let p = randomizedPartition(&a, low, high) if k == p { diff --git a/Kth Largest Element/kthLargest.playground/Contents.swift b/Kth Largest Element/kthLargest.playground/Contents.swift index 3e186760c..a27dfce7d 100644 --- a/Kth Largest Element/kthLargest.playground/Contents.swift +++ b/Kth Largest Element/kthLargest.playground/Contents.swift @@ -1,14 +1,5 @@ //: Playground - noun: a place where people can play -func kthLargest(_ a: [Int], _ k: Int) -> Int? { - let len = a.count - if k > 0 && k <= len { - let sorted = a.sorted() - return sorted[len - k] - } else { - return nil - } -} let a = [5, 1, 3, 2, 7, 6, 4] @@ -22,65 +13,6 @@ kthLargest(a, 6) kthLargest(a, 7) kthLargest(a, 8) -import Foundation - -/* 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))) -} - -/* - Swift's swap() doesn't like it if the items you're trying to swap refer to - the same memory location. This little wrapper simply ignores such swaps. -*/ -public func swap(_ a:inout [T], _ i: Int, _ j: Int) { - if i != j { - swap(&a[i], &a[j]) - } -} - -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) - swap(&a, pivotIndex, high) - return a[high] - } - - func randomizedPartition(_ a: inout[T], _ low: Int, _ high: Int) -> Int { - let pivot = randomPivot(&a, low, high) - var i = low - for j in low..(_ a: inout [T], _ low: Int, _ high: Int, _ k: Int) -> T { - if low < high { - let p = randomizedPartition(&a, low, high) - if k == p { - return a[p] - } else if k < p { - return randomizedSelect(&a, low, p - 1, k) - } else { - return randomizedSelect(&a, p + 1, high, k) - } - } else { - return a[low] - } - } - - precondition(a.count > 0) - return randomizedSelect(&a, 0, a.count - 1, k) -} - randomizedSelect(a, order: 0) randomizedSelect(a, order: 1) randomizedSelect(a, order: 2) diff --git a/Kth Largest Element/kthLargest.swift b/Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift similarity index 57% rename from Kth Largest Element/kthLargest.swift rename to Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift index 16f6caf0c..aad15eac6 100644 --- a/Kth Largest Element/kthLargest.swift +++ b/Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift @@ -4,10 +4,10 @@ import Foundation Returns the k-th largest value inside of an array a. This is an O(n log n) solution since we sort the array. */ -func kthLargest(a: [Int], k: Int) -> Int? { +public func kthLargest(_ a: [Int], _ k: Int) -> Int? { let len = a.count if k > 0 && k <= len { - let sorted = a.sort() + let sorted = a.sorted() return sorted[len - k] } else { return nil @@ -16,22 +16,6 @@ func kthLargest(a: [Int], k: Int) -> Int? { // MARK: - Randomized selection -/* Returns a random integer in the range min...max, inclusive. */ -public func random(min min: Int, max: Int) -> Int { - assert(min < max) - return min + Int(arc4random_uniform(UInt32(max - min + 1))) -} - -/* - Swift's swap() doesn't like it if the items you're trying to swap refer to - the same memory location. This little wrapper simply ignores such swaps. -*/ -public func swap(inout a: [T], _ i: Int, _ j: Int) { - if i != j { - swap(&a[i], &a[j]) - } -} - /* Returns the i-th smallest element from the array. @@ -45,29 +29,29 @@ public func swap(inout a: [T], _ i: Int, _ j: Int) { Expected running time: O(n) if the elements are distinct. */ -public func randomizedSelect(array: [T], order k: Int) -> T { +public func randomizedSelect(_ array: [T], order k: Int) -> T { var a = array - func randomPivot(inout a: [T], _ low: Int, _ high: Int) -> T { - let pivotIndex = random(min: low, max: high) - swap(&a, pivotIndex, high) + func randomPivot(_ a: inout [T], _ low: Int, _ high: Int) -> T { + let pivotIndex = Int.random(in: low...high) + a.swapAt(pivotIndex, high) return a[high] } - func randomizedPartition(inout a: [T], _ low: Int, _ high: Int) -> Int { + func randomizedPartition(_ a: inout [T], _ low: Int, _ high: Int) -> Int { let pivot = randomPivot(&a, low, high) var i = low for j in low..(inout a: [T], _ low: Int, _ high: Int, _ k: Int) -> T { + func randomizedSelect(_ a: inout [T], _ low: Int, _ high: Int, _ k: Int) -> T { if low < high { let p = randomizedPartition(&a, low, high) if k == p { diff --git a/LRU Cache/LRUCache.playground/Sources/LinkedList.swift b/LRU Cache/LRUCache.playground/Sources/LinkedList.swift index 94e978c07..a5594eb31 100755 --- a/LRU Cache/LRUCache.playground/Sources/LinkedList.swift +++ b/LRU Cache/LRUCache.playground/Sources/LinkedList.swift @@ -26,7 +26,7 @@ public final class LinkedList { public var last: Node? { if var node = head { - while case let next? = node.next { + while let next = node.next { node = next } return node @@ -38,7 +38,7 @@ public final class LinkedList { public var count: Int { if var node = head { var c = 1 - while case let next? = node.next { + while let next = node.next { node = next c += 1 } diff --git a/LRU Cache/LRUCache.playground/playground.xcworkspace/contents.xcworkspacedata b/LRU Cache/LRUCache.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/LRU Cache/LRUCache.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/LRU Cache/LRUCache.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/LRU Cache/LRUCache.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/LRU Cache/LRUCache.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Linear Regression/LinearRegression.playground/Contents.swift b/Linear Regression/LinearRegression.playground/Contents.swift index 8faf2b738..a093ef566 100644 --- a/Linear Regression/LinearRegression.playground/Contents.swift +++ b/Linear Regression/LinearRegression.playground/Contents.swift @@ -17,7 +17,7 @@ let numberOfCarAdvertsWeSaw = carPrice.count let numberOfIterations = 100 let alpha = 0.0001 -for n in 1...numberOfIterations { +for _ in 1...numberOfIterations { for i in 0..(_ array: [T], _ object: T) -> Int? { - for (index, obj) in array.enumerated() where obj == object { - return index - } - return nil + for (index, obj) in array.enumerated() where obj == object { + return index + } + return nil } let array = [5, 2, 4, 7] diff --git a/Linear Search/LinearSearch.playground/timeline.xctimeline b/Linear Search/LinearSearch.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Linear Search/LinearSearch.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Linked List/LinkedList.playground/Contents.swift b/Linked List/LinkedList.playground/Contents.swift index c7cd6265d..76b911310 100644 --- a/Linked List/LinkedList.playground/Contents.swift +++ b/Linked List/LinkedList.playground/Contents.swift @@ -3,340 +3,372 @@ // For best results, don't forget to select "Show Rendered Markup" from XCode's "Editor" menu //: Linked List Class Declaration: -public final class LinkedList { - - /// Linked List's Node Class Declaration - public class LinkedListNode { - var value: T - var next: LinkedListNode? - weak var previous: LinkedListNode? - public init(value: T) { - self.value = value +public final class LinkedList { + + /// Linked List's Node Class Declaration + public class LinkedListNode { + var value: T + var next: LinkedListNode? + weak var previous: LinkedListNode? + + public init(value: T) { + self.value = value + } + } + + /// Typealiasing the node class to increase readability of code + public typealias Node = LinkedListNode + + + /// The head of the Linked List + private(set) var head: Node? + + /// Computed property to iterate through the linked list and return the last node in the list (if any) + public var last: Node? { + guard var node = head else { + return nil + } + + while let next = node.next { + node = next + } + return node + } + + /// Computed property to check if the linked list is empty + public var isEmpty: Bool { + return head == nil + } + + /// Computed property to iterate through the linked list and return the total number of nodes + public var count: Int { + guard var node = head else { + return 0 + } + + var count = 1 + while let next = node.next { + node = next + count += 1 + } + return count + } + + /// Default initializer + public init() {} + + + /// Subscript function to return the node at a specific index + /// + /// - Parameter index: Integer value of the requested value's index + public subscript(index: Int) -> T { + let node = self.node(at: index) + return node.value + } + + /// Function to return the node at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameter index: Integer value of the node's index to be returned + /// - Returns: LinkedListNode + public func node(at index: Int) -> Node { + assert(head != nil, "List is empty") + assert(index >= 0, "index must be greater than 0") + + if index == 0 { + return head! + } else { + var node = head!.next + for _ in 1.. - - /// The head of the Linked List - fileprivate var head: Node? - - /// Default initializer - public init() {} - - /// Computed property to check if the linked list is empty - public var isEmpty: Bool { - return head == nil - } - - /// Computed property to get the first node in the linked list (if any) - public var first: Node? { - return head - } - - /// Computed property to iterate through the linked list and return the last node in the list (if any) - public var last: Node? { - if var node = head { - while case let next? = node.next { - node = next - } - return node - } else { - return nil + + /// Append a value to the end of the list + /// + /// - Parameter value: The data value to be appended + public func append(_ value: T) { + let newNode = Node(value: value) + append(newNode) } - } - - /// Computed property to iterate through the linked list and return the total number of nodes - public var count: Int { - if var node = head { - var c = 1 - while case let next? = node.next { - node = next - c += 1 - } - return c - } else { - return 0 + + /// Append a copy of a LinkedListNode to the end of the list. + /// + /// - Parameter node: The node containing the value to be appended + public func append(_ node: Node) { + let newNode = node + if let lastNode = last { + newNode.previous = lastNode + lastNode.next = newNode + } else { + head = newNode + } } - } - - /// Function to return the node at a specific index. Crashes if index is out of bounds (0...self.count) - /// - /// - Parameter index: Integer value of the node's index to be returned - /// - Returns: Optional LinkedListNode - public func node(atIndex index: Int) -> Node? { - if index >= 0 { - var node = head - var i = index - while node != nil { - if i == 0 { return node } - i -= 1 - node = node!.next - } + + /// Append a copy of a LinkedList to the end of the list. + /// + /// - Parameter list: The list to be copied and appended. + public func append(_ list: LinkedList) { + var nodeToCopy = list.head + while let node = nodeToCopy { + append(node.value) + nodeToCopy = node.next + } } - return nil - } - - /// Subscript function to return the node at a specific index - /// - /// - Parameter index: Integer value of the requested value's index - public subscript(index: Int) -> T { - let node = self.node(atIndex: index) - assert(node != nil) - return node!.value - } - - /// Append a value to the end of the list - /// - /// - Parameter value: The data value to be appended - public func append(_ value: T) { - let newNode = Node(value: value) - self.append(newNode) - } - - /// Append a copy of a LinkedListNode to the end of the list. - /// - /// - Parameter node: The node containing the value to be appended - public func append(_ node: Node) { - let newNode = LinkedListNode(value: node.value) - if let lastNode = last { - newNode.previous = lastNode - lastNode.next = newNode - } else { - head = newNode + + /// Insert a value at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameters: + /// - value: The data value to be inserted + /// - index: Integer value of the index to be insterted at + public func insert(_ value: T, at index: Int) { + let newNode = Node(value: value) + insert(newNode, at: index) } - } - - /// Append a copy of a LinkedList to the end of the list. - /// - /// - Parameter list: The list to be copied and appended. - public func append(_ list: LinkedList) { - var nodeToCopy = list.head - while let node = nodeToCopy { - self.append(node.value) - nodeToCopy = node.next + + /// Insert a copy of a node at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameters: + /// - node: The node containing the value to be inserted + /// - index: Integer value of the index to be inserted at + public func insert(_ newNode: Node, at index: Int) { + if index == 0 { + newNode.next = head + head?.previous = newNode + head = newNode + } else { + let prev = node(at: index - 1) + let next = prev.next + newNode.previous = prev + newNode.next = next + next?.previous = newNode + prev.next = newNode + } } - } - - /// A private helper funciton to find the nodes before and after a specified index. Crashes if index is out of bounds (0...self.count) - /// - /// - Parameter index: Integer value of the index between the nodes. - /// - Returns: A tuple of 2 nodes before & after the specified index respectively. - private func nodesBeforeAndAfter(index: Int) -> (Node?, Node?) { - assert(index >= 0) - - var i = index - var next = head - var prev: Node? - - while next != nil && i > 0 { - i -= 1 - prev = next - next = next!.next + + /// Insert a copy of a LinkedList at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameters: + /// - list: The LinkedList to be copied and inserted + /// - index: Integer value of the index to be inserted at + public func insert(_ list: LinkedList, at index: Int) { + if list.isEmpty { return } + + if index == 0 { + list.last?.next = head + head = list.head + } else { + let prev = node(at: index - 1) + let next = prev.next + + prev.next = list.head + list.head?.previous = prev + + list.last?.next = next + next?.previous = list.last + } } - assert(i == 0) // if > 0, then specified index was too large - - return (prev, next) - } - - /// Insert a value at a specific index. Crashes if index is out of bounds (0...self.count) - /// - /// - Parameters: - /// - value: The data value to be inserted - /// - index: Integer value of the index to be insterted at - public func insert(_ value: T, atIndex index: Int) { - let newNode = Node(value: value) - self.insert(newNode, atIndex: index) - } - - /// Insert a copy of a node at a specific index. Crashes if index is out of bounds (0...self.count) - /// - /// - Parameters: - /// - node: The node containing the value to be inserted - /// - index: Integer value of the index to be inserted at - public func insert(_ node: Node, atIndex index: Int) { - let (prev, next) = nodesBeforeAndAfter(index: index) - let newNode = LinkedListNode(value: node.value) - newNode.previous = prev - newNode.next = next - prev?.next = newNode - next?.previous = newNode - - if prev == nil { - head = newNode + + /// Function to remove all nodes/value from the list + public func removeAll() { + head = nil } - } - - /// Insert a copy of a LinkedList at a specific index. Crashes if index is out of bounds (0...self.count) - /// - /// - Parameters: - /// - list: The LinkedList to be copied and inserted - /// - index: Integer value of the index to be inserted at - public func insert(_ list: LinkedList, atIndex index: Int) { - if list.isEmpty { return } - var (prev, next) = nodesBeforeAndAfter(index: index) - var nodeToCopy = list.head - var newNode: Node? - while let node = nodeToCopy { - newNode = Node(value: node.value) - newNode?.previous = prev - if let previous = prev { - previous.next = newNode - } else { - self.head = newNode - } - nodeToCopy = nodeToCopy?.next - prev = newNode + + /// Function to remove a specific node. + /// + /// - Parameter node: The node to be deleted + /// - Returns: The data value contained in the deleted node. + @discardableResult public func remove(node: Node) -> T { + let prev = node.previous + let next = node.next + + if let prev = prev { + prev.next = next + } else { + head = next + } + next?.previous = prev + + node.previous = nil + node.next = nil + return node.value } - prev?.next = next - next?.previous = prev - } - - /// Function to remove all nodes/value from the list - public func removeAll() { - head = nil - } - - /// Function to remove a specific node. - /// - /// - Parameter node: The node to be deleted - /// - Returns: The data value contained in the deleted node. - @discardableResult public func remove(node: Node) -> T { - let prev = node.previous - let next = node.next - - if let prev = prev { - prev.next = next - } else { - head = next + + /// Function to remove the last node/value in the list. Crashes if the list is empty + /// + /// - Returns: The data value contained in the deleted node. + @discardableResult public func removeLast() -> T { + assert(!isEmpty) + return remove(node: last!) + } + + /// Function to remove a node/value at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameter index: Integer value of the index of the node to be removed + /// - Returns: The data value contained in the deleted node + @discardableResult public func remove(at index: Int) -> T { + let node = self.node(at: index) + return remove(node: node) } - next?.previous = prev - - node.previous = nil - node.next = nil - return node.value - } - - /// Function to remove the last node/value in the list. Crashes if the list is empty - /// - /// - Returns: The data value contained in the deleted node. - @discardableResult public func removeLast() -> T { - assert(!isEmpty) - return remove(node: last!) - } - - /// Function to remove a node/value at a specific index. Crashes if index is out of bounds (0...self.count) - /// - /// - Parameter index: Integer value of the index of the node to be removed - /// - Returns: The data value contained in the deleted node - @discardableResult public func remove(atIndex index: Int) -> T { - let node = self.node(atIndex: index) - assert(node != nil) - return remove(node: node!) - } } //: End of the base class declarations & beginning of extensions' declarations: // MARK: - Extension to enable the standard conversion of a list to String extension LinkedList: CustomStringConvertible { - public var description: String { - var s = "[" - var node = head - while node != nil { - s += "\(node!.value)" - node = node!.next - if node != nil { s += ", " } + public var description: String { + var s = "[" + var node = head + while let nd = node { + s += "\(nd.value)" + node = nd.next + if node != nil { s += ", " } + } + return s + "]" } - return s + "]" - } } // MARK: - Extension to add a 'reverse' function to the list extension LinkedList { - public func reverse() { - var node = head - while let currentNode = node { - node = currentNode.next - swap(¤tNode.next, ¤tNode.previous) - head = currentNode + public func reverse() { + var node = head + while let currentNode = node { + node = currentNode.next + swap(¤tNode.next, ¤tNode.previous) + head = currentNode + } } - } } // MARK: - An extension with an implementation of 'map' & 'filter' functions extension LinkedList { - public func map(transform: (T) -> U) -> LinkedList { - let result = LinkedList() - var node = head - while node != nil { - result.append(transform(node!.value)) - node = node!.next + public func map(transform: (T) -> U) -> LinkedList { + let result = LinkedList() + var node = head + while let nd = node { + result.append(transform(nd.value)) + node = nd.next + } + return result } - return result - } - - public func filter(predicate: (T) -> Bool) -> LinkedList { - let result = LinkedList() - var node = head - while node != nil { - if predicate(node!.value) { - result.append(node!.value) - } - node = node!.next + + public func filter(predicate: (T) -> Bool) -> LinkedList { + let result = LinkedList() + var node = head + while let nd = node { + if predicate(nd.value) { + result.append(nd.value) + } + node = nd.next + } + return result } - return result - } } // MARK: - Extension to enable initialization from an Array extension LinkedList { - convenience init(array: Array) { - self.init() - - for element in array { - self.append(element) + convenience init(array: Array) { + self.init() + + array.forEach { append($0) } } - } } // MARK: - Extension to enable initialization from an Array Literal extension LinkedList: ExpressibleByArrayLiteral { - public convenience init(arrayLiteral elements: T...) { - self.init() + public convenience init(arrayLiteral elements: T...) { + self.init() + + elements.forEach { append($0) } + } +} - for element in elements { - self.append(element) +// MARK: - Collection +extension LinkedList: Collection { + + public typealias Index = LinkedListIndex + + /// The position of the first element in a nonempty collection. + /// + /// If the collection is empty, `startIndex` is equal to `endIndex`. + /// - Complexity: O(1) + public var startIndex: Index { + get { + return LinkedListIndex(node: head, tag: 0) + } + } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// - Complexity: O(n), where n is the number of elements in the list. This can be improved by keeping a reference + /// to the last node in the collection. + public var endIndex: Index { + get { + if let h = self.head { + return LinkedListIndex(node: h, tag: count) + } else { + return LinkedListIndex(node: nil, tag: startIndex.tag) + } + } + } + + public subscript(position: Index) -> T { + get { + return position.node!.value + } + } + + public func index(after idx: Index) -> Index { + return LinkedListIndex(node: idx.node?.next, tag: idx.tag + 1) } - } } +// MARK: - Collection Index +/// Custom index type that contains a reference to the node at index 'tag' +public struct LinkedListIndex: Comparable { + fileprivate let node: LinkedList.LinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} //: Ok, now that the declarations are done, let's see our Linked List in action: let list = LinkedList() list.isEmpty // true -list.first // nil +list.head // nil list.last // nil list.append("Hello") -list.isEmpty -list.first!.value // "Hello" +list.isEmpty // false +list.head!.value // "Hello" list.last!.value // "Hello" list.count // 1 list.append("World") -list.first!.value // "Hello" +list.head!.value // "Hello" list.last!.value // "World" list.count // 2 -list.first!.previous // nil -list.first!.next!.value // "World" +list.head!.previous // nil +list.head!.next!.value // "World" list.last!.previous!.value // "Hello" list.last!.next // nil -list.node(atIndex: 0)!.value // "Hello" -list.node(atIndex: 1)!.value // "World" -list.node(atIndex: 2) // nil +list.node(at: 0).value // "Hello" +list.node(at: 1).value // "World" +//list.node(at: 2) // crash! list[0] // "Hello" list[1] // "World" @@ -349,9 +381,9 @@ list.append(list2) // [Hello, World, Goodbye, World] list2.removeAll() // [ ] list2.isEmpty // true list.removeLast() // "World" -list.remove(atIndex: 2) // "Goodbye" +list.remove(at: 2) // "Goodbye" -list.insert("Swift", atIndex: 1) +list.insert("Swift", at: 1) list[0] // "Hello" list[1] // "Swift" list[2] // "World" @@ -359,25 +391,49 @@ print(list) list.reverse() // [World, Swift, Hello] -list.node(atIndex: 0)!.value = "Universe" -list.node(atIndex: 1)!.value = "Swifty" -let m = list.map { s in s.characters.count } +list.node(at: 0).value = "Universe" +list.node(at: 1).value = "Swifty" +let m = list.map { s in s.count } m // [8, 6, 5] -let f = list.filter { s in s.characters.count > 5 } +let f = list.filter { s in s.count > 5 } f // [Universe, Swifty] -list.remove(node: list.first!) // "Universe" +list.remove(node: list.head!) // "Universe" list.count // 2 list[0] // "Swifty" list[1] // "Hello" +list.count // 2 list.removeLast() // "Hello" +list.head?.value list.count // 1 list[0] // "Swifty" -list.remove(atIndex: 0) // "Swifty" +list.remove(at: 0) // "Swifty" list.count // 0 +let list3 = LinkedList() +list3.insert("2", at: 0) // [2] +list3.count // 1 +list3.insert("4", at: 1) // [2,4] +list3.count // 2 +list3.insert("5", at: 2) // [2,4,5] +list3.count // 3 +list3.insert("3", at: 1) // [2,3,4,5] +list3.insert("1", at: 0) // [1,2,3,4,5] + +let list4 = LinkedList() +list4.insert(list3, at: 0) // [1,2,3,4,5] +list4.count // 5 + +let list5 = LinkedList() +list5.append("0") // [0] +list5.insert("End", at:1) // [0,End] +list5.count // 2 +list5.insert(list4, at: 1) // [0,1,2,3,4,5,End] +list5.count // 7 + + let linkedList: LinkedList = [1, 2, 3, 4] // [1, 2, 3, 4] linkedList.count // 4 linkedList[0] // 1 @@ -387,3 +443,23 @@ let listArrayLiteral2: LinkedList = ["Swift", "Algorithm", "Club"] listArrayLiteral2.count // 3 listArrayLiteral2[0] // "Swift" listArrayLiteral2.removeLast() // "Club" + + +// Conformance to the Collection protocol +let collection: LinkedList = [1, 2, 3, 4, 5] +let index2 = collection.index(collection.startIndex, offsetBy: 2) +let value = collection[index2] // 3 + +// Iterating in a for loop, since the Sequence protocol allows this. +var sum = 0 +for element in collection { + sum += element +} +sum //15 + +// Another way of achieving the same result though 'reduce', another method defined in an extension of Sequence. Collections are Sequences. +let result = collection.reduce(0) {$0 + $1} // 15 + + + + diff --git a/Linked List/LinkedList.playground/contents.xcplayground b/Linked List/LinkedList.playground/contents.xcplayground index 3de2b51ba..5e28f2dd0 100644 --- a/Linked List/LinkedList.playground/contents.xcplayground +++ b/Linked List/LinkedList.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Linked List/LinkedList.swift b/Linked List/LinkedList.swift index 94e978c07..de8a42c93 100755 --- a/Linked List/LinkedList.swift +++ b/Linked List/LinkedList.swift @@ -1,79 +1,103 @@ public final class LinkedList { - + + /// Linked List's Node Class Declaration public class LinkedListNode { var value: T var next: LinkedListNode? weak var previous: LinkedListNode? - + public init(value: T) { self.value = value } } - + + /// Typealiasing the node class to increase readability of code public typealias Node = LinkedListNode - - fileprivate var head: Node? - - public init() {} - - public var isEmpty: Bool { - return head == nil - } - - public var first: Node? { - return head - } - + + + /// The head of the Linked List + private(set) var head: Node? + + /// Computed property to iterate through the linked list and return the last node in the list (if any) public var last: Node? { - if var node = head { - while case let next? = node.next { - node = next - } - return node - } else { + guard var node = head else { return nil } + + while let next = node.next { + node = next + } + return node } - + + /// Computed property to check if the linked list is empty + public var isEmpty: Bool { + return head == nil + } + + /// Computed property to iterate through the linked list and return the total number of nodes public var count: Int { - if var node = head { - var c = 1 - while case let next? = node.next { - node = next - c += 1 - } - return c - } else { + guard var node = head else { return 0 } - } - - public func node(atIndex index: Int) -> Node? { - if index >= 0 { - var node = head - var i = index - while node != nil { - if i == 0 { return node } - i -= 1 - node = node!.next - } + + var count = 1 + while let next = node.next { + node = next + count += 1 } - return nil + return count } - + + /// Default initializer + public init() {} + + + /// Subscript function to return the node at a specific index + /// + /// - Parameter index: Integer value of the requested value's index public subscript(index: Int) -> T { - let node = self.node(atIndex: index) - assert(node != nil) - return node!.value + let node = self.node(at: index) + return node.value } - + + /// Function to return the node at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameter index: Integer value of the node's index to be returned + /// - Returns: LinkedListNode + public func node(at index: Int) -> Node { + assert(head != nil, "List is empty") + assert(index >= 0, "index must be greater or equal to 0") + + if index == 0 { + return head! + } else { + var node = head!.next + for _ in 1.. { head = newNode } } - + + /// Append a copy of a LinkedList to the end of the list. + /// + /// - Parameter list: The list to be copied and appended. public func append(_ list: LinkedList) { var nodeToCopy = list.head while let node = nodeToCopy { - self.append(node.value) + append(node.value) nodeToCopy = node.next } } - - private func nodesBeforeAndAfter(index: Int) -> (Node?, Node?) { - assert(index >= 0) - - var i = index - var next = head - var prev: Node? - - while next != nil && i > 0 { - i -= 1 - prev = next - next = next!.next - } - assert(i == 0) // if > 0, then specified index was too large - - return (prev, next) - } - - public func insert(_ value: T, atIndex index: Int) { + + /// Insert a value at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameters: + /// - value: The data value to be inserted + /// - index: Integer value of the index to be insterted at + public func insert(_ value: T, at index: Int) { let newNode = Node(value: value) - self.insert(newNode, atIndex: index) + insert(newNode, at: index) } - - public func insert(_ node: Node, atIndex index: Int) { - let (prev, next) = nodesBeforeAndAfter(index: index) - let newNode = LinkedListNode(value: node.value) - newNode.previous = prev - newNode.next = next - prev?.next = newNode - next?.previous = newNode - - if prev == nil { + + /// Insert a copy of a node at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameters: + /// - node: The node containing the value to be inserted + /// - index: Integer value of the index to be inserted at + public func insert(_ newNode: Node, at index: Int) { + if index == 0 { + newNode.next = head + head?.previous = newNode head = newNode + } else { + let prev = node(at: index - 1) + let next = prev.next + newNode.previous = prev + newNode.next = next + next?.previous = newNode + prev.next = newNode } } - - public func insert(_ list: LinkedList, atIndex index: Int) { - if list.isEmpty { return } - var (prev, next) = nodesBeforeAndAfter(index: index) - var nodeToCopy = list.head - var newNode: Node? - while let node = nodeToCopy { - newNode = Node(value: node.value) - newNode?.previous = prev - if let previous = prev { - previous.next = newNode - } else { - self.head = newNode - } - nodeToCopy = nodeToCopy?.next - prev = newNode + + /// Insert a copy of a LinkedList at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameters: + /// - list: The LinkedList to be copied and inserted + /// - index: Integer value of the index to be inserted at + public func insert(_ list: LinkedList, at index: Int) { + guard !list.isEmpty else { return } + + if index == 0 { + list.last?.next = head + head = list.head + } else { + let prev = node(at: index - 1) + let next = prev.next + + prev.next = list.head + list.head?.previous = prev + + list.last?.next = next + next?.previous = list.last } - prev?.next = next - next?.previous = prev } - + + /// Function to remove all nodes/value from the list public func removeAll() { head = nil } - + + /// Function to remove a specific node. + /// + /// - Parameter node: The node to be deleted + /// - Returns: The data value contained in the deleted node. @discardableResult public func remove(node: Node) -> T { let prev = node.previous let next = node.next - + if let prev = prev { prev.next = next } else { head = next } next?.previous = prev - + node.previous = nil node.next = nil return node.value } - + + /// Function to remove the last node/value in the list. Crashes if the list is empty + /// + /// - Returns: The data value contained in the deleted node. @discardableResult public func removeLast() -> T { assert(!isEmpty) return remove(node: last!) } - - @discardableResult public func remove(atIndex index: Int) -> T { - let node = self.node(atIndex: index) - assert(node != nil) - return remove(node: node!) + + /// Function to remove a node/value at a specific index. Crashes if index is out of bounds (0...self.count) + /// + /// - Parameter index: Integer value of the index of the node to be removed + /// - Returns: The data value contained in the deleted node + @discardableResult public func remove(at index: Int) -> T { + let node = self.node(at: index) + return remove(node: node) } } +//: End of the base class declarations & beginning of extensions' declarations: + +// MARK: - Extension to enable the standard conversion of a list to String extension LinkedList: CustomStringConvertible { public var description: String { var s = "[" var node = head - while node != nil { - s += "\(node!.value)" - node = node!.next + while let nd = node { + s += "\(nd.value)" + node = nd.next if node != nil { s += ", " } } return s + "]" } } +// MARK: - Extension to add a 'reverse' function to the list extension LinkedList { public func reverse() { var node = head @@ -201,46 +241,101 @@ extension LinkedList { } } +// MARK: - An extension with an implementation of 'map' & 'filter' functions extension LinkedList { public func map(transform: (T) -> U) -> LinkedList { let result = LinkedList() var node = head - while node != nil { - result.append(transform(node!.value)) - node = node!.next + while let nd = node { + result.append(transform(nd.value)) + node = nd.next } return result } - + public func filter(predicate: (T) -> Bool) -> LinkedList { let result = LinkedList() var node = head - while node != nil { - if predicate(node!.value) { - result.append(node!.value) + while let nd = node { + if predicate(nd.value) { + result.append(nd.value) } - node = node!.next + node = nd.next } return result } } +// MARK: - Extension to enable initialization from an Array extension LinkedList { convenience init(array: Array) { self.init() - - for element in array { - self.append(element) - } + + array.forEach { append($0) } } } +// MARK: - Extension to enable initialization from an Array Literal extension LinkedList: ExpressibleByArrayLiteral { - public convenience init(arrayLiteral elements: T...) { - self.init() + public convenience init(arrayLiteral elements: T...) { + self.init() + + elements.forEach { append($0) } + } +} - for element in elements { - self.append(element) +// MARK: - Collection +extension LinkedList: Collection { + + public typealias Index = LinkedListIndex + + /// The position of the first element in a nonempty collection. + /// + /// If the collection is empty, `startIndex` is equal to `endIndex`. + /// - Complexity: O(1) + public var startIndex: Index { + get { + return LinkedListIndex(node: head, tag: 0) + } + } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// - Complexity: O(n), where n is the number of elements in the list. This can be improved by keeping a reference + /// to the last node in the collection. + public var endIndex: Index { + get { + if let h = self.head { + return LinkedListIndex(node: h, tag: count) + } else { + return LinkedListIndex(node: nil, tag: startIndex.tag) + } + } + } + + public subscript(position: Index) -> T { + get { + return position.node!.value + } + } + + public func index(after idx: Index) -> Index { + return LinkedListIndex(node: idx.node?.next, tag: idx.tag + 1) } - } } + +// MARK: - Collection Index +/// Custom index type that contains a reference to the node at index 'tag' +public struct LinkedListIndex: Comparable { + fileprivate let node: LinkedList.LinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} + diff --git a/Linked List/README.markdown b/Linked List/README.markdown index c03023c42..ea399ee12 100644 --- a/Linked List/README.markdown +++ b/Linked List/README.markdown @@ -1,5 +1,7 @@ # Linked List +> This topic has been tutorialized [here](https://www.raywenderlich.com/144083/swift-algorithm-club-swift-linked-list-data-structure) + A linked list is a sequence of data items, just like an array. But where an array allocates a big block of memory to store the objects, the elements in a linked list are totally separate objects in memory and are connected through links: +--------+ +--------+ +--------+ +--------+ @@ -108,20 +110,20 @@ Let's also add a property that gives you the last node in the list. This is wher ```swift public var last: Node? { - if var node = head { - while case let next? = node.next { - node = next - } - return node - } else { + guard var node = head else { return nil } + + while let next = node.next { + node = next + } + return node } ``` If you're new to Swift, you've probably seen `if let` but maybe not `if var`. It does the same thing -- it unwraps the `head` optional and puts the result in a new local variable named `node`. The difference is that `node` is not a constant but an actual variable, so we can change it inside the loop. -The loop also does some Swift magic. The `while case let next? = node.next` bit keeps looping until `node.next` is nil. You could have written this as follows: +The loop also does some Swift magic. The `while let next = node.next` bit keeps looping until `node.next` is nil. You could have written this as follows: ```swift var node: Node? = head @@ -196,16 +198,16 @@ Let's add a method to count how many nodes are in the list. This will look very ```swift public var count: Int { - if var node = head { - var c = 1 - while case let next? = node.next { - node = next - c += 1 - } - return c - } else { + guard var node = head else { return 0 } + + var count = 1 + while let next = node.next { + node = next + count += 1 + } + return count } ``` @@ -216,37 +218,43 @@ It loops through the list in the same manner but this time increments a counter What if we wanted to find the node at a specific index in the list? With an array we can just write `array[index]` and it's an **O(1)** operation. It's a bit more involved with linked lists, but again the code follows a similar pattern: ```swift - public func nodeAt(_ index: Int) -> Node? { - if index >= 0 { - var node = head - var i = index - while node != nil { - if i == 0 { return node } - i -= 1 - node = node!.next + public func node(atIndex index: Int) -> Node { + if index == 0 { + return head! + } else { + var node = head!.next + for _ in 1.. T { - let node = nodeAt(index) - assert(node != nil) - return node!.value + let node = node(atIndex: index) + return node.value } ``` @@ -262,84 +270,100 @@ It crashes on `list[2]` because there is no node at that index. So far we've written code to add new nodes to the end of the list, but that's slow because you need to find the end of the list first. (It would be fast if we used a tail pointer.) For this reason, if the order of the items in the list doesn't matter, you should insert at the front of the list instead. That's always an **O(1)** operation. -Let's write a method that lets you insert a new node at any index in the list. First, we'll define a helper function: - -```swift - private func nodesBeforeAndAfter(index: Int) -> (Node?, Node?) { - assert(index >= 0) - - var i = index - var next = head - var prev: Node? - - while next != nil && i > 0 { - i -= 1 - prev = next - next = next!.next - } - assert(i == 0) - - return (prev, next) - } -``` - -This returns a tuple containing the node currently at the specified index and the node that immediately precedes it, if any. The loop is very similar to `nodeAtIndex()`, except that here we also keep track of what the previous node is as we iterate through the list. - -Let's look at an example. Suppose we have the following list: - - head --> A --> B --> C --> D --> E --> nil - -We want to find the nodes before and after index 3. As we start the loop, `i = 3`, `next` points at `"A"`, and `prev` is nil. - - head --> A --> B --> C --> D --> E --> nil - next - -We decrement `i`, make `prev` point to `"A"`, and move `next` to the next node, `"B"`: - - head --> A --> B --> C --> D --> E --> F --> nil - prev next - -Again, we decrement `i` and update the pointers. Now `prev` points to `"B"`, and `next` points to `"C"`: - - head --> A --> B --> C --> D --> E --> F --> nil - prev next -As you can see, `prev` always follows one behind `next`. We do this one more time and then `i` equals 0 and we exit the loop: - - head --> A --> B --> C --> D --> E --> F --> nil - prev next - -The `assert()` after the loop checks whether there really were enough nodes in the list. If `i > 0` at this point, then the specified index was too large. - -> **Note:** If any of the loops in this article don't make much sense to you, then draw a linked list on a piece of paper and step through the loop by hand, just like what we did here. - -For this example, the function returns `("C", "D")` because `"D"` is the node at index 3 and `"C"` is the one right before that. - -Now that we have this helper function, we can write the method for inserting nodes: +Let's write a method that lets you insert a new node at any index in the list. ```swift - public func insert(value: T, atIndex index: Int) { - let (prev, next) = nodesBeforeAndAfter(index) // 1 - - let newNode = Node(value: value) // 2 - newNode.previous = prev - newNode.next = next - prev?.next = newNode - next?.previous = newNode - - if prev == nil { // 3 - head = newNode - } - } + public func insert(_ node: Node, atIndex index: Int) { + let newNode = node + if index == 0 { + newNode.next = head + head?.previous = newNode + head = newNode + } else { + let prev = self.node(atIndex: index-1) + let next = prev.next + + newNode.previous = prev + newNode.next = prev.next + prev.next = newNode + next?.previous = newNode + } +} ``` -Some remarks about this method: - -1. First, we need to find where to insert this node. After calling the helper method, `prev` points to the previous node and `next` is the node currently at the given index. We'll insert the new node in between these two. Note that `prev` can be nil (index is 0), `next` can be nil (index equals size of the list), or both can be nil if the list is empty. +As with node(atIndex :) method, insert(_: at:) method also branches depending on whether the given index is 0 or not. +First let's look at the former case. Suppose we have the following list and the new node(C). + + +---------+ +---------+ + head --->| |---->| |-----//-----> + | A | | B | + nil <---| |<----| |<----//------ + +---------+ +---------+ + [0] [1] + + + +---------+ + new --->| |----> nil + | C | + | | + +---------+ + +Now put the new node before the first node. In this way: + + new.next = head + head.previous = new + + +---------+ +---------+ +---------+ + new --->| |--> head -->| |---->| |-----//-----> + | C | | A | | B | + | |<-----------| |<----| |<----//------ + +---------+ +---------+ +---------+ + + +Finally, replace the head with the new node. + + head = new + + +---------+ +---------+ +---------+ + head --->| |--->| |---->| |-----//-----> + | C | | A | | B | + nil <---| |<---| |<----| |<----//------ + +---------+ +---------+ +---------+ + [0] [1] [2] + + +However, when the given index is greater than 0, it is necessary to get the node previous and next index and insert between them. +You can also obtain the previous and next node using node(atIndex:) as follows: + + +---------+ +---------+ +---------+ + head --->| |---//--->| |---->| |---- + | | | A | | B | + nil <---| |---//<---| |<----| |<--- + +---------+ +---------+ +---------+ + [0] [index-1] [index] + ^ ^ + | | + prev next + + prev = node(at: index-1) + next = prev.next + +Now insert new node between the prev and the next. + + new.prev = prev; prev.next = new // connect prev and new. + new.next = next; next.prev = new // connect new and next. + + +---------+ +---------+ +---------+ +---------+ + head --->| |---//--->| |---->| |---->| | + | | | A | | C | | B | + nil <---| |---//<---| |<----| |<----| | + +---------+ +---------+ +---------+ +---------+ + [0] [index-1] [index] [index+1] + ^ ^ ^ + | | | + prev new next -2. Create the new node and connect the `previous` and `next` pointers. Because the local `prev` and `next` variables are optionals and may be nil, so we use optional chaining here. - -3. If the new node is being inserted at the front of the list, we need to update the `head` pointer. (Note: If the list had a tail pointer, you'd also need to update that pointer here if `next == nil`, because that means the last element has changed.) Try it out: @@ -351,8 +375,7 @@ list[2] // "World" ``` Also try adding new nodes to the front and back of the list, to verify that this works properly. - -> **Note:** The `nodesBeforeAndAfter()` and `insert(atIndex)` functions can also be used with a singly linked list because we don't depend on the node's `previous` pointer to find the previous element. +> **Note:** The `node(atIndex:)` and `insert(_: atIndex:)` functions can also be used with a singly linked list because we don't depend on the node's `previous` pointer to find the previous element. What else do we need? Removing nodes, of course! First we'll do `removeAll()`, which is really simple: @@ -448,9 +471,11 @@ 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 + tail = node // If you had a tail pointer while let currentNode = node { node = currentNode.next swap(¤tNode.next, ¤tNode.previous) @@ -458,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: @@ -512,7 +549,7 @@ And here's filter: And a silly example: ```swift -let f = list.filter { s in s.characters.count > 5 } +let f = list.filter { s in s.count > 5 } f // [Universe, Swifty] ``` @@ -531,10 +568,76 @@ enum ListNode { } ``` -The big difference with the class-based version is that any modification you make to this list will result in a *new copy* being created. Whether that's what you want or not depends on the application. +The big difference with the enum-based version is that any modification you make to this list will result in a *new copy* being created because of [Swift's value semantics](https://developer.apple.com/swift/blog/?id=10). Whether that's what you want or not depends on the application. [I might fill out this section in more detail if there's a demand for it.] +## Conforming to the Collection protocol +Types that conform to the Sequence protocol, whose elements can be traversed multiple times, nondestructively, and accessed by indexed subscript should conform to the Collection protocol defined in Swift's Standard Library. + +Doing so grants access to a very large number of properties and operations that are common when dealing collections of data. In addition to this, it lets custom types follow the patterns that are common to Swift developers. + +In order to conform to this protocol, classes need to provide: + 1 `startIndex` and `endIndex` properties. + 2 Subscript access to elements as O(1). Diversions of this time complexity need to be documented. + +```swift +/// The position of the first element in a nonempty collection. +public var startIndex: Index { + get { + return LinkedListIndex(node: head, tag: 0) + } +} + +/// The collection's "past the end" position---that is, the position one +/// greater than the last valid subscript argument. +/// - Complexity: O(n), where n is the number of elements in the list. +/// This diverts from the protocol's expectation. +public var endIndex: Index { + get { + if let h = self.head { + return LinkedListIndex(node: h, tag: count) + } else { + return LinkedListIndex(node: nil, tag: startIndex.tag) + } + } +} +``` + +```swift +public subscript(position: Index) -> T { + get { + return position.node!.value + } +} +``` + +Becuase collections are responsible for managing their own indexes, the implementation below keeps a reference to a node in the list. A tag property in the index represents the position of the node in the list. + +```swift +/// Custom index type that contains a reference to the node at index 'tag' +public struct LinkedListIndex : Comparable +{ + fileprivate let node: LinkedList.LinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: LinkedListIndex, rhs: LinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} +``` + +Finally, the linked is is able to calculate the index after a given one with the following implementation. +```swift +public func index(after idx: Index) -> Index { + return LinkedListIndex(node: idx.node?.next, tag: idx.tag+1) +} +``` + ## Some things to keep in mind Linked lists are flexible but many operations are **O(n)**. diff --git a/Linked List/Tests/LinkedListTests.swift b/Linked List/Tests/LinkedListTests.swift index 7fd31c2c7..b16af05ba 100755 --- a/Linked List/Tests/LinkedListTests.swift +++ b/Linked List/Tests/LinkedListTests.swift @@ -1,333 +1,339 @@ import XCTest class LinkedListTest: XCTestCase { - let numbers = [8, 2, 10, 9, 7, 5] - - fileprivate func buildList() -> LinkedList { - let list = LinkedList() - for number in numbers { - list.append(number) + let numbers = [8, 2, 10, 9, 7, 5] + + fileprivate func buildList() -> LinkedList { + let list = LinkedList() + for number in numbers { + list.append(number) + } + return list } - return list - } - - func testEmptyList() { - let list = LinkedList() - XCTAssertTrue(list.isEmpty) - XCTAssertEqual(list.count, 0) - XCTAssertNil(list.first) - XCTAssertNil(list.last) - } - - func testListWithOneElement() { - let list = LinkedList() - list.append(123) - - XCTAssertFalse(list.isEmpty) - XCTAssertEqual(list.count, 1) - - XCTAssertNotNil(list.first) - XCTAssertNil(list.first!.previous) - XCTAssertNil(list.first!.next) - XCTAssertEqual(list.first!.value, 123) - - XCTAssertNotNil(list.last) - XCTAssertNil(list.last!.previous) - XCTAssertNil(list.last!.next) - XCTAssertEqual(list.last!.value, 123) - - XCTAssertTrue(list.first === list.last) - } - - func testListWithTwoElements() { - let list = LinkedList() - list.append(123) - list.append(456) - - XCTAssertEqual(list.count, 2) - - XCTAssertNotNil(list.first) - XCTAssertEqual(list.first!.value, 123) - - XCTAssertNotNil(list.last) - XCTAssertEqual(list.last!.value, 456) - - XCTAssertTrue(list.first !== list.last) - - XCTAssertNil(list.first!.previous) - XCTAssertTrue(list.first!.next === list.last) - XCTAssertTrue(list.last!.previous === list.first) - XCTAssertNil(list.last!.next) - } - - func testListWithThreeElements() { - let list = LinkedList() - list.append(123) - list.append(456) - list.append(789) - - XCTAssertEqual(list.count, 3) - - XCTAssertNotNil(list.first) - XCTAssertEqual(list.first!.value, 123) - - let second = list.first!.next - XCTAssertNotNil(second) - XCTAssertEqual(second!.value, 456) - - XCTAssertNotNil(list.last) - XCTAssertEqual(list.last!.value, 789) - - XCTAssertNil(list.first!.previous) - XCTAssertTrue(list.first!.next === second) - XCTAssertTrue(second!.previous === list.first) - XCTAssertTrue(second!.next === list.last) - XCTAssertTrue(list.last!.previous === second) - XCTAssertNil(list.last!.next) - } - - func testNodeAtIndexInEmptyList() { - let list = LinkedList() - let node = list.node(atIndex: 0) - XCTAssertNil(node) - } - - func testNodeAtIndexInListWithOneElement() { - let list = LinkedList() - list.append(123) - - let node = list.node(atIndex: 0) - XCTAssertNotNil(node) - XCTAssertEqual(node!.value, 123) - XCTAssertTrue(node === list.first) - } - - func testNodeAtIndex() { - let list = buildList() - - let nodeCount = list.count - XCTAssertEqual(nodeCount, numbers.count) - - XCTAssertNil(list.node(atIndex: -1)) - XCTAssertNil(list.node(atIndex: nodeCount)) - - let first = list.node(atIndex: 0) - XCTAssertNotNil(first) - XCTAssertTrue(first === list.first) - XCTAssertEqual(first!.value, numbers[0]) - - let last = list.node(atIndex: nodeCount - 1) - XCTAssertNotNil(last) - XCTAssertTrue(last === list.last) - XCTAssertEqual(last!.value, numbers[nodeCount - 1]) - - for i in 0..=4.0) + print("Hello, Swift 4!") + #endif } - } - - func testSubscript() { - let list = buildList() - for i in 0 ..< list.count { - XCTAssertEqual(list[i], numbers[i]) + + func testEmptyList() { + let list = LinkedList() + XCTAssertTrue(list.isEmpty) + XCTAssertEqual(list.count, 0) + XCTAssertNil(list.first) + XCTAssertNil(list.last) + } + + func testListWithOneElement() { + let list = LinkedList() + list.append(123) + + XCTAssertFalse(list.isEmpty) + XCTAssertEqual(list.count, 1) + + XCTAssertNotNil(list.first) + XCTAssertNil(list.head!.previous) + XCTAssertNil(list.head!.next) + XCTAssertEqual(list.head!.value, 123) + + XCTAssertNotNil(list.last) + XCTAssertNil(list.last!.previous) + XCTAssertNil(list.last!.next) + XCTAssertEqual(list.last!.value, 123) + + XCTAssertTrue(list.head === list.last) + } + + func testListWithTwoElements() { + let list = LinkedList() + list.append(123) + list.append(456) + + XCTAssertEqual(list.count, 2) + + XCTAssertNotNil(list.first) + XCTAssertEqual(list.head!.value, 123) + + XCTAssertNotNil(list.last) + XCTAssertEqual(list.last!.value, 456) + + XCTAssertTrue(list.head !== list.last) + + XCTAssertNil(list.head!.previous) + XCTAssertTrue(list.head!.next === list.last) + XCTAssertTrue(list.last!.previous === list.head) + XCTAssertNil(list.last!.next) + } + + func testListWithThreeElements() { + let list = LinkedList() + list.append(123) + list.append(456) + list.append(789) + + XCTAssertEqual(list.count, 3) + + XCTAssertNotNil(list.first) + XCTAssertEqual(list.head!.value, 123) + + let second = list.head!.next + XCTAssertNotNil(second) + XCTAssertEqual(second!.value, 456) + + XCTAssertNotNil(list.last) + XCTAssertEqual(list.last!.value, 789) + + XCTAssertNil(list.head!.previous) + XCTAssertTrue(list.head!.next === second) + XCTAssertTrue(second!.previous === list.head) + XCTAssertTrue(second!.next === list.last) + XCTAssertTrue(list.last!.previous === second) + XCTAssertNil(list.last!.next) + } + + func testNodeAtIndexInListWithOneElement() { + let list = LinkedList() + list.append(123) + + let node = list.node(at: 0) + XCTAssertNotNil(node) + XCTAssertEqual(node.value, 123) + XCTAssertTrue(node === list.head) + } + + func testNodeAtIndex() { + let list = buildList() + + let nodeCount = list.count + XCTAssertEqual(nodeCount, numbers.count) + + let first = list.node(at: 0) + XCTAssertNotNil(first) + XCTAssertTrue(first === list.head) + XCTAssertEqual(first.value, numbers[0]) + + let last = list.node(at: nodeCount - 1) + XCTAssertNotNil(last) + XCTAssertTrue(last === list.last) + XCTAssertEqual(last.value, numbers[nodeCount - 1]) + + for i in 0..() + list.insert(123, at: 0) + + XCTAssertFalse(list.isEmpty) + XCTAssertEqual(list.count, 1) + + let node = list.node(at: 0) + XCTAssertNotNil(node) + XCTAssertEqual(node.value, 123) + } + + func testInsertAtIndex() { + let list = buildList() + let prev = list.node(at: 2) + let next = list.node(at: 3) + let nodeCount = list.count + + list.insert(444, at: 3) + + let node = list.node(at: 3) + XCTAssertNotNil(node) + XCTAssertEqual(node.value, 444) + XCTAssertEqual(nodeCount + 1, list.count) + + XCTAssertFalse(prev === node) + XCTAssertFalse(next === node) + XCTAssertTrue(prev.next === node) + XCTAssertTrue(next.previous === node) + } + + func testInsertListAtIndex() { + let list = buildList() + let list2 = LinkedList() + list2.append(99) + list2.append(102) + list.insert(list2, at: 2) + XCTAssertTrue(list.count == 8) + XCTAssertEqual(list.node(at: 1).value, 2) + XCTAssertEqual(list.node(at: 2).value, 99) + XCTAssertEqual(list.node(at: 3).value, 102) + XCTAssertEqual(list.node(at: 4).value, 10) + } + + func testInsertListAtFirstIndex() { + let list = buildList() + let list2 = LinkedList() + list2.append(99) + list2.append(102) + list.insert(list2, at: 0) + XCTAssertTrue(list.count == 8) + XCTAssertEqual(list.node(at: 0).value, 99) + XCTAssertEqual(list.node(at: 1).value, 102) + XCTAssertEqual(list.node(at: 2).value, 8) + } + + func testInsertListAtLastIndex() { + let list = buildList() + let list2 = LinkedList() + list2.append(99) + list2.append(102) + list.insert(list2, at: list.count) + XCTAssertTrue(list.count == 8) + XCTAssertEqual(list.node(at: 5).value, 5) + XCTAssertEqual(list.node(at: 6).value, 99) + XCTAssertEqual(list.node(at: 7).value, 102) + } + + func testAppendList() { + let list = buildList() + let list2 = LinkedList() + list2.append(99) + list2.append(102) + list.append(list2) + XCTAssertTrue(list.count == 8) + XCTAssertEqual(list.node(at: 5).value, 5) + XCTAssertEqual(list.node(at: 6).value, 99) + XCTAssertEqual(list.node(at: 7).value, 102) + } + + func testAppendListToEmptyList() { + let list = LinkedList() + let list2 = LinkedList() + list2.append(5) + list2.append(10) + list.append(list2) + XCTAssertTrue(list.count == 2) + XCTAssertEqual(list.node(at: 0).value, 5) + XCTAssertEqual(list.node(at: 1).value, 10) + } + + func testRemoveAtIndexOnListWithOneElement() { + let list = LinkedList() + list.append(123) + + let value = list.remove(at: 0) + XCTAssertEqual(value, 123) + + XCTAssertTrue(list.isEmpty) + XCTAssertEqual(list.count, 0) + XCTAssertNil(list.first) + XCTAssertNil(list.last) + } + + func testRemoveAtIndex() { + let list = buildList() + let prev = list.node(at: 2) + let next = list.node(at: 3) + let nodeCount = list.count + + list.insert(444, at: 3) + + let value = list.remove(at: 3) + XCTAssertEqual(value, 444) + + let node = list.node(at: 3) + XCTAssertTrue(next === node) + XCTAssertTrue(prev.next === node) + XCTAssertTrue(node.previous === prev) + XCTAssertEqual(nodeCount, list.count) + } + + func testRemoveLastOnListWithOneElement() { + let list = LinkedList() + list.append(123) + + let value = list.removeLast() + XCTAssertEqual(value, 123) + + XCTAssertTrue(list.isEmpty) + XCTAssertEqual(list.count, 0) + XCTAssertNil(list.first) + XCTAssertNil(list.last) + } + + func testRemoveLast() { + let list = buildList() + let last = list.last + let prev = last!.previous + let nodeCount = list.count + + let value = list.removeLast() + XCTAssertEqual(value, 5) + + XCTAssertNil(last!.previous) + XCTAssertNil(last!.next) + + XCTAssertNil(prev!.next) + XCTAssertTrue(list.last === prev) + XCTAssertEqual(nodeCount - 1, list.count) + } + + func testRemoveAll() { + let list = buildList() + list.removeAll() + XCTAssertTrue(list.isEmpty) + XCTAssertEqual(list.count, 0) + XCTAssertNil(list.first) + XCTAssertNil(list.last) + } + + func testReverseLinkedList() { + let list = buildList() + let first = list.head + let last = list.last + let nodeCount = list.count + + list.reverse() + + XCTAssertTrue(first === list.last) + XCTAssertTrue(last === list.head) + XCTAssertEqual(nodeCount, list.count) + } + + func testArrayLiteralInitTypeInfer() { + let arrayLiteralInitInfer: LinkedList = [1.0, 2.0, 3.0] + + XCTAssertEqual(arrayLiteralInitInfer.count, 3) + XCTAssertEqual(arrayLiteralInitInfer.head?.value, 1.0) + XCTAssertEqual(arrayLiteralInitInfer.last?.value, 3.0) + XCTAssertEqual(arrayLiteralInitInfer[1], 2.0) + XCTAssertEqual(arrayLiteralInitInfer.removeLast(), 3.0) + XCTAssertEqual(arrayLiteralInitInfer.count, 2) + } + + func testArrayLiteralInitExplicit() { + let arrayLiteralInitExplicit: LinkedList = [1, 2, 3] + + XCTAssertEqual(arrayLiteralInitExplicit.count, 3) + XCTAssertEqual(arrayLiteralInitExplicit.head?.value, 1) + XCTAssertEqual(arrayLiteralInitExplicit.last?.value, 3) + XCTAssertEqual(arrayLiteralInitExplicit[1], 2) + XCTAssertEqual(arrayLiteralInitExplicit.removeLast(), 3) + XCTAssertEqual(arrayLiteralInitExplicit.count, 2) + } + + func testConformanceToCollectionProtocol() { + let collection: LinkedList = [1, 2, 3, 4, 5] + let index2 = collection.index(collection.startIndex, offsetBy: 2) + let value = collection[index2] + + XCTAssertTrue(value == 3) } - } - - func testInsertAtIndexInEmptyList() { - let list = LinkedList() - list.insert(123, atIndex: 0) - - XCTAssertFalse(list.isEmpty) - XCTAssertEqual(list.count, 1) - - let node = list.node(atIndex: 0) - XCTAssertNotNil(node) - XCTAssertEqual(node!.value, 123) - } - - func testInsertAtIndex() { - let list = buildList() - let prev = list.node(atIndex: 2) - let next = list.node(atIndex: 3) - let nodeCount = list.count - - list.insert(444, atIndex: 3) - - let node = list.node(atIndex: 3) - XCTAssertNotNil(node) - XCTAssertEqual(node!.value, 444) - XCTAssertEqual(nodeCount + 1, list.count) - - XCTAssertFalse(prev === node) - XCTAssertFalse(next === node) - XCTAssertTrue(prev!.next === node) - XCTAssertTrue(next!.previous === node) - } - - func testInsertListAtIndex() { - let list = buildList() - let list2 = LinkedList() - list2.append(99) - list2.append(102) - list.insert(list2, atIndex: 2) - XCTAssertTrue(list.count == 8) - XCTAssertEqual(list.node(atIndex: 1)?.value, 2) - XCTAssertEqual(list.node(atIndex: 2)?.value, 99) - XCTAssertEqual(list.node(atIndex: 3)?.value, 102) - XCTAssertEqual(list.node(atIndex: 4)?.value, 10) - } - - func testInsertListAtFirstIndex() { - let list = buildList() - let list2 = LinkedList() - list2.append(99) - list2.append(102) - list.insert(list2, atIndex: 0) - XCTAssertTrue(list.count == 8) - XCTAssertEqual(list.node(atIndex: 0)?.value, 99) - XCTAssertEqual(list.node(atIndex: 1)?.value, 102) - XCTAssertEqual(list.node(atIndex: 2)?.value, 8) - } - - func testInsertListAtLastIndex() { - let list = buildList() - let list2 = LinkedList() - list2.append(99) - list2.append(102) - list.insert(list2, atIndex: list.count) - XCTAssertTrue(list.count == 8) - XCTAssertEqual(list.node(atIndex: 5)?.value, 5) - XCTAssertEqual(list.node(atIndex: 6)?.value, 99) - XCTAssertEqual(list.node(atIndex: 7)?.value, 102) - } - - func testAppendList() { - let list = buildList() - let list2 = LinkedList() - list2.append(99) - list2.append(102) - list.append(list2) - XCTAssertTrue(list.count == 8) - XCTAssertEqual(list.node(atIndex: 5)?.value, 5) - XCTAssertEqual(list.node(atIndex: 6)?.value, 99) - XCTAssertEqual(list.node(atIndex: 7)?.value, 102) - } - - func testAppendListToEmptyList() { - let list = LinkedList() - let list2 = LinkedList() - list2.append(5) - list2.append(10) - list.append(list2) - XCTAssertTrue(list.count == 2) - XCTAssertEqual(list.node(atIndex: 0)?.value, 5) - XCTAssertEqual(list.node(atIndex: 1)?.value, 10) - } - - func testRemoveAtIndexOnListWithOneElement() { - let list = LinkedList() - list.append(123) - - let value = list.remove(atIndex: 0) - XCTAssertEqual(value, 123) - - XCTAssertTrue(list.isEmpty) - XCTAssertEqual(list.count, 0) - XCTAssertNil(list.first) - XCTAssertNil(list.last) - } - - func testRemoveAtIndex() { - let list = buildList() - let prev = list.node(atIndex: 2) - let next = list.node(atIndex: 3) - let nodeCount = list.count - - list.insert(444, atIndex: 3) - - let value = list.remove(atIndex: 3) - XCTAssertEqual(value, 444) - - let node = list.node(atIndex: 3) - XCTAssertTrue(next === node) - XCTAssertTrue(prev!.next === node) - XCTAssertTrue(node!.previous === prev) - XCTAssertEqual(nodeCount, list.count) - } - - func testRemoveLastOnListWithOneElement() { - let list = LinkedList() - list.append(123) - - let value = list.removeLast() - XCTAssertEqual(value, 123) - - XCTAssertTrue(list.isEmpty) - XCTAssertEqual(list.count, 0) - XCTAssertNil(list.first) - XCTAssertNil(list.last) - } - - func testRemoveLast() { - let list = buildList() - let last = list.last - let prev = last!.previous - let nodeCount = list.count - - let value = list.removeLast() - XCTAssertEqual(value, 5) - - XCTAssertNil(last!.previous) - XCTAssertNil(last!.next) - - XCTAssertNil(prev!.next) - XCTAssertTrue(list.last === prev) - XCTAssertEqual(nodeCount - 1, list.count) - } - - func testRemoveAll() { - let list = buildList() - list.removeAll() - XCTAssertTrue(list.isEmpty) - XCTAssertEqual(list.count, 0) - XCTAssertNil(list.first) - XCTAssertNil(list.last) - } - - func testReverseLinkedList() { - let list = buildList() - let first = list.first - let last = list.last - let nodeCount = list.count - - list.reverse() - - XCTAssertTrue(first === list.last) - XCTAssertTrue(last === list.first) - XCTAssertEqual(nodeCount, list.count) - } - - func testArrayLiteralInitTypeInfer() { - let arrayLiteralInitInfer: LinkedList = [1.0, 2.0, 3.0] - - XCTAssertEqual(arrayLiteralInitInfer.count, 3) - XCTAssertEqual(arrayLiteralInitInfer.first?.value, 1.0) - XCTAssertEqual(arrayLiteralInitInfer.last?.value, 3.0) - XCTAssertEqual(arrayLiteralInitInfer[1], 2.0) - XCTAssertEqual(arrayLiteralInitInfer.removeLast(), 3.0) - XCTAssertEqual(arrayLiteralInitInfer.count, 2) - } - - func testArrayLiteralInitExplicit() { - let arrayLiteralInitExplicit: LinkedList = [1, 2, 3] - - XCTAssertEqual(arrayLiteralInitExplicit.count, 3) - XCTAssertEqual(arrayLiteralInitExplicit.first?.value, 1) - XCTAssertEqual(arrayLiteralInitExplicit.last?.value, 3) - XCTAssertEqual(arrayLiteralInitExplicit[1], 2) - XCTAssertEqual(arrayLiteralInitExplicit.removeLast(), 3) - XCTAssertEqual(arrayLiteralInitExplicit.count, 2) - } } diff --git a/Linked List/Tests/Tests.xcodeproj/project.pbxproj b/Linked List/Tests/Tests.xcodeproj/project.pbxproj index 3a29f5677..388dcd96c 100644 --- a/Linked List/Tests/Tests.xcodeproj/project.pbxproj +++ b/Linked List/Tests/Tests.xcodeproj/project.pbxproj @@ -221,7 +221,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 = 4.0; }; name = Debug; }; @@ -233,7 +233,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 = 4.0; }; name = Release; }; diff --git a/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift b/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift index 0fc9ec325..0e78b7043 100644 --- a/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift +++ b/Longest Common Subsequence/LongestCommonSubsequence.playground/Contents.swift @@ -1,11 +1,16 @@ +// last checked with Xcode 11.4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + 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 { @@ -17,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() @@ -36,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/Longest Common Subsequence/LongestCommonSubsequence.playground/contents.xcplayground b/Longest Common Subsequence/LongestCommonSubsequence.playground/contents.xcplayground index fc5b4ab70..5da2641c9 100644 --- a/Longest Common Subsequence/LongestCommonSubsequence.playground/contents.xcplayground +++ b/Longest Common Subsequence/LongestCommonSubsequence.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Longest Common Subsequence/README.markdown b/Longest Common Subsequence/README.markdown index 4cd2c2fcb..8a4a763c6 100644 --- a/Longest Common Subsequence/README.markdown +++ b/Longest Common Subsequence/README.markdown @@ -99,7 +99,7 @@ And so on... this is how `lcsLength(_:)` fills in the entire matrix. ## Backtracking to find the actual subsequence -So far we've calculated the length of every possible subsequence. The length of the longest subsequence is found in the bottom-left corner of matrix, at `matrix[n+1][m+1]`. In the above example it is 4, so the LCS consists of 4 characters. +So far we've calculated the length of every possible subsequence. The length of the longest subsequence is found in the bottom-right corner of matrix, at `matrix[n+1][m+1]`. In the above example it is 4, so the LCS consists of 4 characters. Having the length of every combination of substrings makes it possible to determine *which* characters are part of the LCS itself by using a backtracking strategy. @@ -156,7 +156,7 @@ func backtrack(_ matrix: [[Int]]) -> String { } ``` -This backtracks from `matrix[n+1][m+1]` (bottom-right corner) to `matrix[1][1]` (top-right corner), looking for characters that are common to both strings. It adds those characters to a new string, `lcs`. +This backtracks from `matrix[n+1][m+1]` (bottom-right corner) to `matrix[1][1]` (top-left corner), looking for characters that are common to both strings. It adds those characters to a new string, `lcs`. The `charInSequence` variable is an index into the string given by `self`. Initially this points to the last character of the string. Each time we decrement `i`, we also move back `charInSequence`. When the two characters are found to be equal, we add the character at `self[charInSequence]` to the new `lcs` string. (We can't just write `self[i]` because `i` may not map to the current position inside the Swift string.) diff --git a/Merge Sort/MergeSort.playground/Contents.swift b/Merge Sort/MergeSort.playground/Contents.swift index f5a1dbbcf..ca1347890 100644 --- a/Merge Sort/MergeSort.playground/Contents.swift +++ b/Merge Sort/MergeSort.playground/Contents.swift @@ -16,31 +16,24 @@ func merge(leftPile: [T], rightPile: [T]) -> [T] { orderedPile.reserveCapacity(leftPile.count + rightPile.count) } - while leftIndex < leftPile.count && rightIndex < rightPile.count { + while true { + guard leftIndex < leftPile.endIndex else { + orderedPile.append(contentsOf: rightPile[rightIndex.. rightPile[rightIndex] { - orderedPile.append(rightPile[rightIndex]) - rightIndex += 1 } else { - orderedPile.append(leftPile[leftIndex]) - leftIndex += 1 orderedPile.append(rightPile[rightIndex]) rightIndex += 1 } } - - while leftIndex < leftPile.count { - orderedPile.append(leftPile[leftIndex]) - leftIndex += 1 - } - - while rightIndex < rightPile.count { - orderedPile.append(rightPile[rightIndex]) - rightIndex += 1 - } - return orderedPile } diff --git a/Merge Sort/MergeSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Merge Sort/MergeSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Merge Sort/MergeSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Merge Sort/MergeSort.playground/timeline.xctimeline b/Merge Sort/MergeSort.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Merge Sort/MergeSort.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Merge Sort/MergeSort.swift b/Merge Sort/MergeSort.swift index 990c432fa..00e35f498 100644 --- a/Merge Sort/MergeSort.swift +++ b/Merge Sort/MergeSort.swift @@ -17,35 +17,30 @@ func mergeSort(_ array: [T]) -> [T] { func merge(leftPile: [T], rightPile: [T]) -> [T] { var leftIndex = 0 var rightIndex = 0 - var orderedPile = [T]() + var orderedPile: [T] = [] if orderedPile.capacity < leftPile.count + rightPile.count { orderedPile.reserveCapacity(leftPile.count + rightPile.count) } - while leftIndex < leftPile.count && rightIndex < rightPile.count { - if leftPile[leftIndex] < rightPile[rightIndex] { - orderedPile.append(leftPile[leftIndex]) - leftIndex += 1 - } else if leftPile[leftIndex] > rightPile[rightIndex] { - orderedPile.append(rightPile[rightIndex]) - rightIndex += 1 - } else { - orderedPile.append(leftPile[leftIndex]) - leftIndex += 1 - orderedPile.append(rightPile[rightIndex]) - rightIndex += 1 - } - } - - while leftIndex < leftPile.count { - orderedPile.append(leftPile[leftIndex]) - leftIndex += 1 + while true { + guard leftIndex < leftPile.endIndex else { + orderedPile.append(contentsOf: rightPile[rightIndex.. This topic has been tutorialized [here](https://www.raywenderlich.com/154256/swift-algorithm-club-swift-merge-sort) + Goal: Sort an array from low to high (or high to low) Invented in 1945 by John von Neumann, merge-sort is an efficient algorithm with a best, worst, and average time complexity of **O(n log n)**. -The merge-sort algorithm uses the **divide and conquer** approach which is to divide a big problem into smaller problems and solve them. I think of the merge-sort algorithm as **split first** and **merge after**. +The merge-sort algorithm uses the **divide and conquer** approach which is to divide a big problem into smaller problems and solve them. I think of the merge-sort algorithm as **split first** and **merge after**. Assume you need to sort an array of *n* numbers in the right order. The merge-sort algorithm works as follows: @@ -17,13 +19,13 @@ Assume you need to sort an array of *n* numbers in the right order. The merge-so ### Splitting -Assume you are given an array of *n* numbers as`[2, 1, 5, 4, 9]`. This is an unsorted pile. The goal is to keep splitting the pile until you cannot split anymore. +Assume you are given an array of *n* numbers as`[2, 1, 5, 4, 9]`. This is an unsorted pile. The goal is to keep splitting the pile until you cannot split anymore. First, split the array into two halves: `[2, 1]` and `[5, 4, 9]`. Can you keep splitting them? Yes, you can! Focus on the left pile. Split`[2, 1]` into `[2]` and `[1]`. Can you keep splitting them? No. Time to check the other pile. -Split `[5, 4, 9]` into `[5]` and `[4, 9]`. Unsurprisingly, `[5]` cannot be split anymore, but `[4, 9]` can be split into `[4]` and `[9]`. +Split `[5, 4, 9]` into `[5]` and `[4, 9]`. Unsurprisingly, `[5]` cannot be split anymore, but `[4, 9]` can be split into `[4]` and `[9]`. The splitting process ends with the following piles: `[2]` `[1]` `[5]` `[4]` `[9]`. Notice that each pile consists of just one element. @@ -31,11 +33,11 @@ The splitting process ends with the following piles: `[2]` `[1]` `[5]` `[4]` `[9 Now that you have split the array, you should **merge** the piles together **while sorting them**. Remember, the idea is to solve many small problems rather than a big one. For each merge iteration, you must be concerned at merging one pile with another. -Given the piles `[2]` `[1]` `[5]` `[4]` `[9]`, the first pass will result in `[1, 2]` and `[4, 5]` and `[9]`. Since `[9]` is the odd one out, you cannot merge it with anything during this pass. +Given the piles `[2]` `[1]` `[5]` `[4]` `[9]`, the first pass will result in `[1, 2]` and `[4, 5]` and `[9]`. Since `[9]` is the odd one out, you cannot merge it with anything during this pass. -The next pass will merge `[1, 2]` and `[4, 5]` together. This results in `[1, 2, 4, 5]`, with the `[9]` left out again because it is the odd one out. +The next pass will merge `[1, 2]` and `[4, 5]` together. This results in `[1, 2, 4, 5]`, with the `[9]` left out again because it is the odd one out. -You are left with only two piles and `[9]`, finally gets its chance to merge, resulting in the sorted array as `[1, 2, 4, 5, 9]`. +You are left with only two piles `[1, 2, 4, 5]` and `[9]`, finally gets its chance to merge, resulting in the sorted array as `[1, 2, 4, 5, 9]`. ## Top-down implementation @@ -59,7 +61,7 @@ A step-by-step explanation of how the code works: 1. If the array is empty or contains a single element, there is no way to split it into smaller pieces. You must just return the array. -2. Find the middle index. +2. Find the middle index. 3. Using the middle index from the previous step, recursively split the left side of the array. @@ -75,8 +77,9 @@ func merge(leftPile: [Int], rightPile: [Int]) -> [Int] { var leftIndex = 0 var rightIndex = 0 - // 2 + // 2 var orderedPile = [Int]() + orderedPile.reserveCapacity(leftPile.count + rightPile.count) // 3 while leftIndex < leftPile.count && rightIndex < rightPile.count { @@ -113,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. @@ -142,16 +145,16 @@ This process repeats. At each step, we pick the smallest item from either the `l leftPile rightPile orderedPile [ 1, 7, 8 ] [ 3, 6, 9 ] [ 1, 3, 6 ] l -->r - + leftPile rightPile orderedPile [ 1, 7, 8 ] [ 3, 6, 9 ] [ 1, 3, 6, 7 ] -->l r - + leftPile rightPile orderedPile [ 1, 7, 8 ] [ 3, 6, 9 ] [ 1, 3, 6, 7, 8 ] -->l r -Now, there are no more items in the left pile. We simply add the remaining items from the right pile, and we are done. The merged pile is `[ 1, 3, 6, 7, 8, 9 ]`. +Now, there are no more items in the left pile. We simply add the remaining items from the right pile, and we are done. The merged pile is `[ 1, 3, 6, 7, 8, 9 ]`. Notice that, this algorithm is very simple: it moves from left-to-right through the two piles and at every step picks the smallest item. This works because we guarantee that each of the piles is already sorted. @@ -167,20 +170,20 @@ func mergeSortBottomUp(_ a: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { var z = [a, a] // 1 var d = 0 - + var width = 1 while width < n { // 2 - + var i = 0 while i < n { // 3 var j = i var l = i var r = i + width - + let lmax = min(l + width, n) let rmax = min(r + width, n) - + while l < lmax && r < rmax { // 4 if isOrderedBefore(z[d][l], z[d][r]) { z[1 - d][j] = z[d][l] @@ -204,7 +207,7 @@ func mergeSortBottomUp(_ a: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { i += width*2 } - + width *= 2 d = 1 - d // 5 } @@ -237,11 +240,11 @@ mergeSortBottomUp(array, <) // [1, 2, 4, 5, 9] ## Performance -The speed of the merge-sort algorithm is dependent on the size of the array it needs to sort. The larger the array, the more work it needs to do. +The speed of the merge-sort algorithm is dependent on the size of the array it needs to sort. The larger the array, the more work it needs to do. -Whether or not the initial array is sorted already doesnot affect the speed of the merge-sort algorithm since you will be doing the same amount splits and comparisons regardless of the initial order of the elements. +Whether or not the initial array is sorted already does not affect the speed of the merge-sort algorithm since you will be doing the same amount splits and comparisons regardless of the initial order of the elements. -Therefore, the time complexity for the best, worst, and average case will always be **O(n log n)**. +Therefore, the time complexity for the best, worst, and average case will always be **O(n log n)**. A disadvantage of the merge-sort algorithm is that it needs a temporary "working" array equal in size to the array being sorted. It is not an **in-place** sort, unlike for example [quicksort](../Quicksort/). diff --git a/Miller-Rabin Primality Test/MRPrimality.playground/Contents.swift b/Miller-Rabin Primality Test/MRPrimality.playground/Contents.swift index 98032bd59..35b457d1c 100644 --- a/Miller-Rabin Primality Test/MRPrimality.playground/Contents.swift +++ b/Miller-Rabin Primality Test/MRPrimality.playground/Contents.swift @@ -1,24 +1,26 @@ //: Playground - noun: a place where people can play -// Real primes -mrPrimalityTest(5) -mrPrimalityTest(439) -mrPrimalityTest(1201) -mrPrimalityTest(143477) -mrPrimalityTest(1299869) -mrPrimalityTest(15487361) -mrPrimalityTest(179426363) -mrPrimalityTest(32416187747) - -// Fake primes -mrPrimalityTest(15) -mrPrimalityTest(435) -mrPrimalityTest(1207) -mrPrimalityTest(143473) -mrPrimalityTest(1291869) -mrPrimalityTest(15487161) -mrPrimalityTest(178426363) -mrPrimalityTest(32415187747) - -// With iteration -mrPrimalityTest(32416190071, iteration: 10) +do { + // Real primes + try checkWithMillerRabin(5) + try checkWithMillerRabin(439) + try checkWithMillerRabin(1201) + try checkWithMillerRabin(143477) + try checkWithMillerRabin(1299869) + try checkWithMillerRabin(15487361) + try checkWithMillerRabin(179426363) + + // Fake primes + try checkWithMillerRabin(15) + try checkWithMillerRabin(435) + try checkWithMillerRabin(1207) + try checkWithMillerRabin(143473) + try checkWithMillerRabin(1291869) + try checkWithMillerRabin(15487161) + try checkWithMillerRabin(178426363) + + // Specifying accuracy + try checkWithMillerRabin(179426363, accuracy: 10) +} catch { + dump(error) +} \ No newline at end of file diff --git a/Miller-Rabin Primality Test/MRPrimality.playground/Sources/MRPrimality.swift b/Miller-Rabin Primality Test/MRPrimality.playground/Sources/MRPrimality.swift deleted file mode 100644 index ee2ace8b9..000000000 --- a/Miller-Rabin Primality Test/MRPrimality.playground/Sources/MRPrimality.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// MRPrimality.swift -// -// -// Created by Sahn Cha on 2016. 10. 18.. -// -// - -import Foundation - -/* - Miller-Rabin Primality Test. - One of the most used algorithms for primality testing. - Since this is a probabilistic algorithm, it needs to be executed multiple times to increase accuracy. - - Inputs: - n: UInt64 { target integer to be tested for primality } - k: Int { optional. number of iterations } - - Outputs: - true { probably prime } - false { composite } - */ -public func mrPrimalityTest(_ n: UInt64, iteration k: Int = 1) -> Bool { - guard n > 2 && n % 2 == 1 else { return false } - - var d = n - 1 - var s = 0 - - while d % 2 == 0 { - d /= 2 - s += 1 - } - - let range = UInt64.max - UInt64.max % (n - 2) - var r: UInt64 = 0 - repeat { - arc4random_buf(&r, MemoryLayout.size(ofValue: r)) - } while r >= range - - r = r % (n - 2) + 2 - - for _ in 1 ... k { - var x = powmod64(r, d, n) - if x == 1 || x == n - 1 { continue } - - if s == 1 { s = 2 } - - for _ in 1 ... s - 1 { - x = powmod64(x, 2, n) - if x == 1 { return false } - if x == n - 1 { break } - } - - if x != n - 1 { return false } - } - - return true -} - -/* - Calculates (base^exp) mod m. - - Inputs: - base: UInt64 { base } - exp: UInt64 { exponent } - m: UInt64 { modulus } - - Outputs: - the result - */ -private func powmod64(_ base: UInt64, _ exp: UInt64, _ m: UInt64) -> UInt64 { - if m == 1 { return 0 } - - var result: UInt64 = 1 - var b = base % m - var e = exp - - while e > 0 { - if e % 2 == 1 { result = mulmod64(result, b, m) } - b = mulmod64(b, b, m) - e >>= 1 - } - - return result -} - -/* - Calculates (first * second) mod m, hopefully without overflow. :] - - Inputs: - first: UInt64 { first integer } - second: UInt64 { second integer } - m: UInt64 { modulus } - - Outputs: - the result - */ -private func mulmod64(_ first: UInt64, _ second: UInt64, _ m: UInt64) -> UInt64 { - var result: UInt64 = 0 - var a = first - var b = second - - while a != 0 { - if a % 2 == 1 { result = ((result % m) + (b % m)) % m } // This may overflow if 'm' is a 64bit number && both 'result' and 'b' are very close to but smaller than 'm'. - a >>= 1 - b = (b << 1) % m - } - - return result -} diff --git a/Miller-Rabin Primality Test/MRPrimality.playground/Sources/MillerRabin.swift b/Miller-Rabin Primality Test/MRPrimality.playground/Sources/MillerRabin.swift new file mode 100644 index 000000000..03daac1fa --- /dev/null +++ b/Miller-Rabin Primality Test/MRPrimality.playground/Sources/MillerRabin.swift @@ -0,0 +1,100 @@ +// +// MRPrimality.swift +// +// +// Created by Sahn Cha on 2016. 10. 18.. +// +// + +import Foundation + +enum MillerRabinError: Error { + case primeLowAccuracy + case primeLowerBorder + case uIntOverflow +} + +/* + The Miller–Rabin test relies on an equality or set of equalities that + hold true for prime values, then checks whether or not they hold for + a number that we want to test for primality. + + - Parameter n: an odd integer to be tested for primality; + - Parameter k: a parameter that determines the accuracy of the test + - throws: Can throw an error of type `MillerRabinError`. + - Returns: composite if n is composite, otherwise probably prime + */ +public func checkWithMillerRabin(_ n: UInt, accuracy k: UInt = 1) throws -> Bool { + guard k > 0 else { throw MillerRabinError.primeLowAccuracy } + guard n > 0 else { throw MillerRabinError.primeLowerBorder } + guard n > 3 else { return true } + + // return false for all even numbers bigger than 2 + if n % 2 == 0 { + return false + } + + let s: UInt = UInt((n - 1).trailingZeroBitCount) + let d: UInt = (n - 1) >> s + + guard UInt(pow(2.0, Double(s))) * d == n - 1 else { throw MillerRabinError.primeLowerBorder } + + /// Inspect whether a given witness will reveal the true identity of n. + func tryComposite(_ a: UInt, d: UInt, n: UInt) throws -> Bool? { + var x = try calculateModularExponentiation(base: a, exponent: d, modulus: n) + if x == 1 || x == (n - 1) { + return nil + } + for _ in 1.. UInt { + guard modulus > 1 else { return 0 } + guard !(modulus-1).multipliedReportingOverflow(by: (modulus-1)).overflow else { + throw MillerRabinError.uIntOverflow + } + + var result: UInt = 1 + var exponentCopy = exponent + var baseCopy = base % modulus + + while exponentCopy > 0 { + if exponentCopy % 2 == 1 { + result = (result * baseCopy) % modulus + } + exponentCopy = exponentCopy >> 1 + baseCopy = (baseCopy * baseCopy) % modulus + } + + return result +} diff --git a/Miller-Rabin Primality Test/MRPrimality.playground/playground.xcworkspace/contents.xcworkspacedata b/Miller-Rabin Primality Test/MRPrimality.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Miller-Rabin Primality Test/MRPrimality.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Miller-Rabin Primality Test/MRPrimality.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Miller-Rabin Primality Test/MRPrimality.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Miller-Rabin Primality Test/MRPrimality.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Miller-Rabin Primality Test/MRPrimality.swift b/Miller-Rabin Primality Test/MRPrimality.swift index ee2ace8b9..24b9c674c 100644 --- a/Miller-Rabin Primality Test/MRPrimality.swift +++ b/Miller-Rabin Primality Test/MRPrimality.swift @@ -1,6 +1,6 @@ // // MRPrimality.swift -// +// // // Created by Sahn Cha on 2016. 10. 18.. // @@ -8,104 +8,93 @@ import Foundation -/* - Miller-Rabin Primality Test. - One of the most used algorithms for primality testing. - Since this is a probabilistic algorithm, it needs to be executed multiple times to increase accuracy. - - Inputs: - n: UInt64 { target integer to be tested for primality } - k: Int { optional. number of iterations } - - Outputs: - true { probably prime } - false { composite } - */ -public func mrPrimalityTest(_ n: UInt64, iteration k: Int = 1) -> Bool { - guard n > 2 && n % 2 == 1 else { return false } - - var d = n - 1 - var s = 0 - - while d % 2 == 0 { - d /= 2 - s += 1 - } - - let range = UInt64.max - UInt64.max % (n - 2) - var r: UInt64 = 0 - repeat { - arc4random_buf(&r, MemoryLayout.size(ofValue: r)) - } while r >= range - - r = r % (n - 2) + 2 - - for _ in 1 ... k { - var x = powmod64(r, d, n) - if x == 1 || x == n - 1 { continue } - - if s == 1 { s = 2 } - - for _ in 1 ... s - 1 { - x = powmod64(x, 2, n) - if x == 1 { return false } - if x == n - 1 { break } - } - - if x != n - 1 { return false } - } - - return true +enum MillerRabinError: Error { + case primeLowAccuracy + case primeLowerBorder + case uIntOverflow } /* - Calculates (base^exp) mod m. - - Inputs: - base: UInt64 { base } - exp: UInt64 { exponent } - m: UInt64 { modulus } - - Outputs: - the result - */ -private func powmod64(_ base: UInt64, _ exp: UInt64, _ m: UInt64) -> UInt64 { - if m == 1 { return 0 } + The Miller–Rabin test relies on an equality or set of equalities that + hold true for prime values, then checks whether or not they hold for + a number that we want to test for primality. + + - Parameter n: an odd integer to be tested for primality; + - Parameter k: a parameter that determines the accuracy of the test + - throws: Can throw an error of type `MillerRabinError`. + - Returns: composite if n is composite, otherwise probably prime +*/ +func checkWithMillerRabin(_ n: UInt, accuracy k: UInt = 1) throws -> Bool { + guard k > 0 else { throw MillerRabinError.primeLowAccuracy } + guard n > 0 else { throw MillerRabinError.primeLowerBorder } + guard n > 3 else { return true } + + // return false for all even numbers bigger than 2 + if n % 2 == 0 { + return false + } - var result: UInt64 = 1 - var b = base % m - var e = exp + let s: UInt = UInt((n - 1).trailingZeroBitCount) + let d: UInt = (n - 1) >> s + + guard UInt(pow(2.0, Double(s))) * d == n - 1 else { throw EncryptionError.primeLowerBorder } + + /// Inspect whether a given witness will reveal the true identity of n. + func tryComposite(_ a: UInt, d: UInt, n: UInt) throws -> Bool? { + var x = try calculateModularExponentiation(base: a, exponent: d, modulus: n) + if x == 1 || x == (n - 1) { + return nil + } + for _ in 1.. 0 { - if e % 2 == 1 { result = mulmod64(result, b, m) } - b = mulmod64(b, b, m) - e >>= 1 - } + for _ in 0.. UInt64 { - var result: UInt64 = 0 - var a = first - var b = second + Calculates the modular exponentiation based on `Applied Cryptography by Bruce Schneier.` + in `Schneier, Bruce (1996). Applied Cryptography: Protocols, Algorithms, + and Source Code in C, Second Edition (2nd ed.). Wiley. ISBN 978-0-471-11709-4.` + + - Parameter base: The natural base b. + - Parameter base: The natural exponent e. + - Parameter base: The natural modulus m. + - Throws: Can throw a `uIntOverflow` if the modulus' square exceeds the memory + limitations of UInt on the current system. + - Returns: The modular exponentiation c. +*/ +private func calculateModularExponentiation(base: UInt, exponent: UInt, modulus: UInt) throws -> UInt { + guard modulus > 1 else { return 0 } + guard !(modulus-1).multipliedReportingOverflow(by: (modulus-1)).overflow else { + throw MillerRabinError.uIntOverflow + } - while a != 0 { - if a % 2 == 1 { result = ((result % m) + (b % m)) % m } // This may overflow if 'm' is a 64bit number && both 'result' and 'b' are very close to but smaller than 'm'. - a >>= 1 - b = (b << 1) % m - } + var result: UInt = 1 + var exponentCopy = exponent + var baseCopy = base % modulus + + while exponentCopy > 0 { + if exponentCopy % 2 == 1 { + result = (result * baseCopy) % modulus + } + exponentCopy = exponentCopy >> 1 + baseCopy = (baseCopy * baseCopy) % modulus + } - return result + return result } diff --git a/Miller-Rabin Primality Test/README.markdown b/Miller-Rabin Primality Test/README.markdown index 07acb9e18..9cc677a2e 100644 --- a/Miller-Rabin Primality Test/README.markdown +++ b/Miller-Rabin Primality Test/README.markdown @@ -27,8 +27,8 @@ The following pseudo code is excerpted from Wikipedia[3]: ## Usage ```swift -mrPrimalityTest(7) // test if 7 is prime. (default iteration = 1) -mrPrimalityTest(7, iteration: 10) // test if 7 is prime && iterate 10 times. +checkWithMillerRabin(7) // test if 7 is prime. (default iteration = 1) +checkWithMillerRabin(7, accuracy: 10) // test if 7 is prime && iterate 10 times. ``` ## Reference @@ -37,6 +37,7 @@ mrPrimalityTest(7, iteration: 10) // test if 7 is prime && iterate 10 time 3. Miller–Rabin primality test - Wikipedia, the free encyclopedia _Written for Swift Algorithm Club by **Sahn Cha**, @scha00_ +_Code updated by **Simon C. Krüger**._ [1]: https://cs.uwaterloo.ca/research/tr/1975/CS-75-27.pdf [2]: http://www.sciencedirect.com/science/article/pii/0022314X80900840 diff --git a/Minimum Edit Distance/MinimumEditDistance.playground/Contents.swift b/Minimum Edit Distance/MinimumEditDistance.playground/Contents.swift old mode 100755 new mode 100644 index 238afeb9b..5db0aeb0d --- a/Minimum Edit Distance/MinimumEditDistance.playground/Contents.swift +++ b/Minimum Edit Distance/MinimumEditDistance.playground/Contents.swift @@ -1,31 +1,34 @@ -extension String { +// Minimum Edit Distance + +extension String { + public func minimumEditDistance(other: String) -> Int { - let m = self.characters.count - let n = other.characters.count + let m = self.count + let n = other.count var matrix = [[Int]](repeating: [Int](repeating: 0, count: n + 1), count: m + 1) - + // initialize matrix for index in 1...m { // the distance of any first string to an empty second string matrix[index][0] = index } - + for index in 1...n { // the distance of any second string to an empty first string matrix[0][index] = index } - + // compute Levenshtein distance - 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 { // substitution of equal symbols with cost 0 matrix[i + 1][j + 1] = matrix[i][j] } else { // minimum of the cost of insertion, deletion, or substitution // added to the already computed costs in the corresponding cells - matrix[i + 1][j + 1] = min(matrix[i][j] + 1, matrix[i + 1][j] + 1, matrix[i][j + 1] + 1) + matrix[i + 1][j + 1] = Swift.min(matrix[i][j] + 1, matrix[i + 1][j] + 1, matrix[i][j + 1] + 1) } } } diff --git a/Minimum Edit Distance/MinimumEditDistance.playground/contents.xcplayground b/Minimum Edit Distance/MinimumEditDistance.playground/contents.xcplayground old mode 100755 new mode 100644 diff --git a/Minimum Edit Distance/README.markdown b/Minimum Edit Distance/README.markdown index 905b358ae..c8e5e2f6d 100755 --- a/Minimum Edit Distance/README.markdown +++ b/Minimum Edit Distance/README.markdown @@ -37,15 +37,15 @@ Then in each cell the minimum of the cost of insertion, deletion, or substitutio ```swift // compute Levenshtein distance -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 { // substitution of equal symbols with cost 0 matrix[i + 1][j + 1] = matrix[i][j] } else { // minimum of the cost of insertion, deletion, or substitution // added to the already computed costs in the corresponding cells - matrix[i + 1][j + 1] = min(matrix[i][j] + 1, matrix[i + 1][j] + 1, matrix[i][j + 1] + 1) + matrix[i + 1][j + 1] = Swift.min(matrix[i][j] + 1, matrix[i + 1][j] + 1, matrix[i][j + 1] + 1) } } } diff --git a/Minimum Spanning Tree (Unweighted)/MinimumSpanningTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Minimum Spanning Tree (Unweighted)/MinimumSpanningTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Minimum Spanning Tree (Unweighted)/MinimumSpanningTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj b/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj index a066b7a71..afb3b8acb 100644 --- a/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj +++ b/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj @@ -89,12 +89,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0820; + LastSwiftMigration = 1000; }; }; }; @@ -149,13 +149,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -193,13 +203,23 @@ 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; CODE_SIGN_IDENTITY = "-"; @@ -218,6 +238,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; @@ -231,7 +252,8 @@ PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -244,7 +266,8 @@ 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_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 8ef8d8581..afd69e6a7 100644 --- a/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Minimum Spanning Tree (Unweighted)/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ (graph: Graph) -> (cost: Int, tree: Graph() let sortedEdgeListByWeight = graph.edgeList.sorted(by: { $0.weight < $1.weight }) - + var unionFind = UnionFind() for vertex in graph.vertices { unionFind.addSetWith(vertex) } - + for edge in sortedEdgeListByWeight { let v1 = edge.vertex1 let v2 = edge.vertex2 @@ -25,6 +25,6 @@ func minimumSpanningTreeKruskal(graph: Graph) -> (cost: Int, tree: Graph(graph: Graph) -> (cost: Int, tree: Graph) { - var cost: Int = 0 - var tree = Graph() - let sortedEdgeListByWeight = graph.edgeList.sorted(by: { $0.weight < $1.weight }) - - var unionFind = UnionFind() - for vertex in graph.vertices { - unionFind.addSetWith(vertex) - } - - for edge in sortedEdgeListByWeight { - let v1 = edge.vertex1 - let v2 = edge.vertex2 - if !unionFind.inSameSet(v1, and: v2) { - cost += edge.weight - tree.addEdge(edge) - unionFind.unionSetsContaining(v1, and: v2) + var cost: Int = 0 + var tree = Graph() + let sortedEdgeListByWeight = graph.edgeList.sorted(by: { $0.weight < $1.weight }) + + var unionFind = UnionFind() + for vertex in graph.vertices { + unionFind.addSetWith(vertex) } - } - - return (cost: cost, tree: tree) + + for edge in sortedEdgeListByWeight { + let v1 = edge.vertex1 + let v2 = edge.vertex2 + if !unionFind.inSameSet(v1, and: v2) { + cost += edge.weight + tree.addEdge(edge) + unionFind.unionSetsContaining(v1, and: v2) + } + } + + return (cost: cost, tree: tree) } func minimumSpanningTreePrim(graph: Graph) -> (cost: Int, tree: Graph) { - var cost: Int = 0 - var tree = Graph() - - guard let start = graph.vertices.first else { - return (cost: cost, tree: tree) - } - - var visited = Set() - var priorityQueue = PriorityQueue<(vertex: T, weight: Int, parent: T?)>( - sort: { $0.weight < $1.weight }) - - priorityQueue.enqueue((vertex: start, weight: 0, parent: nil)) - while let head = priorityQueue.dequeue() { - let vertex = head.vertex - if visited.contains(vertex) { - continue - } - visited.insert(vertex) - - cost += head.weight - if let prev = head.parent { - tree.addEdge(vertex1: prev, vertex2: vertex, weight: head.weight) + var cost: Int = 0 + var tree = Graph() + + guard let start = graph.vertices.first else { + return (cost: cost, tree: tree) } - - if let neighbours = graph.adjList[vertex] { - for neighbour in neighbours { - let nextVertex = neighbour.vertex - if !visited.contains(nextVertex) { - priorityQueue.enqueue((vertex: nextVertex, weight: neighbour.weight, parent: vertex)) + + var visited = Set() + var priorityQueue = PriorityQueue<(vertex: T, weight: Int, parent: T?)>( + sort: { $0.weight < $1.weight }) + + priorityQueue.enqueue((vertex: start, weight: 0, parent: nil)) + while let head = priorityQueue.dequeue() { + let vertex = head.vertex + if visited.contains(vertex) { + continue + } + visited.insert(vertex) + + cost += head.weight + if let prev = head.parent { + tree.addEdge(vertex1: prev, vertex2: vertex, weight: head.weight) + } + + if let neighbours = graph.adjList[vertex] { + for neighbour in neighbours { + let nextVertex = neighbour.vertex + if !visited.contains(nextVertex) { + priorityQueue.enqueue((vertex: nextVertex, weight: neighbour.weight, parent: vertex)) + } + } } - } } - } - - return (cost: cost, tree: tree) + + return (cost: cost, tree: tree) } /*: diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Heap.swift b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Heap.swift index 80b40c65f..8a2c2aa07 100644 --- a/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Heap.swift +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Heap.swift @@ -147,7 +147,7 @@ public struct Heap { let size = elements.count - 1 if index != size { - swap(&elements[index], &elements[size]) + elements.swapAt(index,size) shiftDown(index, heapSize: size) shiftUp(index) } @@ -200,7 +200,7 @@ public struct Heap { } if first == parentIndex { return } - swap(&elements[parentIndex], &elements[first]) + elements.swapAt(parentIndex,first) parentIndex = first } } diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Minimum Spanning Tree/MinimumSpanningTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Minimum Spanning Tree/Prim.swift b/Minimum Spanning Tree/Prim.swift index d65d53f29..b05fc1b1f 100644 --- a/Minimum Spanning Tree/Prim.swift +++ b/Minimum Spanning Tree/Prim.swift @@ -9,15 +9,15 @@ func minimumSpanningTreePrim(graph: Graph) -> (cost: Int, tree: Graph) { var cost: Int = 0 var tree = Graph() - + guard let start = graph.vertices.first else { return (cost: cost, tree: tree) } - + var visited = Set() var priorityQueue = PriorityQueue<(vertex: T, weight: Int, parent: T?)>( sort: { $0.weight < $1.weight }) - + priorityQueue.enqueue((vertex: start, weight: 0, parent: nil)) while let head = priorityQueue.dequeue() { let vertex = head.vertex @@ -25,12 +25,12 @@ func minimumSpanningTreePrim(graph: Graph) -> (cost: Int, tree: Graph) continue } visited.insert(vertex) - + cost += head.weight if let prev = head.parent { tree.addEdge(vertex1: prev, vertex2: vertex, weight: head.weight) } - + if let neighbours = graph.adjList[vertex] { for neighbour in neighbours { let nextVertex = neighbour.vertex @@ -40,6 +40,6 @@ func minimumSpanningTreePrim(graph: Graph) -> (cost: Int, tree: Graph) } } } - + return (cost: cost, tree: tree) } diff --git a/Minimum Spanning Tree/README.markdown b/Minimum Spanning Tree/README.markdown index c47713701..6ae7a89d9 100644 --- a/Minimum Spanning Tree/README.markdown +++ b/Minimum Spanning Tree/README.markdown @@ -1,5 +1,7 @@ # Minimum Spanning Tree (Weighted Graph) +> This topic has been tutorialized [here](https://www.raywenderlich.com/169392/swift-algorithm-club-minimum-spanning-tree-with-prims-algorithm) + A [minimum spanning tree](https://en.wikipedia.org/wiki/Minimum_spanning_tree) (MST) of a connected undirected weighted graph has a subset of the edges from the original graph that connects all the vertices together, without any cycles and with the minimum possible total edge weight. There can be more than one MSTs of a graph. There are two popular algorithms to calculate MST of a graph - [Kruskal's algorithm](https://en.wikipedia.org/wiki/Kruskal's_algorithm) and [Prim's algorithm](https://en.wikipedia.org/wiki/Prim's_algorithm). Both algorithms have a total time complexity of `O(ElogE)` where `E` is the number of edges from the original graph. diff --git a/MinimumCoinChange/Tests/MinimumCoinChangeTests/MinimumCoinChangeTests.swift b/MinimumCoinChange/Tests/MinimumCoinChangeTests/MinimumCoinChangeTests.swift index 7c106eeb9..45beff775 100644 --- a/MinimumCoinChange/Tests/MinimumCoinChangeTests/MinimumCoinChangeTests.swift +++ b/MinimumCoinChange/Tests/MinimumCoinChangeTests/MinimumCoinChangeTests.swift @@ -2,27 +2,34 @@ import XCTest @testable import MinimumCoinChange class MinimumCoinChangeTests: XCTestCase { - func testExample() { - let mcc = MinimumCoinChange(coinSet: [1, 2, 5, 10, 20, 25]) - print("Coin set: \(mcc.sortedCoinSet)") - - do { - for i in 0..<100 { - let greedy = try mcc.changeGreedy(i) - let dynamic = try mcc.changeDynamic(i) - - XCTAssertEqual(greedy.reduce(0, +), dynamic.reduce(0, +), "Greedy and Dynamic return two different changes") - - if greedy.count != dynamic.count { - print("\(i): greedy = \(greedy) dynamic = \(dynamic)") - } - } - } catch { - XCTFail("Test Failed: impossible to change with the given coin set") + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + + func testExample() { + let mcc = MinimumCoinChange(coinSet: [1, 2, 5, 10, 20, 25]) + print("Coin set: \(mcc.sortedCoinSet)") + + do { + for i in 0..<100 { + let greedy = try mcc.changeGreedy(i) + let dynamic = try mcc.changeDynamic(i) + + XCTAssertEqual(greedy.reduce(0, +), dynamic.reduce(0, +), "Greedy and Dynamic return two different changes") + + if greedy.count != dynamic.count { + print("\(i): greedy = \(greedy) dynamic = \(dynamic)") } + } + } catch { + XCTFail("Test Failed: impossible to change with the given coin set") } - - static var allTests = [ - ("testExample", testExample), + } + + static var allTests = [ + ("testExample", testExample), ] } diff --git a/Monty Hall Problem/MontyHall.playground/Contents.swift b/Monty Hall Problem/MontyHall.playground/Contents.swift index bcdafd137..a809410c7 100644 --- a/Monty Hall Problem/MontyHall.playground/Contents.swift +++ b/Monty Hall Problem/MontyHall.playground/Contents.swift @@ -43,7 +43,7 @@ func playRound() { } // Run the simulation a large number of times. -for i in 1...5000 { +for _ in 1...5000 { playRound() } diff --git a/Multiset/Multiset.playground/Contents.swift b/Multiset/Multiset.playground/Contents.swift new file mode 100644 index 000000000..64b729df1 --- /dev/null +++ b/Multiset/Multiset.playground/Contents.swift @@ -0,0 +1,38 @@ +//: Playground - noun: a place where people can play + +import Cocoa + +var set = Multiset() + +set.add("Foo") +set.add("Foo") +set.add("Bar") +set.add("Baz") + +set.count +set.count(for: "Foo") + +set.allItems + +var set2 = Multiset() +set2.add("Foo") +set2.add("Foo") + +set2.isSubSet(of: set) // true +set.isSubSet(of: set2) // false + +// Convenience constructor: pass a Collection of Hashables to the constructor +var cacti = Multiset("cacti") +cacti.allItems +var tactical = Multiset("tactical") +cacti.isSubSet(of: tactical) // true +tactical.isSubSet(of: cacti) // false + +// Test ExpressibleByArrayLiteral protocol +let set3: Multiset = ["foo", "bar"] +set3.count(for: "foo") + +// Test Equatable protocol +let set4 = Multiset(set3.allItems) +set4 == set3 // true +set4 == set // false diff --git a/Multiset/Multiset.playground/Sources/Multiset.swift b/Multiset/Multiset.playground/Sources/Multiset.swift new file mode 100644 index 000000000..5dba5d32c --- /dev/null +++ b/Multiset/Multiset.playground/Sources/Multiset.swift @@ -0,0 +1,84 @@ +// +// Multiset.swift +// Multiset +// +// Created by Simon Whitaker on 28/08/2017. +// + +import Foundation + +public struct Multiset { + private var storage: [T: UInt] = [:] + public private(set) var count: UInt = 0 + + public init() {} + + public init(_ collection: C) where C.Element == T { + for element in collection { + self.add(element) + } + } + + public mutating func add (_ elem: T) { + storage[elem, default: 0] += 1 + count += 1 + } + + public mutating func remove (_ elem: T) { + if let currentCount = storage[elem] { + if currentCount > 1 { + storage[elem] = currentCount - 1 + } else { + storage.removeValue(forKey: elem) + } + count -= 1 + } + } + + public func isSubSet (of superset: Multiset) -> Bool { + for (key, count) in storage { + let supersetcount = superset.storage[key] ?? 0 + if count > supersetcount { + return false + } + } + return true + } + + public func count(for key: T) -> UInt { + return storage[key] ?? 0 + } + + public var allItems: [T] { + var result = [T]() + for (key, count) in storage { + for _ in 0 ..< count { + result.append(key) + } + } + return result + } +} + +// MARK: - Equatable +extension Multiset: Equatable { + public static func == (lhs: Multiset, rhs: Multiset) -> Bool { + if lhs.storage.count != rhs.storage.count { + return false + } + for (lkey, lcount) in lhs.storage { + let rcount = rhs.storage[lkey] ?? 0 + if lcount != rcount { + return false + } + } + return true + } +} + +// MARK: - ExpressibleByArrayLiteral +extension Multiset: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: T...) { + self.init(elements) + } +} diff --git a/Multiset/Multiset.playground/contents.xcplayground b/Multiset/Multiset.playground/contents.xcplayground new file mode 100644 index 000000000..63b6dd8df --- /dev/null +++ b/Multiset/Multiset.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Multiset/README.markdown b/Multiset/README.markdown new file mode 100644 index 000000000..cdd0af885 --- /dev/null +++ b/Multiset/README.markdown @@ -0,0 +1,104 @@ +# Multiset + +A multiset (also known as a bag) is a data structure similar to a regular set, but it can store multiple instances of the same element. + +For example, if I added the elements 1, 2, 2 to a regular set, the set would only contain two items, since adding 2 a second time has no effect. + +``` +var set = Set() +set.add(1) // set is now [1] +set.add(2) // set is now [1, 2] +set.add(2) // set is still [1, 2] +``` + +By comparison, after adding the elements 1, 2, 2 to a multiset, it would contain three items. + +``` +var set = Multiset() +set.add(1) // set is now [1] +set.add(2) // set is now [1, 2] +set.add(2) // set is now [1, 2, 2] +``` + +You might be thinking that this looks an awful lot like an array. So why would you use a multiset? Let's consider the differences between the two… + +- Ordering: arrays maintain the order of items added to them, multisets do not +- Testing for membership: testing whether an element is a member of the collection is O(N) for arrays, O(1) for multisets. +- Testing for subset: testing whether collection X is a subset of collection Y is a simple operation for a multiset, but complex for arrays + +Typical operations on a multiset are: + +- Add an element +- Remove an element +- Get the count for an element (the number of times it's been added) +- Get the count for the whole set (the number of items that have been added) +- Check whether it is a subset of another multiset + +One real-world use of multisets is to determine whether one string is a partial anagram of another. For example, the word "cacti" is a partial anagrams of "tactical". (In other words, I can rearrange the letters of "tactical" to make "cacti", with some letters left over.) + +``` swift +var cacti = Multiset("cacti") +var tactical = Multiset("tactical") +cacti.isSubSet(of: tactical) // true! +``` + +## Implementation + +Under the hood, this implementation of Multiset uses a dictionary to store a mapping of elements to the number of times they've been added. + +Here's the essence of it: + +``` swift +public struct Multiset { + private var storage: [Element: UInt] = [:] + + public init() {} +``` + +And here's how you'd use this class to create a multiset of strings: + +``` swift +var set = Multiset() +``` + +Adding an element is a case of incrementing the counter for that element, or setting it to 1 if it doesn't already exist: + +``` swift +public mutating func add (_ elem: Element) { + storage[elem, default: 0] += 1 +} +``` + +Here's how you'd use this method to add to the set we created earlier: + +```swift +set.add("foo") +set.add("foo") +set.allItems // returns ["foo", "foo"] +``` + +Our set now contains two elements, both the string "foo". + +Removing an element works much the same way as adding; decrement the counter for the element, or remove it from the underlying dictionary if its value is 1 before removal. + +``` swift +public mutating func remove (_ elem: Element) { + if let currentCount = storage[elem] { + if currentCount > 1 { + storage[elem] = currentCount - 1 + } else { + storage.removeValue(forKey: elem) + } + } +} +``` + +Getting the count for an item is simple: we just return the value for the given item in the internal dictionary. + +``` swift +public func count(for key: Element) -> UInt { + return storage[key] ?? 0 +} +``` + +*Written for the Swift Algorithm Club by Simon Whitaker* 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/Naive Bayes Classifier/NaiveBayes.playground/Contents.swift b/Naive Bayes Classifier/NaiveBayes.playground/Contents.swift new file mode 100644 index 000000000..6a2e01aab --- /dev/null +++ b/Naive Bayes Classifier/NaiveBayes.playground/Contents.swift @@ -0,0 +1,110 @@ +import Foundation + +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + +/*: + ## Naive Bayes Classifier + + This playground uses the given algorithm and utilizes its features with some example datasets + + ### Gaussian Naive Bayes + - Note: + When using Gaussian NB you have to have continuous features (Double). + + For this example we are going to use a famous dataset with different types of wine. The labels of the features can be viewed [here](https://gist.github.com/tijptjik/9408623) + */ +guard let wineCSV = Bundle.main.path(forResource: "wine", ofType: "csv") else { + print("Resource could not be found!") + exit(0) +} + +guard let csv = try? String(contentsOfFile: wineCSV) else { + print("File could not be read!") + exit(0) +} + +/*: + Reading the .csv file line per line + */ +let rows = csv.characters.split(separator: "\r\n").map { String($0) } +/*: + Splitting on the ; sign and converting the value to a Double + + - Important: + Do not force unwrap the mapped values in your real application. Carefully convert them! This is just for the sake of showing how the algorithm works. + */ +let wineData = rows.map { row -> [Double] in + let split = row.characters.split(separator: ";") + return split.map { Double(String($0))! } +} + +/*: + The algorithm wants the classes and the data seperated since this gives a huge performance boost. Also I haven't implemented this in the NB class itself since it is not in the scope of it. + */ +let rowOfClasses = 0 +let classes = wineData.map { Int($0[rowOfClasses]) } +let data = wineData.map { row in + return row.enumerated().filter { $0.offset != rowOfClasses }.map { $0.element } +} + +/*: + Again use `guard` on the result of a `try?` or simply `do-try-catch` because this would crash your application if an error occured. + + The array in the `classifyProba` method I passed is a former entry in the .csv file which I removed in order to classify it. + */ +let wineBayes = try! NaiveBayes(type: .gaussian, data: data, classes: classes).train() +let result = wineBayes.classifyProba(with: [12.85, 1.6, 2.52, 17.8, 95, 2.48, 2.37, 0.26, 1.46, 3.93, 1.09, 3.63, 1015]) +/*: + I can assure you that ***class 1*** is the correct result and as you can see the classifier thinks that its ***99.99%*** likely too. + + ### Multinomial Naive Bayes + + - Note: + When using Multinomial NB you have to have categorical features (Int). + + Now this dataset is commonly used to describe the classification problem and it is categorical which means you don't have real values you just have categorical data as stated before. The structure of this dataset is as follows. + + Outlook,Temperature,Humidity,Windy + + ***Outlook***: 0 = rainy, 1 = overcast, 2 = sunny + + ***Temperature***: 0 = hot, 1 = mild, 2 = cool + + ***Humidity***: 0 = high, 1 = normal + + ***Windy***: 0 = false, 1 = true + + The classes are either he will play golf or not depending on the weather conditions. (0 = won't play, 1 = will play) + */ + +let golfData = [ + [0, 0, 0, 0], + [0, 0, 0, 1], + [1, 0, 0, 0], + [2, 1, 0, 0], + [2, 2, 1, 0], + [2, 2, 1, 1], + [1, 2, 1, 1], + [0, 1, 0, 0], + [0, 2, 1, 0], + [2, 1, 1, 0], + [0, 1, 1, 1], + [1, 1, 0, 1], + [1, 0, 1, 0], + [2, 1, 0, 1] +] +let golfClasses = [0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0] + +let golfNaive = try! NaiveBayes(type: .multinomial, data: golfData, classes: golfClasses).train() + +/*: + The weather conditions is as follows now: Outlook=rainy, Temperature=cool, Humidity=high, Windy=true + */ +let golfResult = golfNaive.classifyProba(with: [0, 2, 0, 1]) + +/*: + Naive Bayes tells us that the golf player will ***not*** play with a likelihood of almost ***80%***. Which is true of course. + */ diff --git a/Naive Bayes Classifier/NaiveBayes.playground/Resources/wine.csv b/Naive Bayes Classifier/NaiveBayes.playground/Resources/wine.csv new file mode 100644 index 000000000..89e14fd02 --- /dev/null +++ b/Naive Bayes Classifier/NaiveBayes.playground/Resources/wine.csv @@ -0,0 +1,177 @@ +1;14.23;1.71;2.43;15.6;127;2.8;3.06;.28;2.29;5.64;1.04;3.92;1065 +1;13.2;1.78;2.14;11.2;100;2.65;2.76;.26;1.28;4.38;1.05;3.4;1050 +1;13.16;2.36;2.67;18.6;101;2.8;3.24;.3;2.81;5.68;1.03;3.17;1185 +1;14.37;1.95;2.5;16.8;113;3.85;3.49;.24;2.18;7.8;.86;3.45;1480 +1;13.24;2.59;2.87;21;118;2.8;2.69;.39;1.82;4.32;1.04;2.93;735 +1;14.2;1.76;2.45;15.2;112;3.27;3.39;.34;1.97;6.75;1.05;2.85;1450 +1;14.39;1.87;2.45;14.6;96;2.5;2.52;.3;1.98;5.25;1.02;3.58;1290 +1;14.06;2.15;2.61;17.6;121;2.6;2.51;.31;1.25;5.05;1.06;3.58;1295 +1;14.83;1.64;2.17;14;97;2.8;2.98;.29;1.98;5.2;1.08;2.85;1045 +1;13.86;1.35;2.27;16;98;2.98;3.15;.22;1.85;7.22;1.01;3.55;1045 +1;14.1;2.16;2.3;18;105;2.95;3.32;.22;2.38;5.75;1.25;3.17;1510 +1;14.12;1.48;2.32;16.8;95;2.2;2.43;.26;1.57;5;1.17;2.82;1280 +1;13.75;1.73;2.41;16;89;2.6;2.76;.29;1.81;5.6;1.15;2.9;1320 +1;14.75;1.73;2.39;11.4;91;3.1;3.69;.43;2.81;5.4;1.25;2.73;1150 +1;14.38;1.87;2.38;12;102;3.3;3.64;.29;2.96;7.5;1.2;3;1547 +1;13.63;1.81;2.7;17.2;112;2.85;2.91;.3;1.46;7.3;1.28;2.88;1310 +1;14.3;1.92;2.72;20;120;2.8;3.14;.33;1.97;6.2;1.07;2.65;1280 +1;13.83;1.57;2.62;20;115;2.95;3.4;.4;1.72;6.6;1.13;2.57;1130 +1;14.19;1.59;2.48;16.5;108;3.3;3.93;.32;1.86;8.7;1.23;2.82;1680 +1;13.64;3.1;2.56;15.2;116;2.7;3.03;.17;1.66;5.1;.96;3.36;845 +1;14.06;1.63;2.28;16;126;3;3.17;.24;2.1;5.65;1.09;3.71;780 +1;12.93;3.8;2.65;18.6;102;2.41;2.41;.25;1.98;4.5;1.03;3.52;770 +1;13.71;1.86;2.36;16.6;101;2.61;2.88;.27;1.69;3.8;1.11;4;1035 +1;13.5;1.81;2.61;20;96;2.53;2.61;.28;1.66;3.52;1.12;3.82;845 +1;13.05;2.05;3.22;25;124;2.63;2.68;.47;1.92;3.58;1.13;3.2;830 +1;13.39;1.77;2.62;16.1;93;2.85;2.94;.34;1.45;4.8;.92;3.22;1195 +1;13.3;1.72;2.14;17;94;2.4;2.19;.27;1.35;3.95;1.02;2.77;1285 +1;13.87;1.9;2.8;19.4;107;2.95;2.97;.37;1.76;4.5;1.25;3.4;915 +1;14.02;1.68;2.21;16;96;2.65;2.33;.26;1.98;4.7;1.04;3.59;1035 +1;13.73;1.5;2.7;22.5;101;3;3.25;.29;2.38;5.7;1.19;2.71;1285 +1;13.58;1.66;2.36;19.1;106;2.86;3.19;.22;1.95;6.9;1.09;2.88;1515 +1;13.68;1.83;2.36;17.2;104;2.42;2.69;.42;1.97;3.84;1.23;2.87;990 +1;13.76;1.53;2.7;19.5;132;2.95;2.74;.5;1.35;5.4;1.25;3;1235 +1;13.51;1.8;2.65;19;110;2.35;2.53;.29;1.54;4.2;1.1;2.87;1095 +1;13.48;1.81;2.41;20.5;100;2.7;2.98;.26;1.86;5.1;1.04;3.47;920 +1;13.28;1.64;2.84;15.5;110;2.6;2.68;.34;1.36;4.6;1.09;2.78;880 +1;13.05;1.65;2.55;18;98;2.45;2.43;.29;1.44;4.25;1.12;2.51;1105 +1;13.07;1.5;2.1;15.5;98;2.4;2.64;.28;1.37;3.7;1.18;2.69;1020 +1;14.22;3.99;2.51;13.2;128;3;3.04;.2;2.08;5.1;.89;3.53;760 +1;13.56;1.71;2.31;16.2;117;3.15;3.29;.34;2.34;6.13;.95;3.38;795 +1;13.41;3.84;2.12;18.8;90;2.45;2.68;.27;1.48;4.28;.91;3;1035 +1;13.88;1.89;2.59;15;101;3.25;3.56;.17;1.7;5.43;.88;3.56;1095 +1;13.24;3.98;2.29;17.5;103;2.64;2.63;.32;1.66;4.36;.82;3;680 +1;13.05;1.77;2.1;17;107;3;3;.28;2.03;5.04;.88;3.35;885 +1;14.21;4.04;2.44;18.9;111;2.85;2.65;.3;1.25;5.24;.87;3.33;1080 +1;14.38;3.59;2.28;16;102;3.25;3.17;.27;2.19;4.9;1.04;3.44;1065 +1;13.9;1.68;2.12;16;101;3.1;3.39;.21;2.14;6.1;.91;3.33;985 +1;14.1;2.02;2.4;18.8;103;2.75;2.92;.32;2.38;6.2;1.07;2.75;1060 +1;13.94;1.73;2.27;17.4;108;2.88;3.54;.32;2.08;8.90;1.12;3.1;1260 +1;13.05;1.73;2.04;12.4;92;2.72;3.27;.17;2.91;7.2;1.12;2.91;1150 +1;13.83;1.65;2.6;17.2;94;2.45;2.99;.22;2.29;5.6;1.24;3.37;1265 +1;13.82;1.75;2.42;14;111;3.88;3.74;.32;1.87;7.05;1.01;3.26;1190 +1;13.77;1.9;2.68;17.1;115;3;2.79;.39;1.68;6.3;1.13;2.93;1375 +1;13.74;1.67;2.25;16.4;118;2.6;2.9;.21;1.62;5.85;.92;3.2;1060 +1;13.56;1.73;2.46;20.5;116;2.96;2.78;.2;2.45;6.25;.98;3.03;1120 +1;14.22;1.7;2.3;16.3;118;3.2;3;.26;2.03;6.38;.94;3.31;970 +1;13.29;1.97;2.68;16.8;102;3;3.23;.31;1.66;6;1.07;2.84;1270 +1;13.72;1.43;2.5;16.7;108;3.4;3.67;.19;2.04;6.8;.89;2.87;1285 +2;12.37;.94;1.36;10.6;88;1.98;.57;.28;.42;1.95;1.05;1.82;520 +2;12.33;1.1;2.28;16;101;2.05;1.09;.63;.41;3.27;1.25;1.67;680 +2;12.64;1.36;2.02;16.8;100;2.02;1.41;.53;.62;5.75;.98;1.59;450 +2;13.67;1.25;1.92;18;94;2.1;1.79;.32;.73;3.8;1.23;2.46;630 +2;12.37;1.13;2.16;19;87;3.5;3.1;.19;1.87;4.45;1.22;2.87;420 +2;12.17;1.45;2.53;19;104;1.89;1.75;.45;1.03;2.95;1.45;2.23;355 +2;12.37;1.21;2.56;18.1;98;2.42;2.65;.37;2.08;4.6;1.19;2.3;678 +2;13.11;1.01;1.7;15;78;2.98;3.18;.26;2.28;5.3;1.12;3.18;502 +2;12.37;1.17;1.92;19.6;78;2.11;2;.27;1.04;4.68;1.12;3.48;510 +2;13.34;.94;2.36;17;110;2.53;1.3;.55;.42;3.17;1.02;1.93;750 +2;12.21;1.19;1.75;16.8;151;1.85;1.28;.14;2.5;2.85;1.28;3.07;718 +2;12.29;1.61;2.21;20.4;103;1.1;1.02;.37;1.46;3.05;906;1.82;870 +2;13.86;1.51;2.67;25;86;2.95;2.86;.21;1.87;3.38;1.36;3.16;410 +2;13.49;1.66;2.24;24;87;1.88;1.84;.27;1.03;3.74;.98;2.78;472 +2;12.99;1.67;2.6;30;139;3.3;2.89;.21;1.96;3.35;1.31;3.5;985 +2;11.96;1.09;2.3;21;101;3.38;2.14;.13;1.65;3.21;.99;3.13;886 +2;11.66;1.88;1.92;16;97;1.61;1.57;.34;1.15;3.8;1.23;2.14;428 +2;13.03;.9;1.71;16;86;1.95;2.03;.24;1.46;4.6;1.19;2.48;392 +2;11.84;2.89;2.23;18;112;1.72;1.32;.43;.95;2.65;.96;2.52;500 +2;12.33;.99;1.95;14.8;136;1.9;1.85;.35;2.76;3.4;1.06;2.31;750 +2;12.7;3.87;2.4;23;101;2.83;2.55;.43;1.95;2.57;1.19;3.13;463 +2;12;.92;2;19;86;2.42;2.26;.3;1.43;2.5;1.38;3.12;278 +2;12.72;1.81;2.2;18.8;86;2.2;2.53;.26;1.77;3.9;1.16;3.14;714 +2;12.08;1.13;2.51;24;78;2;1.58;.4;1.4;2.2;1.31;2.72;630 +2;13.05;3.86;2.32;22.5;85;1.65;1.59;.61;1.62;4.8;.84;2.01;515 +2;11.84;.89;2.58;18;94;2.2;2.21;.22;2.35;3.05;.79;3.08;520 +2;12.67;.98;2.24;18;99;2.2;1.94;.3;1.46;2.62;1.23;3.16;450 +2;12.16;1.61;2.31;22.8;90;1.78;1.69;.43;1.56;2.45;1.33;2.26;495 +2;11.65;1.67;2.62;26;88;1.92;1.61;.4;1.34;2.6;1.36;3.21;562 +2;11.64;2.06;2.46;21.6;84;1.95;1.69;.48;1.35;2.8;1;2.75;680 +2;12.08;1.33;2.3;23.6;70;2.2;1.59;.42;1.38;1.74;1.07;3.21;625 +2;12.08;1.83;2.32;18.5;81;1.6;1.5;.52;1.64;2.4;1.08;2.27;480 +2;12;1.51;2.42;22;86;1.45;1.25;.5;1.63;3.6;1.05;2.65;450 +2;12.69;1.53;2.26;20.7;80;1.38;1.46;.58;1.62;3.05;.96;2.06;495 +2;12.29;2.83;2.22;18;88;2.45;2.25;.25;1.99;2.15;1.15;3.3;290 +2;11.62;1.99;2.28;18;98;3.02;2.26;.17;1.35;3.25;1.16;2.96;345 +2;12.47;1.52;2.2;19;162;2.5;2.27;.32;3.28;2.6;1.16;2.63;937 +2;11.81;2.12;2.74;21.5;134;1.6;.99;.14;1.56;2.5;.95;2.26;625 +2;12.29;1.41;1.98;16;85;2.55;2.5;.29;1.77;2.9;1.23;2.74;428 +2;12.37;1.07;2.1;18.5;88;3.52;3.75;.24;1.95;4.5;1.04;2.77;660 +2;12.29;3.17;2.21;18;88;2.85;2.99;.45;2.81;2.3;1.42;2.83;406 +2;12.08;2.08;1.7;17.5;97;2.23;2.17;.26;1.4;3.3;1.27;2.96;710 +2;12.6;1.34;1.9;18.5;88;1.45;1.36;.29;1.35;2.45;1.04;2.77;562 +2;12.34;2.45;2.46;21;98;2.56;2.11;.34;1.31;2.8;.8;3.38;438 +2;11.82;1.72;1.88;19.5;86;2.5;1.64;.37;1.42;2.06;.94;2.44;415 +2;12.51;1.73;1.98;20.5;85;2.2;1.92;.32;1.48;2.94;1.04;3.57;672 +2;12.42;2.55;2.27;22;90;1.68;1.84;.66;1.42;2.7;.86;3.3;315 +2;12.25;1.73;2.12;19;80;1.65;2.03;.37;1.63;3.4;1;3.17;510 +2;12.72;1.75;2.28;22.5;84;1.38;1.76;.48;1.63;3.3;.88;2.42;488 +2;12.22;1.29;1.94;19;92;2.36;2.04;.39;2.08;2.7;.86;3.02;312 +2;11.61;1.35;2.7;20;94;2.74;2.92;.29;2.49;2.65;.96;3.26;680 +2;11.46;3.74;1.82;19.5;107;3.18;2.58;.24;3.58;2.9;.75;2.81;562 +2;12.52;2.43;2.17;21;88;2.55;2.27;.26;1.22;2;.9;2.78;325 +2;11.76;2.68;2.92;20;103;1.75;2.03;.6;1.05;3.8;1.23;2.5;607 +2;11.41;.74;2.5;21;88;2.48;2.01;.42;1.44;3.08;1.1;2.31;434 +2;12.08;1.39;2.5;22.5;84;2.56;2.29;.43;1.04;2.9;.93;3.19;385 +2;11.03;1.51;2.2;21.5;85;2.46;2.17;.52;2.01;1.9;1.71;2.87;407 +2;11.82;1.47;1.99;20.8;86;1.98;1.6;.3;1.53;1.95;.95;3.33;495 +2;12.42;1.61;2.19;22.5;108;2;2.09;.34;1.61;2.06;1.06;2.96;345 +2;12.77;3.43;1.98;16;80;1.63;1.25;.43;.83;3.4;.7;2.12;372 +2;12;3.43;2;19;87;2;1.64;.37;1.87;1.28;.93;3.05;564 +2;11.45;2.4;2.42;20;96;2.9;2.79;.32;1.83;3.25;.8;3.39;625 +2;11.56;2.05;3.23;28.5;119;3.18;5.08;.47;1.87;6;.93;3.69;465 +2;12.42;4.43;2.73;26.5;102;2.2;2.13;.43;1.71;2.08;.92;3.12;365 +2;13.05;5.8;2.13;21.5;86;2.62;2.65;.3;2.01;2.6;.73;3.1;380 +2;11.87;4.31;2.39;21;82;2.86;3.03;.21;2.91;2.8;.75;3.64;380 +2;12.07;2.16;2.17;21;85;2.6;2.65;.37;1.35;2.76;.86;3.28;378 +2;12.43;1.53;2.29;21.5;86;2.74;3.15;.39;1.77;3.94;.69;2.84;352 +2;11.79;2.13;2.78;28.5;92;2.13;2.24;.58;1.76;3;.97;2.44;466 +2;12.37;1.63;2.3;24.5;88;2.22;2.45;.4;1.9;2.12;.89;2.78;342 +2;12.04;4.3;2.38;22;80;2.1;1.75;.42;1.35;2.6;.79;2.57;580 +3;12.86;1.35;2.32;18;122;1.51;1.25;.21;.94;4.1;.76;1.29;630 +3;12.88;2.99;2.4;20;104;1.3;1.22;.24;.83;5.4;.74;1.42;530 +3;12.81;2.31;2.4;24;98;1.15;1.09;.27;.83;5.7;.66;1.36;560 +3;12.7;3.55;2.36;21.5;106;1.7;1.2;.17;.84;5;.78;1.29;600 +3;12.51;1.24;2.25;17.5;85;2;.58;.6;1.25;5.45;.75;1.51;650 +3;12.6;2.46;2.2;18.5;94;1.62;.66;.63;.94;7.1;.73;1.58;695 +3;12.25;4.72;2.54;21;89;1.38;.47;.53;.8;3.85;.75;1.27;720 +3;12.53;5.51;2.64;25;96;1.79;.6;.63;1.1;5;.82;1.69;515 +3;13.49;3.59;2.19;19.5;88;1.62;.48;.58;.88;5.7;.81;1.82;580 +3;12.84;2.96;2.61;24;101;2.32;.6;.53;.81;4.92;.89;2.15;590 +3;12.93;2.81;2.7;21;96;1.54;.5;.53;.75;4.6;.77;2.31;600 +3;13.36;2.56;2.35;20;89;1.4;.5;.37;.64;5.6;.7;2.47;780 +3;13.52;3.17;2.72;23.5;97;1.55;.52;.5;.55;4.35;.89;2.06;520 +3;13.62;4.95;2.35;20;92;2;.8;.47;1.02;4.4;.91;2.05;550 +3;12.25;3.88;2.2;18.5;112;1.38;.78;.29;1.14;8.21;.65;2;855 +3;13.16;3.57;2.15;21;102;1.5;.55;.43;1.3;4;.6;1.68;830 +3;13.88;5.04;2.23;20;80;.98;.34;.4;.68;4.9;.58;1.33;415 +3;12.87;4.61;2.48;21.5;86;1.7;.65;.47;.86;7.65;.54;1.86;625 +3;13.32;3.24;2.38;21.5;92;1.93;.76;.45;1.25;8.42;.55;1.62;650 +3;13.08;3.9;2.36;21.5;113;1.41;1.39;.34;1.14;9.40;.57;1.33;550 +3;13.5;3.12;2.62;24;123;1.4;1.57;.22;1.25;8.60;.59;1.3;500 +3;12.79;2.67;2.48;22;112;1.48;1.36;.24;1.26;10.8;.48;1.47;480 +3;13.11;1.9;2.75;25.5;116;2.2;1.28;.26;1.56;7.1;.61;1.33;425 +3;13.23;3.3;2.28;18.5;98;1.8;.83;.61;1.87;10.52;.56;1.51;675 +3;12.58;1.29;2.1;20;103;1.48;.58;.53;1.4;7.6;.58;1.55;640 +3;13.17;5.19;2.32;22;93;1.74;.63;.61;1.55;7.9;.6;1.48;725 +3;13.84;4.12;2.38;19.5;89;1.8;.83;.48;1.56;9.01;.57;1.64;480 +3;12.45;3.03;2.64;27;97;1.9;.58;.63;1.14;7.5;.67;1.73;880 +3;14.34;1.68;2.7;25;98;2.8;1.31;.53;2.7;13;.57;1.96;660 +3;13.48;1.67;2.64;22.5;89;2.6;1.1;.52;2.29;11.75;.57;1.78;620 +3;12.36;3.83;2.38;21;88;2.3;.92;.5;1.04;7.65;.56;1.58;520 +3;13.69;3.26;2.54;20;107;1.83;.56;.5;.8;5.88;.96;1.82;680 +3;12.85;3.27;2.58;22;106;1.65;.6;.6;.96;5.58;.87;2.11;570 +3;12.96;3.45;2.35;18.5;106;1.39;.7;.4;.94;5.28;.68;1.75;675 +3;13.78;2.76;2.3;22;90;1.35;.68;.41;1.03;9.58;.7;1.68;615 +3;13.73;4.36;2.26;22.5;88;1.28;.47;.52;1.15;6.62;.78;1.75;520 +3;13.45;3.7;2.6;23;111;1.7;.92;.43;1.46;10.68;.85;1.56;695 +3;12.82;3.37;2.3;19.5;88;1.48;.66;.4;.97;10.26;.72;1.75;685 +3;13.58;2.58;2.69;24.5;105;1.55;.84;.39;1.54;8.66;.74;1.8;750 +3;13.4;4.6;2.86;25;112;1.98;.96;.27;1.11;8.5;.67;1.92;630 +3;12.2;3.03;2.32;19;96;1.25;.49;.4;.73;5.5;.66;1.83;510 +3;12.77;2.39;2.28;19.5;86;1.39;.51;.48;.64;9.899999;.57;1.63;470 +3;14.16;2.51;2.48;20;91;1.68;.7;.44;1.24;9.7;.62;1.71;660 +3;13.71;5.65;2.45;20.5;95;1.68;.61;.52;1.06;7.7;.64;1.74;740 +3;13.4;3.91;2.48;23;102;1.8;.75;.43;1.41;7.3;.7;1.56;750 +3;13.27;4.28;2.26;20;120;1.59;.69;.43;1.35;10.2;.59;1.56;835 +3;13.17;2.59;2.37;20;120;1.65;.68;.53;1.46;9.3;.6;1.62;840 +3;14.13;4.1;2.74;24.5;96;2.05;.76;.56;1.35;9.2;.61;1.6;560 \ No newline at end of file diff --git a/Naive Bayes Classifier/NaiveBayes.playground/Sources/NaiveBayes.swift b/Naive Bayes Classifier/NaiveBayes.playground/Sources/NaiveBayes.swift new file mode 100644 index 000000000..6e6d7b4c0 --- /dev/null +++ b/Naive Bayes Classifier/NaiveBayes.playground/Sources/NaiveBayes.swift @@ -0,0 +1,196 @@ +// +// NaiveBayes.swift +// NaiveBayes +// +// Created by Philipp Gabriel on 14.04.17. +// Copyright © 2017 ph1ps. All rights reserved. +// + +import Foundation + +extension String: Error {} + +extension Array where Element == Double { + + func mean() -> Double { + return self.reduce(0, +) / Double(count) + } + + func standardDeviation() -> Double { + let calculatedMean = mean() + + let sum = self.reduce(0.0) { (previous, next) in + return previous + pow(next - calculatedMean, 2) + } + + return sqrt(sum / Double(count - 1)) + } +} + +extension Array where Element == Int { + + func uniques() -> Set { + return Set(self) + } + +} + +public enum NBType { + + case gaussian + case multinomial + //case bernoulli --> TODO + + func calcLikelihood(variables: [Any], input: Any) -> Double? { + + if case .gaussian = self { + + guard let input = input as? Double else { + return nil + } + + guard let mean = variables[0] as? Double else { + return nil + } + + guard let stDev = variables[1] as? Double else { + return nil + } + + let eulerPart = pow(M_E, -1 * pow(input - mean, 2) / (2 * pow(stDev, 2))) + let distribution = eulerPart / sqrt(2 * .pi) / stDev + + return distribution + + } else if case .multinomial = self { + + guard let variables = variables as? [(category: Int, probability: Double)] else { + return nil + } + + guard let input = input as? Int else { + return nil + } + + return variables.first { variable in + return variable.category == input + }?.probability + + } + + return nil + } + + func train(values: [Any]) -> [Any]? { + + if case .gaussian = self { + + guard let values = values as? [Double] else { + return nil + } + + return [values.mean(), values.standardDeviation()] + + } else if case .multinomial = self { + + guard let values = values as? [Int] else { + return nil + } + + let count = values.count + let categoryProba = values.uniques().map { value -> (Int, Double) in + return (value, Double(values.filter { $0 == value }.count) / Double(count)) + } + return categoryProba + } + + return nil + } +} + +public class NaiveBayes { + + var variables: [Int: [(feature: Int, variables: [Any])]] + var type: NBType + + var data: [[T]] + var classes: [Int] + + public init(type: NBType, data: [[T]], classes: [Int]) throws { + self.type = type + self.data = data + self.classes = classes + self.variables = [Int: [(Int, [Any])]]() + + if case .gaussian = type, T.self != Double.self { + throw "When using Gaussian NB you have to have continuous features (Double)" + } else if case .multinomial = type, T.self != Int.self { + throw "When using Multinomial NB you have to have categorical features (Int)" + } + } + + public func train() throws -> Self { + + for `class` in classes.uniques() { + variables[`class`] = [(Int, [Any])]() + + let classDependent = data.enumerated().filter { (offset, _) in + return classes[offset] == `class` + } + + for feature in 0.. Int { + let likelihoods = classifyProba(with: input).max { (first, second) -> Bool in + return first.1 < second.1 + } + + guard let `class` = likelihoods?.0 else { + return -1 + } + + return `class` + } + + public func classifyProba(with input: [T]) -> [(Int, Double)] { + + var probaClass = [Int: Double]() + let amount = classes.count + + classes.forEach { `class` in + let individual = classes.filter { $0 == `class` }.count + probaClass[`class`] = Double(individual) / Double(amount) + } + + let classesAndFeatures = variables.map { (`class`, value) -> (Int, [Double]) in + let distribution = value.map { (feature, variables) -> Double in + return type.calcLikelihood(variables: variables, input: input[feature]) ?? 0.0 + } + return (`class`, distribution) + } + + let likelihoods = classesAndFeatures.map { (`class`, distribution) in + return (`class`, distribution.reduce(1, *) * (probaClass[`class`] ?? 0.0)) + } + + let sum = likelihoods.map { $0.1 }.reduce(0, +) + let normalized = likelihoods.map { (`class`, likelihood) in + return (`class`, likelihood / sum) + } + + return normalized + } +} diff --git a/Naive Bayes Classifier/NaiveBayes.playground/contents.xcplayground b/Naive Bayes Classifier/NaiveBayes.playground/contents.xcplayground new file mode 100644 index 000000000..89da2d470 --- /dev/null +++ b/Naive Bayes Classifier/NaiveBayes.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Naive Bayes Classifier/NaiveBayes.playground/playground.xcworkspace/contents.xcworkspacedata b/Naive Bayes Classifier/NaiveBayes.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Naive Bayes Classifier/NaiveBayes.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Naive Bayes Classifier/NaiveBayes.playground/timeline.xctimeline b/Naive Bayes Classifier/NaiveBayes.playground/timeline.xctimeline new file mode 100644 index 000000000..7bc414f58 --- /dev/null +++ b/Naive Bayes Classifier/NaiveBayes.playground/timeline.xctimeline @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + diff --git a/Naive Bayes Classifier/NaiveBayes.swift b/Naive Bayes Classifier/NaiveBayes.swift new file mode 100644 index 000000000..46a0bb4f5 --- /dev/null +++ b/Naive Bayes Classifier/NaiveBayes.swift @@ -0,0 +1,196 @@ +// +// NaiveBayes.swift +// NaiveBayes +// +// Created by Philipp Gabriel on 14.04.17. +// Copyright © 2017 ph1ps. All rights reserved. +// + +import Foundation + +extension String: Error {} + +extension Array where Element == Double { + + func mean() -> Double { + return self.reduce(0, +) / Double(count) + } + + func standardDeviation() -> Double { + let calculatedMean = mean() + + let sum = self.reduce(0.0) { (previous, next) in + return previous + pow(next - calculatedMean, 2) + } + + return sqrt(sum / Double(count - 1)) + } +} + +extension Array where Element == Int { + + func uniques() -> Set { + return Set(self) + } + +} + +enum NBType { + + case gaussian + case multinomial + //case bernoulli --> TODO + + func calcLikelihood(variables: [Any], input: Any) -> Double? { + + if case .gaussian = self { + + guard let input = input as? Double else { + return nil + } + + guard let mean = variables[0] as? Double else { + return nil + } + + guard let stDev = variables[1] as? Double else { + return nil + } + + let eulerPart = pow(M_E, -1 * pow(input - mean, 2) / (2 * pow(stDev, 2))) + let distribution = eulerPart / sqrt(2 * .pi) / stDev + + return distribution + + } else if case .multinomial = self { + + guard let variables = variables as? [(category: Int, probability: Double)] else { + return nil + } + + guard let input = input as? Int else { + return nil + } + + return variables.first { variable in + return variable.category == input + }?.probability + + } + + return nil + } + + func train(values: [Any]) -> [Any]? { + + if case .gaussian = self { + + guard let values = values as? [Double] else { + return nil + } + + return [values.mean(), values.standardDeviation()] + + } else if case .multinomial = self { + + guard let values = values as? [Int] else { + return nil + } + + let count = values.count + let categoryProba = values.uniques().map { value -> (Int, Double) in + return (value, Double(values.filter { $0 == value }.count) / Double(count)) + } + return categoryProba + } + + return nil + } +} + +class NaiveBayes { + + var variables: [Int: [(feature: Int, variables: [Any])]] + var type: NBType + + var data: [[T]] + var classes: [Int] + + init(type: NBType, data: [[T]], classes: [Int]) throws { + self.type = type + self.data = data + self.classes = classes + self.variables = [Int: [(Int, [Any])]]() + + if case .gaussian = type, T.self != Double.self { + throw "When using Gaussian NB you have to have continuous features (Double)" + } else if case .multinomial = type, T.self != Int.self { + throw "When using Multinomial NB you have to have categorical features (Int)" + } + } + + func train() throws -> Self { + + for `class` in classes.uniques() { + variables[`class`] = [(Int, [Any])]() + + let classDependent = data.enumerated().filter { (offset, _) in + return classes[offset] == `class` + } + + for feature in 0.. Int { + let likelihoods = classifyProba(with: input).max { (first, second) -> Bool in + return first.1 < second.1 + } + + guard let `class` = likelihoods?.0 else { + return -1 + } + + return `class` + } + + func classifyProba(with input: [T]) -> [(Int, Double)] { + + var probaClass = [Int: Double]() + let amount = classes.count + + classes.forEach { `class` in + let individual = classes.filter { $0 == `class` }.count + probaClass[`class`] = Double(individual) / Double(amount) + } + + let classesAndFeatures = variables.map { (`class`, value) -> (Int, [Double]) in + let distribution = value.map { (feature, variables) -> Double in + return type.calcLikelihood(variables: variables, input: input[feature]) ?? 0.0 + } + return (`class`, distribution) + } + + let likelihoods = classesAndFeatures.map { (`class`, distribution) in + return (`class`, distribution.reduce(1, *) * (probaClass[`class`] ?? 0.0)) + } + + let sum = likelihoods.map { $0.1 }.reduce(0, +) + let normalized = likelihoods.map { (`class`, likelihood) in + return (`class`, likelihood / sum) + } + + return normalized + } +} diff --git a/Naive Bayes Classifier/README.md b/Naive Bayes Classifier/README.md new file mode 100644 index 000000000..9c0f0082a --- /dev/null +++ b/Naive Bayes Classifier/README.md @@ -0,0 +1,99 @@ +# Naive Bayes Classifier + +> ***Disclaimer:*** Do not get scared of complicated formulas or terms, I will describe them right after I use them. Also the math skills you need to understand this are very basic. + +The goal of a classifier is to predict the class of a given data entry based on previously fed data and its features. + +Now what is a class or a feature? The best I can do is to describe it with a table. +This is a dataset that uses height, weight and foot size of a person to illustrate the relationship between those values and the sex. + +| Sex | height (feet) | weight(lbs) | foot size (inches) | +| ------------- |:-------------:|:-----:|:---:| +| male | 6 | 180 | 12 | +| male | 5.92 | 190 | 11 | +| male | 5.58 | 170 | 12 | +| male | 5.92 | 165 | 10 | +| female | 5 | 100 | 6 | +| female | 5.5 | 150 | 8 | +| female | 5.42 | 130 | 7 | +| female | 5.75 | 150 | 9 | + +The ***classes*** of this table is the data in the sex column (male/female). You "classify" the rest of the data and bind them to a sex. + +The ***features*** of this table are the labels of the other columns (height, weight, foot size) and the numbers right under the labels. + +Now that I've told you what a classifier is I will tell you what exactly a ***Naive Bayes classifier*** is. There are a lot of other classifiers out there but what's so special about this specific is that it only needs a very small dataset to get good results. The others like Random Forests normally need a very large dataset. + +Why isn't this algorithm used more you might ask (or not). Because it is normally ***outperformed*** in accuracy by ***Random Forests*** or ***Boosted Trees***. + +## Theory + +The Naive Bayes classifier utilizes the ***Bayes Theorem*** (as its name suggests) which looks like this. + +![](images/bayes.gif) + +***P*** always means the probability of something. + +***A*** is the class, ***B*** is the data depending on a feature and the ***pipe*** symbol means given. + +P(A | B) therefore is: probability of the class given the data (which is dependent on the feature). + +This is all you have to know about the Bayes Theorem. The important thing for us is now how to calculate all those variables, plug them into this formula and you are ready to classify data. + +### **P(A)** +This is the probability of the class. To get back to the example I gave before: Let's say we want to classify this data entry: + +| height (feet) | weight(lbs) | foot size (inches) | +|:-------------:|:-----:|:---:| +| 6 | 130 | 8 | + +What Naive Bayes classifier now does: it checks the probability for every class possible which is in our case either male or female. Look back at the original table and count the male and the female entries. Then divide them by the overall count of data entries. + +P(male) = 4 / 8 = 0.5 + +P(female) = 4 / 8 = 0.5 + +This should be a very easy task to do. Basically just the probability of all classes. + +### **P(B)** +This variable is not needed in a Naive Bayes classifier. It is the probability of the data. It does not change, therefore it is a constant. And what can you do with a constant? Exactly! Discard it. This saves time and code. + +### **P(B | A)** +This is the probability of the data given the class. To calculate this I have to introduce you to the subtypes of NB. You have to decide which you use depending on your data which you want to classify. + +### **Gaussian Naive Bayes** +If you have a dataset like the one I showed you before (continuous features -> `Double`s) you have to use this subtype. There are 3 formulas you need for Gaussian NB to calculate P(B | A). + +![mean](images/mean.gif) + +![standard deviation](images/standard_deviation.gif) + +![normal distribution](images/normal_distribution.gif) + +and **P(x | y) = P(B | A)** + +Again, very complicated looking formulas but they are very easy. The first formula with µ is just the mean of the data (adding all data points and dividing them by the count). The second with σ is the standard deviation. You might have heard of it somewhen in school. It is just the sum of all values minus the mean, squared and that divided by the count of the data minus 1 and a sqaure root around it. The third equation is the Gaussian or normal distribution if you want to read more about it I suggest reading [this](https://en.wikipedia.org/wiki/Normal_distribution). + +Why the Gaussian distribution? Because we assume that the continuous values associated with each class are distributed according to the Gaussian distribution. Simple as that. + +### **Multinomial Naive Bayes** + +What do we do if we have this for examples: + +![tennis or golf](images/tennis_dataset.png) + +We can't just calculate the mean of sunny, overcast and rainy. This is why we need the categorical model which is the multinomial NB. This is the last formula, I promise! + +![multinomial](images/multinomial.gif) + +Now this is the number of times feature **i** appears in a sample **N** of class **y** in the data set divided by the count of the sample just depending on the class **y**. That θ is also just a fancy way of writing P(B | A). + +You might have noticed that there is still the α in this formula. This solves a problem called "zero-frequency-problem". Because what happens if there is no sample with feature **i** and class **y**? The whole equation would result in 0 (because 0 / something is always 0). This is a huge problem but there is a simple solution to this. Just add 1 to any count of the sample (α = 1). + +## Those formulas in action + +Enough talking! This is the code. If you want a deeper explanation of how the code works just look at the Playground I provided. + +![code example](images/code_example.png) + +*Written for Swift Algorithm Club by Philipp Gabriel* \ No newline at end of file diff --git a/Naive Bayes Classifier/images/bayes.gif b/Naive Bayes Classifier/images/bayes.gif new file mode 100644 index 000000000..363ff3425 Binary files /dev/null and b/Naive Bayes Classifier/images/bayes.gif differ diff --git a/Naive Bayes Classifier/images/code_example.png b/Naive Bayes Classifier/images/code_example.png new file mode 100644 index 000000000..a147ecdd6 Binary files /dev/null and b/Naive Bayes Classifier/images/code_example.png differ diff --git a/Naive Bayes Classifier/images/mean.gif b/Naive Bayes Classifier/images/mean.gif new file mode 100644 index 000000000..7518c9995 Binary files /dev/null and b/Naive Bayes Classifier/images/mean.gif differ diff --git a/Naive Bayes Classifier/images/multinomial.gif b/Naive Bayes Classifier/images/multinomial.gif new file mode 100644 index 000000000..dbad012fd Binary files /dev/null and b/Naive Bayes Classifier/images/multinomial.gif differ diff --git a/Naive Bayes Classifier/images/normal_distribution.gif b/Naive Bayes Classifier/images/normal_distribution.gif new file mode 100644 index 000000000..b2f35e8eb Binary files /dev/null and b/Naive Bayes Classifier/images/normal_distribution.gif differ diff --git a/Naive Bayes Classifier/images/standard_deviation.gif b/Naive Bayes Classifier/images/standard_deviation.gif new file mode 100644 index 000000000..b917d9922 Binary files /dev/null and b/Naive Bayes Classifier/images/standard_deviation.gif differ diff --git a/Naive Bayes Classifier/images/tennis_dataset.png b/Naive Bayes Classifier/images/tennis_dataset.png new file mode 100644 index 000000000..ce9a1a699 Binary files /dev/null and b/Naive Bayes Classifier/images/tennis_dataset.png differ diff --git a/Octree/Octree.playground/Contents.swift b/Octree/Octree.playground/Contents.swift new file mode 100644 index 000000000..cdcd2df0f --- /dev/null +++ b/Octree/Octree.playground/Contents.swift @@ -0,0 +1,33 @@ +//: Playground - noun: a place where people can play + +import UIKit +import simd + +let boxMin = vector_double3(0, 2, 6) +let boxMax = vector_double3(5, 10, 9) +let box = Box(boxMin: boxMin, boxMax: boxMax) +var octree = Octree(boundingBox: box, minimumCellSize: 5.0) +var five = octree.add(5, at: vector_double3(3,4,8)) +octree.add(8, at: vector_double3(3,4,8.2)) +octree.add(10, at: vector_double3(3,4,8.2)) +octree.add(7, at: vector_double3(2,5,8)) +octree.add(2, at: vector_double3(1,6,7)) + +var cont = octree.elements(at: vector_double3(3,4,8.2)) +octree.remove(8) +octree.elements(at: vector_double3(3,4,8)) + +let boxMin2 = vector_double3(1,3,7) +let boxMax2 = vector_double3(4,9,8) +let box2 = Box(boxMin: boxMin2, boxMax: boxMax2) +box.isContained(in: box2) +box.intersects(box2) + +let boxMin3 = vector_double3(3,8,8) +let boxMax3 = vector_double3(10,12,20) +let box3 = Box(boxMin: boxMin3, boxMax: boxMax3) +box3.intersects(box3) + +octree.elements(in: box) +octree.elements(in: box2) +print(octree) diff --git a/Octree/Octree.playground/Sources/Octree.swift b/Octree/Octree.playground/Sources/Octree.swift new file mode 100644 index 000000000..72863fb7c --- /dev/null +++ b/Octree/Octree.playground/Sources/Octree.swift @@ -0,0 +1,372 @@ +import Foundation +import simd + +public struct Box: CustomStringConvertible { + public var boxMin: vector_double3 + public var boxMax: vector_double3 + + public init(boxMin: vector_double3, boxMax: vector_double3) { + self.boxMin = boxMin + self.boxMax = boxMax + } + + public var boxSize: vector_double3 { + return boxMax - boxMin + } + + var halfBoxSize: vector_double3 { + return boxSize/2 + } + + var frontLeftTop: Box { + let boxMin = self.boxMin + vector_double3(0, halfBoxSize.y, halfBoxSize.z) + let boxMax = self.boxMax - vector_double3(halfBoxSize.x, 0, 0) + return Box(boxMin: boxMin, boxMax: boxMax) + } + var frontLeftBottom: Box { + let boxMin = self.boxMin + vector_double3(0, 0, halfBoxSize.z) + let boxMax = self.boxMax - vector_double3(halfBoxSize.x, halfBoxSize.y, 0) + return Box(boxMin: boxMin, boxMax: boxMax) + } + var frontRightTop: Box { + let boxMin = self.boxMin + vector_double3(halfBoxSize.x, halfBoxSize.y, halfBoxSize.z) + let boxMax = self.boxMax - vector_double3(0, 0, 0) + return Box(boxMin: boxMin, boxMax: boxMax) + } + var frontRightBottom: Box { + let boxMin = self.boxMin + vector_double3(halfBoxSize.x, 0, halfBoxSize.z) + let boxMax = self.boxMax - vector_double3(0, halfBoxSize.y, 0) + return Box(boxMin: boxMin, boxMax: boxMax) + } + var backLeftTop: Box { + let boxMin = self.boxMin + vector_double3(0, halfBoxSize.y, 0) + let boxMax = self.boxMax - vector_double3(halfBoxSize.x, 0, halfBoxSize.z) + return Box(boxMin: boxMin, boxMax: boxMax) + } + var backLeftBottom: Box { + let boxMin = self.boxMin + vector_double3(0, 0, 0) + let boxMax = self.boxMax - vector_double3(halfBoxSize.x, halfBoxSize.y, halfBoxSize.z) + return Box(boxMin: boxMin, boxMax: boxMax) + } + var backRightTop: Box { + let boxMin = self.boxMin + vector_double3(halfBoxSize.x, halfBoxSize.y, 0) + let boxMax = self.boxMax - vector_double3(0, 0, halfBoxSize.z) + return Box(boxMin: boxMin, boxMax: boxMax) + } + var backRightBottom: Box { + let boxMin = self.boxMin + vector_double3(halfBoxSize.x, 0, 0) + let boxMax = self.boxMax - vector_double3(0, halfBoxSize.y, halfBoxSize.z) + return Box(boxMin: boxMin, boxMax: boxMax) + } + + public func contains(_ point: vector_double3) -> Bool { + return (boxMin.x <= point.x && point.x <= boxMax.x) && (boxMin.y <= point.y && point.y <= boxMax.y) && (boxMin.z <= point.z && point.z <= boxMax.z) + } + + public func contains(_ box: Box) -> Bool { + return + self.boxMin.x <= box.boxMin.x && + self.boxMin.y <= box.boxMin.y && + self.boxMin.z <= box.boxMin.z && + self.boxMax.x >= box.boxMax.x && + self.boxMax.y >= box.boxMax.y && + self.boxMax.z >= box.boxMax.z + } + + public func isContained(in box: Box) -> Bool { + return + self.boxMin.x >= box.boxMin.x && + self.boxMin.y >= box.boxMin.y && + self.boxMin.z >= box.boxMin.z && + self.boxMax.x <= box.boxMax.x && + self.boxMax.y <= box.boxMax.y && + self.boxMax.z <= box.boxMax.z + } + + /* This intersect function does not handle all possibilities such as two beams + of different diameter crossing each other half way. But it does cover all cases + needed for an octree as the bounding box has to contain the given intersect box */ + public func intersects(_ box: Box) -> Bool { + let corners = [ + vector_double3(boxMin.x, boxMax.y, boxMax.z), //frontLeftTop + vector_double3(boxMin.x, boxMin.y, boxMax.z), //frontLeftBottom + vector_double3(boxMax.x, boxMax.y, boxMax.z), //frontRightTop + vector_double3(boxMax.x, boxMin.y, boxMax.z), //frontRightBottom + vector_double3(boxMin.x, boxMax.y, boxMin.z), //backLeftTop + vector_double3(boxMin.x, boxMin.y, boxMin.z), //backLeftBottom + vector_double3(boxMax.x, boxMax.y, boxMin.z), //backRightTop + vector_double3(boxMax.x, boxMin.y, boxMin.z) //backRightBottom + ] + for corner in corners { + if box.contains(corner) { + return true + } + } + return false + } + + public var description: String { + return "Box from:\(boxMin) to:\(boxMax)" + } +} + +public class OctreeNode: CustomStringConvertible { + let box: Box + var point: vector_double3! + var elements: [T]! + var type: NodeType = .leaf + + enum NodeType { + case leaf + case `internal`(children: Children) + } + + public var description: String { + switch type { + case .leaf: + return "leaf node with \(box) elements: \(elements)" + case .internal: + return "internal node with \(box)" + } + } + + var recursiveDescription: String { + return recursiveDescription(withTabCount: 0) + } + + private func recursiveDescription(withTabCount count: Int) -> String { + let indent = String(repeating: "\t", count: count) + var result = "\(indent)" + description + "\n" + switch type { + case .internal(let children): + for child in children { + result += child.recursiveDescription(withTabCount: count + 1) + } + default: + break + } + return result + } + + struct Children: Sequence { + let frontLeftTop: OctreeNode + let frontLeftBottom: OctreeNode + let frontRightTop: OctreeNode + let frontRightBottom: OctreeNode + let backLeftTop: OctreeNode + let backLeftBottom: OctreeNode + let backRightTop: OctreeNode + let backRightBottom: OctreeNode + + init(parentNode: OctreeNode) { + frontLeftTop = OctreeNode(box: parentNode.box.frontLeftTop) + frontLeftBottom = OctreeNode(box: parentNode.box.frontLeftBottom) + frontRightTop = OctreeNode(box: parentNode.box.frontRightTop) + frontRightBottom = OctreeNode(box: parentNode.box.frontRightBottom) + backLeftTop = OctreeNode(box: parentNode.box.backLeftTop) + backLeftBottom = OctreeNode(box: parentNode.box.backLeftBottom) + backRightTop = OctreeNode(box: parentNode.box.backRightTop) + backRightBottom = OctreeNode(box: parentNode.box.backRightBottom) + } + + struct ChildrenIterator: IteratorProtocol { + var index = 0 + let children: Children + + init(children: Children) { + self.children = children + } + + mutating func next() -> OctreeNode? { + defer { index += 1 } + switch index { + case 0: return children.frontLeftTop + case 1: return children.frontLeftBottom + case 2: return children.frontRightTop + case 3: return children.frontRightBottom + case 4: return children.backLeftTop + case 5: return children.backLeftBottom + case 6: return children.backRightTop + case 7: return children.backRightBottom + default: return nil + } + } + } + + func makeIterator() -> ChildrenIterator { + return ChildrenIterator(children: self) + } + } + + init(box: Box) { + self.box = box + } + + @discardableResult + func add(_ element: T, at point: vector_double3) -> OctreeNode { + return tryAdd(element, at: point)! + } + + private func tryAdd(_ element: T, at point: vector_double3) -> OctreeNode? { + if !box.contains(point) { + return nil + } + + switch type { + case .internal(let children): + // pass the point to one of the children + for child in children { + if let child = child.tryAdd(element, at: point) { + return child + } + } + + fatalError("box.contains evaluted to true, but none of the children added the point") + case .leaf: + if self.point != nil { + // leaf already has an asigned point + if self.point == point { + self.elements.append(element) + return self + } else { + return subdivide(adding: element, at: point) + } + } else { + self.elements = [element] + self.point = point + return self + } + } + } + + func add(_ elements: [T], at point: vector_double3) { + for element in elements { + self.add(element, at: point) + } + } + + @discardableResult + func remove(_ element: T) -> Bool { + switch type { + case .leaf: + if let elements = self.elements { + // leaf contains one ore more elements + if let index = elements.index(of: element) { + // leaf contains the element we want to remove + self.elements.remove(at: index) + // if elements is now empty remove it + if self.elements.isEmpty { + self.elements = nil + } + return true + } + } + return false + case .internal(let children): + for child in children { + if child.remove(element) { + return true + } + } + return false + } + } + + func elements(at point: vector_double3) -> [T]? { + switch type { + case .leaf: + if self.point == point { + return self.elements + } + case .internal(let children): + for child in children { + if child.box.contains(point) { + return child.elements(at: point) + } + } + } + // tree does not contain given point + return nil + } + + func elements(in box: Box) -> [T]? { + var values: [T] = [] + switch type { + case .leaf: + // check if leaf has an assigned point + if let point = self.point { + // check if point is inside given box + if box.contains(point) { + values += elements ?? [] + } + } + case .internal(let children): + for child in children { + if child.box.isContained(in: box) { + // child is contained in box + // add all children of child + values += child.elements(in: child.box) ?? [] + } else if child.box.contains(box) || child.box.intersects(box) { + // child contains at least part of box + values += child.elements(in: box) ?? [] + } + // child does not contain any part of given box + } + } + if values.isEmpty { return nil } + return values + } + + private func subdivide(adding element: T, at point: vector_double3) -> OctreeNode? { + precondition(self.elements != nil, "Subdividing while leaf does not contain a element") + precondition(self.point != nil, "Subdividing while leaf does not contain a point") + switch type { + case .leaf: + type = .internal(children: Children(parentNode: self)) + // add element previously contained in leaf to children + self.add(self.elements, at: self.point) + self.elements = nil + self.point = nil + // add new element to children + return self.add(element, at: point) + case .internal: + preconditionFailure("Calling subdivide on an internal node") + } + } +} + +public class Octree: CustomStringConvertible { + var root: OctreeNode + + public var description: String { + return "Octree\n" + root.recursiveDescription + } + + public init(boundingBox: Box, minimumCellSize: Double) { + root = OctreeNode(box: boundingBox) + } + + @discardableResult + public func add(_ element: T, at point: vector_double3) -> OctreeNode { + return root.add(element, at: point) + } + + @discardableResult + public func remove(_ element: T, using node: OctreeNode) -> Bool { + return node.remove(element) + } + + @discardableResult + public func remove(_ element: T) -> Bool { + return root.remove(element) + } + + public func elements(at point: vector_double3) -> [T]? { + return root.elements(at: point) + } + + public func elements(in box: Box) -> [T]? { + precondition(root.box.contains(box), "box is outside of octree bounds") + return root.elements(in: box) + } +} diff --git a/Octree/Octree.playground/contents.xcplayground b/Octree/Octree.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Octree/Octree.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Octree/Octree.playground/timeline.xctimeline b/Octree/Octree.playground/timeline.xctimeline new file mode 100644 index 000000000..783cc6694 --- /dev/null +++ b/Octree/Octree.playground/timeline.xctimeline @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/Octree/README.md b/Octree/README.md new file mode 100644 index 000000000..12b8cff39 --- /dev/null +++ b/Octree/README.md @@ -0,0 +1,27 @@ +# OcTree + +An octree is a [tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Tree) in which each internal (not leaf) node has eight children. Often used for collision detection in games for example. + +### Problem + +Consider the following problem: your need to store a number of objects in 3D space (each at a certain location with `X`, `Y` and `Z` coordinates) and then you need to answer which objects lie in a certain 3D region. A naive solution would be to store the points inside an array and then iterate over the points and check each one individually. This solution runs in O(n) though. + +### A Better Approach + +Octrees are most commonly used to partition a three-dimensional space by recursively subdividing it into 8 regions. Let's see how we can use an Octree to store some values. + +Each node in the tree represents a box-like region. Leaf nodes store a single point in that region with an array of objects assigned to that point. + +Once an object within the same region (but at a different point) is added the leaf node turns into an internal node and 8 child nodes (leaves) are added to it. All points previously contained in the node are passed to its corresponding children and stored. Thus only leaves contain actual points and values. + +To find the points that lie in a given region we can now traverse the tree from top to bottom and collect the suitable points from nodes. + +Both adding a point and searching can still take up to O(n) in the worst case, since the tree isn't balanced in any way. However, on average it runs significantly faster (something comparable to O(log n)). + +### See also + +More info on [Wiki](https://en.wikipedia.org/wiki/Octree) +Apple's implementation of [GKOctree](https://developer.apple.com/documentation/gameplaykit/gkoctree) + +*Written for Swift Algorithm Club by Jaap Wijnen* +*Heavily inspired by Timur Galimov's Quadtree implementation and Apple's GKOctree implementation diff --git a/Ordered Array/OrderedArray.playground/Contents.swift b/Ordered Array/OrderedArray.playground/Contents.swift index 4fe199f28..e48af22e2 100644 --- a/Ordered Array/OrderedArray.playground/Contents.swift +++ b/Ordered Array/OrderedArray.playground/Contents.swift @@ -1,5 +1,7 @@ //: Playground - noun: a place where people can play + + public struct OrderedArray { fileprivate var array = [T]() diff --git a/Ordered Array/OrderedArray.playground/timeline.xctimeline b/Ordered Array/OrderedArray.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Ordered Array/OrderedArray.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Ordered Set/AppleOrderedSet.playground/Contents.swift b/Ordered Set/AppleOrderedSet.playground/Contents.swift deleted file mode 100644 index 4388c664b..000000000 --- a/Ordered Set/AppleOrderedSet.playground/Contents.swift +++ /dev/null @@ -1,13 +0,0 @@ -let s = AppleOrderedSet() - -s.add(1) -s.add(2) -s.add(-1) -s.add(0) -s.insert(4, at: 3) - -s.set(-1, at: 0) -s.remove(-1) - -print(s.object(at: 1)) - diff --git a/Ordered Set/AppleOrderedSet.playground/Sources/AppleOrderedSet.swift b/Ordered Set/AppleOrderedSet.playground/Sources/AppleOrderedSet.swift deleted file mode 100644 index 3ff19cb8d..000000000 --- a/Ordered Set/AppleOrderedSet.playground/Sources/AppleOrderedSet.swift +++ /dev/null @@ -1,72 +0,0 @@ -public class AppleOrderedSet { - private var objects: [T] = [] - private var indexOfKey: [T: Int] = [:] - - public init() {} - - // O(1) - public func add(_ object: T) { - guard indexOfKey[object] == nil else { - return - } - - objects.append(object) - indexOfKey[object] = objects.count - 1 - } - - // O(n) - public func insert(_ object: T, at index: Int) { - assert(index < objects.count, "Index should be smaller than object count") - assert(index >= 0, "Index should be bigger than 0") - - guard indexOfKey[object] == nil else { - return - } - - objects.insert(object, at: index) - indexOfKey[object] = index - for i in index+1.. T { - assert(index < objects.count, "Index should be smaller than object count") - assert(index >= 0, "Index should be bigger than 0") - - return objects[index] - } - - // O(1) - public func set(_ object: T, at index: Int) { - assert(index < objects.count, "Index should be smaller than object count") - assert(index >= 0, "Index should be bigger than 0") - - guard indexOfKey[object] == nil else { - return - } - - indexOfKey.removeValue(forKey: objects[index]) - indexOfKey[object] = index - objects[index] = object - } - - // O(1) - public func indexOf(_ object: T) -> Int { - return indexOfKey[object] ?? -1 - } - - // O(n) - public func remove(_ object: T) { - guard let index = indexOfKey[object] else { - return - } - - indexOfKey.removeValue(forKey: object) - objects.remove(at: index) - for i in index.. { - private var objects: [T] = [] - private var indexOfKey: [T: Int] = [:] - - public init() {} - - // O(1) - public func add(_ object: T) { - guard indexOfKey[object] == nil else { - return - } - - objects.append(object) - indexOfKey[object] = objects.count - 1 - } - - // O(n) - public func insert(_ object: T, at index: Int) { - assert(index < objects.count, "Index should be smaller than object count") - assert(index >= 0, "Index should be bigger than 0") - - guard indexOfKey[object] == nil else { - return - } - - objects.insert(object, at: index) - indexOfKey[object] = index - for i in index+1.. T { - assert(index < objects.count, "Index should be smaller than object count") - assert(index >= 0, "Index should be bigger than 0") - - return objects[index] - } - - // O(1) - public func set(_ object: T, at index: Int) { - assert(index < objects.count, "Index should be smaller than object count") - assert(index >= 0, "Index should be bigger than 0") - - guard indexOfKey[object] == nil else { - return - } - - indexOfKey.removeValue(forKey: objects[index]) - indexOfKey[object] = index - objects[index] = object - } - - // O(1) - public func indexOf(_ object: T) -> Int { - return indexOfKey[object] ?? -1 - } - - // O(n) - public func remove(_ object: T) { - guard let index = indexOfKey[object] else { - return - } - - indexOfKey.removeValue(forKey: object) - objects.remove(at: index) - for i in index..() + +s.add(1) +s.add(2) +s.add(-1) +s.add(0) +s.insert(4, at: 3) + +print(s.all()) // [1, 2, -1, 4, 0] + +s.set(-1, at: 0) // We already have -1 in index: 2, so we will do nothing here + +print(s.all()) // [1, 2, -1, 4, 0] + +s.remove(-1) + +print(s.all()) // [1, 2, 4, 0] + +print(s.object(at: 1)) // 2 + +print(s.object(at: 2)) // 4 + diff --git a/Ordered Set/OrderedSet.playground/Pages/Example 1.xcplaygroundpage/timeline.xctimeline b/Ordered Set/OrderedSet.playground/Pages/Example 1.xcplaygroundpage/timeline.xctimeline deleted file mode 100644 index 454d1011b..000000000 --- a/Ordered Set/OrderedSet.playground/Pages/Example 1.xcplaygroundpage/timeline.xctimeline +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - diff --git a/Ordered Set/OrderedSet.playground/Pages/Example 3.xcplaygroundpage/timeline.xctimeline b/Ordered Set/OrderedSet.playground/Pages/Example 3.xcplaygroundpage/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Ordered Set/OrderedSet.playground/Pages/Example 3.xcplaygroundpage/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Ordered Set/OrderedSet.playground/Sources/OrderedSet.swift b/Ordered Set/OrderedSet.playground/Sources/OrderedSet.swift index 8490a3320..5508c3637 100644 --- a/Ordered Set/OrderedSet.playground/Sources/OrderedSet.swift +++ b/Ordered Set/OrderedSet.playground/Sources/OrderedSet.swift @@ -1,117 +1,76 @@ -/* - An Ordered Set is a collection where all items in the set follow an ordering, - usually ordered from 'least' to 'most'. The way you value and compare items - can be user-defined. -*/ -public struct OrderedSet { - private var internalSet = [T]() - - public init() { } - - // Returns the number of elements in the OrderedSet. - public var count: Int { - return internalSet.count - } - - // Inserts an item. Performance: O(n) - public mutating func insert(_ item: T) { - if exists(item) { - return // don't add an item if it already exists - } - - // Insert new the item just before the one that is larger. - for i in 0.. item { - internalSet.insert(item, at: i) - return - } +public class OrderedSet { + private var objects: [T] = [] + private var indexOfKey: [T: Int] = [:] + + public init() {} + + // O(1) + public func add(_ object: T) { + guard indexOfKey[object] == nil else { + return } - - // Append to the back if the new item is greater than any other in the set. - internalSet.append(item) + + objects.append(object) + indexOfKey[object] = objects.count - 1 } - - // Removes an item if it exists. Performance: O(n) - public mutating func remove(_ item: T) { - if let index = index(of: item) { - internalSet.remove(at: index) + + // O(n) + public func insert(_ object: T, at index: Int) { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + guard indexOfKey[object] == nil else { + return } - } - - // Returns true if and only if the item exists somewhere in the set. - public func exists(_ item: T) -> Bool { - return index(of: item) != nil - } - - // Returns the index of an item if it exists, or -1 otherwise. - public func index(of item: T) -> Int? { - var leftBound = 0 - var rightBound = count - 1 - - while leftBound <= rightBound { - let mid = leftBound + ((rightBound - leftBound) / 2) - - if internalSet[mid] > item { - rightBound = mid - 1 - } else if internalSet[mid] < item { - leftBound = mid + 1 - } else if internalSet[mid] == item { - return mid - } else { - // When we get here, we've landed on an item whose value is equal to the - // value of the item we're looking for, but the items themselves are not - // equal. We need to check the items with the same value to the right - // and to the left in order to find an exact match. - - // Check to the right. - for j in stride(from: mid, to: count - 1, by: 1) { - if internalSet[j + 1] == item { - return j + 1 - } else if internalSet[j] < internalSet[j + 1] { - break - } - } - - // Check to the left. - for j in stride(from: mid, to: 0, by: -1) { - if internalSet[j - 1] == item { - return j - 1 - } else if internalSet[j] > internalSet[j - 1] { - break - } - } - return nil - } + + objects.insert(object, at: index) + indexOfKey[object] = index + for i in index+1.. T { - assert(index >= 0 && index < count) - return internalSet[index] + + // O(1) + public func object(at index: Int) -> T { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + return objects[index] } - - // Returns the 'maximum' or 'largest' value in the set. - public func max() -> T? { - return count == 0 ? nil : internalSet[count - 1] + + // O(1) + public func set(_ object: T, at index: Int) { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + guard indexOfKey[object] == nil else { + return + } + + indexOfKey.removeValue(forKey: objects[index]) + indexOfKey[object] = index + objects[index] = object } - - // Returns the 'minimum' or 'smallest' value in the set. - public func min() -> T? { - return count == 0 ? nil : internalSet[0] + + // O(1) + public func indexOf(_ object: T) -> Int { + return indexOfKey[object] ?? -1 } - - // Returns the k-th largest element in the set, if k is in the range - // [1, count]. Returns nil otherwise. - public func kLargest(_ k: Int) -> T? { - return k > count || k <= 0 ? nil : internalSet[count - k] + + // O(n) + public func remove(_ object: T) { + guard let index = indexOfKey[object] else { + return + } + + indexOfKey.removeValue(forKey: object) + objects.remove(at: index) + for i in index.. T? { - return k > count || k <= 0 ? nil : internalSet[k - 1] + + public func all() -> [T] { + return objects } } diff --git a/Ordered Set/OrderedSet.playground/contents.xcplayground b/Ordered Set/OrderedSet.playground/contents.xcplayground index 18c02c912..5da2641c9 100644 --- a/Ordered Set/OrderedSet.playground/contents.xcplayground +++ b/Ordered Set/OrderedSet.playground/contents.xcplayground @@ -1,8 +1,4 @@ - - - - - - + + \ No newline at end of file diff --git a/Ordered Set/OrderedSet.swift b/Ordered Set/OrderedSet.swift index a8b3b0966..8b6df7d3e 100644 --- a/Ordered Set/OrderedSet.swift +++ b/Ordered Set/OrderedSet.swift @@ -1,117 +1,77 @@ -/* - An Ordered Set is a collection where all items in the set follow an ordering, - usually ordered from 'least' to 'most'. The way you value and compare items - can be user-defined. -*/ -public struct OrderedSet { - private var internalSet = [T]() - - public init() { } - - // Returns the number of elements in the OrderedSet. - public var count: Int { - return internalSet.count - } - - // Inserts an item. Performance: O(n) - public mutating func insert(item: T) { - if exists(item) { - return // don't add an item if it already exists +public class OrderedSet { + private var objects: [T] = [] + private var indexOfKey: [T: Int] = [:] + + public init() {} + + // O(1) + public func add(_ object: T) { + guard indexOfKey[object] == nil else { + return } - - // Insert new the item just before the one that is larger. - for i in 0.. item { - internalSet.insert(item, atIndex: i) - return - } - } - - // Append to the back if the new item is greater than any other in the set. - internalSet.append(item) + + objects.append(object) + indexOfKey[object] = objects.count - 1 } - - // Removes an item if it exists. Performance: O(n) - public mutating func remove(item: T) { - if let index = indexOf(item) { - internalSet.removeAtIndex(index) + + // O(n) + public func insert(_ object: T, at index: Int) { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + guard indexOfKey[object] == nil else { + return } - } - - // Returns true if and only if the item exists somewhere in the set. - public func exists(item: T) -> Bool { - return indexOf(item) != nil - } - - // Returns the index of an item if it exists, or -1 otherwise. - public func indexOf(item: T) -> Int? { - var leftBound = 0 - var rightBound = count - 1 - - while leftBound <= rightBound { - let mid = leftBound + ((rightBound - leftBound) / 2) - - if internalSet[mid] > item { - rightBound = mid - 1 - } else if internalSet[mid] < item { - leftBound = mid + 1 - } else if internalSet[mid] == item { - return mid - } else { - // When we get here, we've landed on an item whose value is equal to the - // value of the item we're looking for, but the items themselves are not - // equal. We need to check the items with the same value to the right - // and to the left in order to find an exact match. - - // Check to the right. - for j in mid.stride(to: count - 1, by: 1) { - if internalSet[j + 1] == item { - return j + 1 - } else if internalSet[j] < internalSet[j + 1] { - break - } - } - - // Check to the left. - for j in mid.stride(to: 0, by: -1) { - if internalSet[j - 1] == item { - return j - 1 - } else if internalSet[j] > internalSet[j - 1] { - break - } - } - return nil - } + + objects.insert(object, at: index) + indexOfKey[object] = index + for i in index+1.. T { - assert(index >= 0 && index < count) - return internalSet[index] + + // O(1) + public func object(at index: Int) -> T { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + return objects[index] } - - // Returns the 'maximum' or 'largest' value in the set. - public func max() -> T? { - return count == 0 ? nil : internalSet[count - 1] + + // O(1) + public func set(_ object: T, at index: Int) { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + guard indexOfKey[object] == nil else { + return + } + + indexOfKey.removeValue(forKey: objects[index]) + indexOfKey[object] = index + objects[index] = object } - - // Returns the 'minimum' or 'smallest' value in the set. - public func min() -> T? { - return count == 0 ? nil : internalSet[0] + + // O(1) + public func indexOf(_ object: T) -> Int { + return indexOfKey[object] ?? -1 } - - // Returns the k-th largest element in the set, if k is in the range - // [1, count]. Returns nil otherwise. - public func kLargest(k: Int) -> T? { - return k > count || k <= 0 ? nil : internalSet[count - k] + + // O(n) + public func remove(_ object: T) { + guard let index = indexOfKey[object] else { + return + } + + indexOfKey.removeValue(forKey: object) + objects.remove(at: index) + for i in index.. T? { - return k > count || k <= 0 ? nil : internalSet[k - 1] + + public func all() -> [T] { + return objects } } + diff --git a/Ordered Set/README.markdown b/Ordered Set/README.markdown index bc5003a61..82d736731 100644 --- a/Ordered Set/README.markdown +++ b/Ordered Set/README.markdown @@ -1,337 +1,121 @@ # Ordered Set -An Ordered Set is a collection of unique items in sorted order. Items are usually sorted from least to greatest. +Let's look into how to implement [Ordered Set](https://developer.apple.com/documentation/foundation/nsorderedset). -The Ordered Set data type is a hybrid of: - -- a [Set](https://en.wikipedia.org/wiki/Set_%28mathematics%29), a collection of unique items where the order does not matter, and -- a [Sequence](https://en.wikipedia.org/wiki/Sequence), an ordered list of items where each item may appear more than once. - -It's important to keep in mind that two items can have the same *value* but still may not be equal. For example, we could define "a" and "z" to have the same value (their lengths), but clearly "a" != "z". - -## Why use an ordered set? - -Ordered Sets should be considered when you need to keep your collection sorted at all times, and you do lookups on the collection much more frequently than inserting or deleting items. Many of the lookup operations for an Ordered Set are **O(1)**. - -A good example would be keeping track of the rankings of players in a scoreboard (see example 2 below). - -#### These are ordered sets - -A set of integers: - - [1, 2, 3, 6, 8, 10, 1000] - -A set of strings: - - ["a", "is", "set", "this"] - -The "value" of these strings could be their text content, but also for example their length. - -#### These are not ordered sets - -This set violates the property of uniqueness: - - [1, 1, 2, 3, 5, 8] - -This set violates the sorted property: - - [1, 11, 2, 3] - -## The code - -We'll start by creating our internal representation for the Ordered Set. Since the idea of a set is similar to that of an array, we will use an array to represent our set. Furthermore, since we'll need to keep the set sorted, we need to be able to compare the individual elements. Thus, any type must conform to the [Comparable Protocol](https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Comparable_Protocol/index.html). - -```swift -public struct OrderedSet { - private var internalSet = [T]() - - // Returns the number of elements in the OrderedSet. - public var count: Int { - return internalSet.count - } - ... -``` - -Lets take a look at the `insert()` function first. This first checks if the item already exists in the collection. If so, it returns and does not insert the item. Otherwise, it will insert the item through straightforward iteration. +Here is the example about how it works ```swift - public mutating func insert(_ item: T){ - if exists(item) { - return // don't add an item if it already exists - } - - // Insert new the item just before the one that is larger. - for i in 0.. item { - internalSet.insert(item, at: i) - return - } - } - - // Append to the back if the new item is greater than any other in the set. - internalSet.append(item) - } -``` +let s = AppleOrderedSet() -As we'll see later on, checking if the item is already in the set has an efficiency of **O(log(n) + k)** where **k** is the number of items with the same value as the item we are inserting. +s.add(1) +s.add(2) +s.add(-1) +s.add(0) +s.insert(4, at: 3) -To insert the new item, the `for` loop starts from the beginning of the array, and checks to see if each item is larger than the item we want to insert. Once we find such an item, we insert the new one into its place. This shifts the rest of the array over to the right by 1 position. This loop is at worst **O(n)**. +print(s.all()) // [1, 2, -1, 4, 0] -The total performance of the `insert()` function is therefore **O(n)**. +s.set(-1, at: 0) // We already have -1 in index: 2, so we will do nothing here -Next up is the `remove()` function: +print(s.all()) // [1, 2, -1, 4, 0] -```swift - public mutating func remove(_ item: T) { - if let index = index(of: item) { - internalSet.remove(at: index) - } - } -``` +s.remove(-1) -First this checks if the item exists and then removes it from the array. Because of the `removeAtIndex()` function, the efficiency for remove is **O(n)**. +print(s.all()) // [1, 2, 4, 0] -The next function is `indexOf()`, which takes in an object of type `T` and returns the index of the corresponding item if it is in the set, or `nil` if it is not. Since our set is sorted, we can use a binary search to quickly search for the item. +print(s.object(at: 1)) // 2 -```swift - public func index(of item: T) -> Int? { - var leftBound = 0 - var rightBound = count - 1 - - while leftBound <= rightBound { - let mid = leftBound + ((rightBound - leftBound) / 2) - - if internalSet[mid] > item { - rightBound = mid - 1 - } else if internalSet[mid] < item { - leftBound = mid + 1 - } else if internalSet[mid] == item { - return mid - } else { - // see below - } - } - return nil - } +print(s.object(at: 2)) // 4 ``` -> **Note:** If you are not familiar with the concept of binary search, we have an [article that explains all about it](../Binary%20Search). - -However, there is an important issue to deal with here. Recall that two objects can be unequal yet still have the same "value" for the purposes of comparing them. Since a set can contain multiple items with the same value, it is important to check that the binary search has landed on the correct item. +The significant difference is the the array is not sorted. The elements in the array are the same when insert them. Image the array without duplicates and with `O(logn)` or `O(1)` search time. -For example, consider this ordered set of `Player` objects. Each `Player` has a name and a number of points: - - [ ("Bill", 50), ("Ada", 50), ("Jony", 50), ("Steve", 200), ("Jean-Louis", 500), ("Woz", 1000) ] - -We want the set to be ordered by points, from low to high. Multiple players can have the same number of points. The name of the player is not important for this ordering. However, the name *is* important for retrieving the correct item. - -Let's say we do `indexOf(bill)` where `bill` is player object `("Bill", 50)`. If we did a traditional binary search we'd land on index 2, which is the object `("Jony", 50)`. The value 50 matches, but it's not the object we're looking for! - -Therefore, we also need to check the items with the same value to the right and left of the midpoint. The code to check the left and right side looks like this: +The idea here is using a data structure to provide `O(1)` or `O(logn)` time complexity, so it's easy to think about hash table. ```swift - // Check to the right. - for j in mid.stride(to: count - 1, by: 1) { - if internalSet[j + 1] == item { - return j + 1 - } else if internalSet[j] < internalSet[j + 1] { - break - } - } - - // Check to the left. - for j in mid.stride(to: 0, by: -1) { - if internalSet[j - 1] == item { - return j - 1 - } else if internalSet[j] > internalSet[j - 1] { - break - } - } - - return nil +var indexOfKey: [T: Int] +var objects: [T] ``` -These loops start at the current `mid` value and then look at the neighboring values until we've found the correct object. +`indexOfKey` is used to track the index of the element. `objects` is array holding elements. -The combined runtime for `indexOf()` is **O(log(n) + k)** where **n** is the length of the set, and **k** is the number of items with the same *value* as the one that is being searched for. +We will go through some key functions details here. -Since the set is sorted, the following operations are all **O(1)**: +### Add -```swift - // Returns the 'maximum' or 'largest' value in the set. - public func max() -> T? { - return count == 0 ? nil : internalSet[count - 1] - } - - // Returns the 'minimum' or 'smallest' value in the set. - public func min() -> T? { - return count == 0 ? nil : internalSet[0] - } - - // Returns the k-th largest element in the set, if k is in the range - // [1, count]. Returns nil otherwise. - public func kLargest(_ k: Int) -> T? { - return k > count || k <= 0 ? nil : internalSet[count - k] - } - - // Returns the k-th smallest element in the set, if k is in the range - // [1, count]. Returns nil otherwise. - public func kSmallest(_ k: Int) -> T? { - return k > count || k <= 0 ? nil : internalSet[k - 1] - } -``` - -## Examples - -Below are a few examples that can be found in the playground file. - -### Example 1 - -Here we create a set with random Integers. Printing the largest/smallest 5 numbers in the set is fairly easy. +Update `indexOfKey` and insert element in the end of `objects` ```swift -// Example 1 with type Int -var mySet = OrderedSet() - -// Insert random numbers into the set -for _ in 0..<50 { - mySet.insert(randomNum(50, max: 500)) -} - -print(mySet) - -print(mySet.max()) -print(mySet.min()) - -// Print the 5 largest values -for k in 1...5 { - print(mySet.kLargest(k)) -} - -// Print the 5 lowest values -for k in 1...5 { - print(mySet.kSmallest(k)) +// O(1) +public func add(_ object: T) { + guard indexOfKey[object] == nil else { + return + } + + objects.append(object) + indexOfKey[object] = objects.count - 1 } ``` -### Example 2 +### Insert -In this example we take a look at something a bit more interesting. We define a `Player` struct as follows: +Insert in a random place of the array will cost `O(n)` time. ```swift -public struct Player: Comparable { - public var name: String - public var points: Int +// O(n) +public func insert(_ object: T, at index: Int) { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + guard indexOfKey[object] == nil else { + return + } + + objects.insert(object, at: index) + indexOfKey[object] = index + for i in index+1.. Bool { - return x.name == y.name && x.points == y.points -} -``` +### Set -But `<` only compares the points: +If the `object` already existed in the `OrderedSet`, do nothing. Otherwise, we need to update the `indexOfkey` and `objects`. ```swift -func <(x: Player, y: Player) -> Bool { - return x.points < y.points +// O(1) +public func set(_ object: T, at index: Int) { + assert(index < objects.count, "Index should be smaller than object count") + assert(index >= 0, "Index should be bigger than 0") + + guard indexOfKey[object] == nil else { + return + } + + indexOfKey.removeValue(forKey: objects[index]) + indexOfKey[object] = index + objects[index] = object } ``` -Therefore, two `Player`s can each have the same value (the number of points), but are not guaranteed to be equal (they can have different names). +### Remove -We create a new set and insert 20 random players. The `Player()` constructor gives each player a random name and score: +Remove element in the array will cost `O(n)`. At the same time, we need to update all elements's index after the removed element. ```swift -var playerSet = OrderedSet() - -// Populate the set with random players. -for _ in 0..<20 { - playerSet.insert(Player()) +// O(n) +public func remove(_ object: T) { + guard let index = indexOfKey[object] else { + return + } + + indexOfKey.removeValue(forKey: object) + objects.remove(at: index) + for i in index..() - -repeatedSet.insert(Player(name: "Player 1", points: 100)) -repeatedSet.insert(Player(name: "Player 2", points: 100)) -repeatedSet.insert(Player(name: "Player 3", points: 100)) -repeatedSet.insert(Player(name: "Player 4", points: 100)) -repeatedSet.insert(Player(name: "Player 5", points: 100)) -repeatedSet.insert(Player(name: "Player 6", points: 50)) -repeatedSet.insert(Player(name: "Player 7", points: 200)) -repeatedSet.insert(Player(name: "Player 8", points: 250)) -repeatedSet.insert(Player(name: "Player 9", points: 25)) -``` - -Notice how several of these players have the same value of 100 points. - -The set looks something like this: - - [Player 9, Player 6, Player 1, Player 2, Player 3, Player 4, Player 5, Player 7, Player 8] - -The next line looks for `Player 2`: - -```swift -print(repeatedSet.index(of: Player(name: "Player 2", points: 100))) -``` - -After the binary search finishes, the value of `mid` is at index 5: - - [Player 9, Player 6, Player 1, Player 2, Player 3, Player 4, Player 5, Player 7, Player 8] - mid - -However, this is not `Player 2`. Both `Player 4` and `Player 2` have the same points, but a different name. The binary search only looked at the points, not the name. - -But we do know that `Player 2` must be either to the immediate left or the right of `Player 4`, so we check both sides of `mid`. We only need to look at the objects with the same value as `Player 4`. The others are replaced by `X`: - - [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] - mid - -The code then first checks on the right of `mid` (where the `*` is): - - [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] - mid * - -The right side did not contain the item, so we look at the left side: - - [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] - * mid - - [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] - * mid - -Finally, we've found `Player 2`, and return index 3. - -## See Also - -[Apple Ordered Set](AppleOrderedSet.playground) -@remlostime added an Apple version of `OrderedSet` - -*Written By Zain Humayun* +*Written By Kai Chen* diff --git a/Palindromes/Palindromes.playground/Contents.swift b/Palindromes/Palindromes.playground/Contents.swift index b9e857dd0..49f767982 100644 --- a/Palindromes/Palindromes.playground/Contents.swift +++ b/Palindromes/Palindromes.playground/Contents.swift @@ -2,44 +2,7 @@ import Foundation -/** - Validate that a string is a plaindrome - - parameter str: The string to validate - - returns: `true` if string is plaindrome, `false` if string is not - */ -func isPalindrome(_ str: String) -> Bool { - let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil) - let length = strippedString.characters.count - - if length > 1 { - return palindrome(strippedString.lowercased(), left: 0, right: length - 1) - } - return false -} - -/** - Compares a strings left side character against right side character following - - parameter str: The string to compare characters of - - parameter left: Index of left side to compare, must be less than or equal to right - - parameter right: Index of right side to compare, must be greater than or equal to left - - returns: `true` if left side and right side have all been compared and they all match, `false` if a left and right aren't equal - */ -private func palindrome(_ str: String, left: Int, right: Int) -> Bool { - if left >= right { - return true - } - - let lhs = str[str.index(str.startIndex, offsetBy: left)] - let rhs = str[str.index(str.startIndex, offsetBy: right)] - - if lhs != rhs { - return false - } - - return palindrome(str, left: left + 1, right: right - 1) -} - -//true +// true isPalindrome("A man, a plan, a canal, Panama!") isPalindrome("abbcbba") isPalindrome("racecar") diff --git a/Palindromes/Palindromes.playground/Sources/Palindrome.swift b/Palindromes/Palindromes.playground/Sources/Palindrome.swift new file mode 100644 index 000000000..3652dbb1f --- /dev/null +++ b/Palindromes/Palindromes.playground/Sources/Palindrome.swift @@ -0,0 +1,38 @@ +import Foundation + +/** + Validate that a string is a plaindrome + - parameter str: The string to validate + - returns: `true` if string is plaindrome, `false` if string is not + */ +public func isPalindrome(_ str: String) -> Bool { + let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil) + let length = strippedString.count + + if length > 1 { + return palindrome(strippedString.lowercased(), left: 0, right: length - 1) + } + return false +} + +/** + Compares a strings left side character against right side character following + - parameter str: The string to compare characters of + - parameter left: Index of left side to compare, must be less than or equal to right + - parameter right: Index of right side to compare, must be greater than or equal to left + - returns: `true` if left side and right side have all been compared and they all match, `false` if a left and right aren't equal + */ +private func palindrome(_ str: String, left: Int, right: Int) -> Bool { + if left >= right { + return true + } + + let lhs = str[str.index(str.startIndex, offsetBy: left)] + let rhs = str[str.index(str.startIndex, offsetBy: right)] + + if lhs != rhs { + return false + } + + return palindrome(str, left: left + 1, right: right - 1) +} diff --git a/Palindromes/Palindromes.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Palindromes/Palindromes.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Palindromes/Palindromes.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Palindromes/Palindromes.swift b/Palindromes/Palindromes.swift deleted file mode 100644 index 9ddd04c80..000000000 --- a/Palindromes/Palindromes.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation - -func isPalindrome(_ str: String) -> Bool { - let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil) - let length = strippedString.characters.count - - if length > 1 { - return palindrome(strippedString.lowercased(), left: 0, right: length - 1) - } - return false -} - -private func palindrome(_ str: String, left: Int, right: Int) -> Bool { - if left >= right { - return true - } - - let lhs = str[str.index(str.startIndex, offsetBy: left)] - let rhs = str[str.index(str.startIndex, offsetBy: right)] - - if lhs != rhs { - return false - } - - return palindrome(str, left: left + 1, right: right - 1) -} diff --git a/Palindromes/README.markdown b/Palindromes/README.markdown index 18518adbe..e4012d8da 100644 --- a/Palindromes/README.markdown +++ b/Palindromes/README.markdown @@ -26,7 +26,7 @@ Here is a recursive implementation of this in Swift: ```swift func isPalindrome(_ str: String) -> Bool { let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil) - let length = strippedString.characters.count + let length = strippedString.count if length > 1 { return palindrome(strippedString.lowercased(), left: 0, right: length - 1) @@ -104,4 +104,4 @@ racecar -> left index == right index -> return true [Palindrome Wikipedia](https://en.wikipedia.org/wiki/Palindrome) -*Written by [Joshua Alvarado](https://github.com/https://github.com/lostatseajoshua)* +*Written by [Joshua Alvarado](https://github.com/lostatseajoshua)* diff --git a/Palindromes/Test/Palindromes.swift b/Palindromes/Test/Palindrome.swift similarity index 93% rename from Palindromes/Test/Palindromes.swift rename to Palindromes/Test/Palindrome.swift index 9ddd04c80..16af14fdb 100644 --- a/Palindromes/Test/Palindromes.swift +++ b/Palindromes/Test/Palindrome.swift @@ -2,7 +2,7 @@ import Foundation func isPalindrome(_ str: String) -> Bool { let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil) - let length = strippedString.characters.count + let length = strippedString.count if length > 1 { return palindrome(strippedString.lowercased(), left: 0, right: length - 1) diff --git a/Palindromes/Test/Test.xcodeproj/project.pbxproj b/Palindromes/Test/Test.xcodeproj/project.pbxproj index 07aae3e63..f01f6f061 100644 --- a/Palindromes/Test/Test.xcodeproj/project.pbxproj +++ b/Palindromes/Test/Test.xcodeproj/project.pbxproj @@ -8,11 +8,11 @@ /* Begin PBXBuildFile section */ 9437D8841E0D960A00A38FB8 /* Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9437D8831E0D960A00A38FB8 /* Test.swift */; }; - 9437D88B1E0D969500A38FB8 /* Palindromes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9437D8791E0D948A00A38FB8 /* Palindromes.swift */; }; + 9437D88B1E0D969500A38FB8 /* Palindrome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9437D8791E0D948A00A38FB8 /* Palindrome.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 9437D8791E0D948A00A38FB8 /* Palindromes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palindromes.swift; sourceTree = ""; }; + 9437D8791E0D948A00A38FB8 /* Palindrome.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palindrome.swift; sourceTree = ""; }; 9437D8811E0D960A00A38FB8 /* Test.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Test.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9437D8831E0D960A00A38FB8 /* Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test.swift; sourceTree = ""; }; 9437D8851E0D960A00A38FB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -32,7 +32,7 @@ 9437D8651E0D945200A38FB8 = { isa = PBXGroup; children = ( - 9437D8791E0D948A00A38FB8 /* Palindromes.swift */, + 9437D8791E0D948A00A38FB8 /* Palindrome.swift */, 9437D8821E0D960A00A38FB8 /* Test */, 9437D86F1E0D945200A38FB8 /* Products */, ); @@ -82,11 +82,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Joshua Alvarado"; TargetAttributes = { 9437D8801E0D960A00A38FB8 = { CreatedOnToolsVersion = 8.2; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; }; @@ -123,7 +124,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9437D88B1E0D969500A38FB8 /* Palindromes.swift in Sources */, + 9437D88B1E0D969500A38FB8 /* Palindrome.swift in Sources */, 9437D8841E0D960A00A38FB8 /* Test.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -140,15 +141,23 @@ 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_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_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; @@ -187,15 +196,23 @@ 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_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_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; @@ -229,7 +246,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -243,7 +261,8 @@ PRODUCT_BUNDLE_IDENTIFIER = self.edu.Test; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -266,6 +285,7 @@ 9437D8881E0D960A00A38FB8 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Palindromes/Test/Test.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Palindromes/Test/Test.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Palindromes/Test/Test.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Palindromes/Test/Test.xcodeproj/xcshareddata/xcschemes/Test.xcscheme b/Palindromes/Test/Test.xcodeproj/xcshareddata/xcschemes/Test.xcscheme index 9a1b39e69..72ae3d696 100644 --- a/Palindromes/Test/Test.xcodeproj/xcshareddata/xcschemes/Test.xcscheme +++ b/Palindromes/Test/Test.xcodeproj/xcshareddata/xcschemes/Test.xcscheme @@ -1,6 +1,6 @@ + + + + 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/Priority Queue/Tests/PriorityQueueTests.swift b/Priority Queue/Tests/PriorityQueueTests.swift index 56536310d..e31fa3d07 100755 --- a/Priority Queue/Tests/PriorityQueueTests.swift +++ b/Priority Queue/Tests/PriorityQueueTests.swift @@ -11,6 +11,13 @@ private func < (m1: Message, m2: Message) -> Bool { } class PriorityQueueTest: XCTestCase { + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + func testEmpty() { var queue = PriorityQueue(sort: <) XCTAssertTrue(queue.isEmpty) diff --git a/Priority Queue/Tests/Tests.xcodeproj/project.pbxproj b/Priority Queue/Tests/Tests.xcodeproj/project.pbxproj index 0f7d84be9..5ddfd574d 100644 --- a/Priority Queue/Tests/Tests.xcodeproj/project.pbxproj +++ b/Priority Queue/Tests/Tests.xcodeproj/project.pbxproj @@ -180,6 +180,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -219,6 +220,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -230,7 +232,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 = 4.0; }; name = Debug; }; @@ -242,7 +244,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 = 4.0; }; name = Release; }; diff --git a/QuadTree/README.md b/QuadTree/README.md index 3fc682547..ff660f852 100644 --- a/QuadTree/README.md +++ b/QuadTree/README.md @@ -6,11 +6,11 @@ A quadtree is a [tree](https://github.com/raywenderlich/swift-algorithm-club/tre ### Problem -Consider the following problem: your need to store a number of points (each point is a pair of `X` and `Y` coordinates) and then you need to answer which points lie in a certain rectangular region. A naive solution would be to store the points inside an array and then iterate over the points and check each one individually. This solution runs in O(n) though. +Consider the following problem: your need to store a number of points (each point is a pair of `X` and `Y` coordinates) and then you need to answer which points lie in a certain rectangular region. A naive solution would be to store the points inside an array and then iterate over the points and check each one individually. This solution runs in O(n) though. ### A Better Approach -Quadrees are most commonly used to partition a two-dimensional space by recursively subdividing it into four regions(quadrants). Let's see how we can use a Quadtree to store the points. +Quadtrees are most commonly used to partition a two-dimensional space by recursively subdividing it into four regions(quadrants). Let's see how we can use a Quadtree to store the points. Each node in the tree represents a rectangular region and stores a limited number(`maxPointCapacity`) of points that all lie in its region. @@ -40,7 +40,7 @@ class QuadTreeNode { init(rect: Rect) { self.rect = rect } - + ... } @@ -52,11 +52,11 @@ extension QuadTreeNode { @discardableResult func add(point: Point) -> Bool { - - if !rect.containts(point: point) { + + if !rect.contains(point: point) { return false } - + switch type { case .internal(let children): // pass the point to one of the children @@ -75,7 +75,7 @@ extension QuadTreeNode { } return true } - + private func subdivide() { switch type { case .leaf: @@ -105,9 +105,9 @@ To find the points that lie in a given region we can now traverse the tree from class QuadTree { ... - + let root: QuadTreeNode - + public func points(inRect rect: Rect) -> [Point] { return root.points(inRect: rect) } @@ -115,23 +115,23 @@ class QuadTree { extension QuadTreeNode { func points(inRect rect: Rect) -> [Point] { - + // if the node's rect and the given rect don't intersect, return an empty array, // because there can't be any points that lie the node's (or its children's) rect and // in the given rect if !self.rect.intersects(rect: rect) { return [] } - + var result: [Point] = [] - + // collect the node's points that lie in the rect for point in points { - if rect.containts(point: point) { + if rect.contains(point: point) { result.append(point) } } - + switch type { case .leaf: break @@ -141,7 +141,7 @@ extension QuadTreeNode { result.append(contentsOf: childNode.points(inRect: rect)) } } - + return result } } @@ -152,9 +152,8 @@ Both adding a point and searching can still take up to O(n) in the worst case, s ### See also -Displaying a large amount of objects in a MapView - a great use case for a Quadtree ([Thoughtbot Atricle](https://robots.thoughtbot.com/how-to-handle-large-amounts-of-data-on-maps)) +Displaying a large amount of objects in a MapView - a great use case for a Quadtree ([Thoughtbot Article](https://robots.thoughtbot.com/how-to-handle-large-amounts-of-data-on-maps)) -More info on [Wiki](https://en.wikipedia.org/wiki/Quadtree) +More info on [Wikipedia](https://en.wikipedia.org/wiki/Quadtree) *Written for Swift Algorithm Club by Timur Galimov* - 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.. + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Queue/README.markdown b/Queue/README.markdown index 8938e0cf1..ca55f0f28 100644 --- a/Queue/README.markdown +++ b/Queue/README.markdown @@ -1,5 +1,7 @@ # Queue +> This topic has been tutorialized [here](https://www.raywenderlich.com/148141/swift-algorithm-club-swift-queue-data-structure) + A queue is a list where you can only insert new items at the back and remove items from the front. This ensures that the first item you enqueue is also the first item you dequeue. First come, first serve! Why would you need this? Well, in many algorithms you want to add objects to a temporary list and pull them off this list later. Often the order in which you add and remove these objects matters. diff --git a/Queue/Tests/Tests.xcodeproj/project.pbxproj b/Queue/Tests/Tests.xcodeproj/project.pbxproj index a554fd16e..f927aba0d 100644 --- a/Queue/Tests/Tests.xcodeproj/project.pbxproj +++ b/Queue/Tests/Tests.xcodeproj/project.pbxproj @@ -83,12 +83,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 1000; }; }; }; @@ -141,14 +141,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; @@ -187,14 +195,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; @@ -226,7 +242,8 @@ 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_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -238,7 +255,8 @@ 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_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Queue/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Queue/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Queue/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 14f27f777..afd69e6a7 100644 --- a/Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ (_ a: inout [T], low: Int, high: Int) -> Int var i = low for j in low..(_ a: inout [T], low: Int, high: Int) -> Int { repeat { i += 1 } while a[i] < pivot if i < j { - swap(&a[i], &a[j]) + a.swapAt(i, j) } else { return j } @@ -132,7 +132,7 @@ list4 */ public func swap(_ a: inout [T], _ i: Int, _ j: Int) { if i != j { - swap(&a[i], &a[j]) + a.swapAt(i, j) } } diff --git a/Quicksort/Quicksort.swift b/Quicksort/Quicksort.swift index 39fa27b66..a67122723 100644 --- a/Quicksort/Quicksort.swift +++ b/Quicksort/Quicksort.swift @@ -90,7 +90,7 @@ func partitionHoare(_ a: inout [T], low: Int, high: Int) -> Int { repeat { i += 1 } while a[i] < pivot if i < j { - swap(&a[i], &a[j]) + a.swapAt(i, j) } else { return j } @@ -144,7 +144,7 @@ func quicksortRandom(_ a: inout [T], low: Int, high: Int) { */ public func swap(_ a: inout [T], _ i: Int, _ j: Int) { if i != j { - swap(&a[i], &a[j]) + a.swapAt(i, j) } } diff --git a/Quicksort/README.markdown b/Quicksort/README.markdown index e0bd8e604..120ddc346 100644 --- a/Quicksort/README.markdown +++ b/Quicksort/README.markdown @@ -137,12 +137,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 { repeat { i += 1 } while a[i] < pivot if i < j { - swap(&a[i], &a[j]) + a.swapAt(i, j) } else { return j } 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.xcodeproj/project.pbxproj b/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj similarity index 84% rename from Quicksort/Tests/Tests.xcodeproj/project.pbxproj rename to Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj index 2d8ee18ff..923195121 100644 --- a/Quicksort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 7B2BBC801C779D720067B71D /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B2BBC801C779D720067B71D /* Tests-Quicksort.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests-Quicksort.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 7B2BBC941C779E7B0067B71D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; 7B80C3E51C77A4CA003CECC7 /* Quicksort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Quicksort.swift; path = ../Quicksort.swift; sourceTree = SOURCE_ROOT; }; 7B80C3E71C77A4D0003CECC7 /* QuicksortTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuicksortTests.swift; sourceTree = SOURCE_ROOT; }; @@ -42,7 +42,7 @@ 7B2BBC721C779D710067B71D /* Products */ = { isa = PBXGroup; children = ( - 7B2BBC801C779D720067B71D /* Tests.xctest */, + 7B2BBC801C779D720067B71D /* Tests-Quicksort.xctest */, ); name = Products; sourceTree = ""; @@ -62,9 +62,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 7B2BBC7F1C779D720067B71D /* Tests */ = { + 7B2BBC7F1C779D720067B71D /* Tests-Quicksort */ = { isa = PBXNativeTarget; - buildConfigurationList = 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "Tests" */; + buildConfigurationList = 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "Tests-Quicksort" */; buildPhases = ( 7B2BBC7C1C779D720067B71D /* Sources */, 7B2BBC7D1C779D720067B71D /* Frameworks */, @@ -74,9 +74,9 @@ ); dependencies = ( ); - name = Tests; + name = "Tests-Quicksort"; productName = TestsTests; - productReference = 7B2BBC801C779D720067B71D /* Tests.xctest */; + productReference = 7B2BBC801C779D720067B71D /* Tests-Quicksort.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -86,16 +86,16 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0820; + LastSwiftMigration = 0900; }; }; }; - buildConfigurationList = 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "Tests" */; + buildConfigurationList = 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "Tests-Quicksort" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; @@ -108,7 +108,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 7B2BBC7F1C779D720067B71D /* Tests */, + 7B2BBC7F1C779D720067B71D /* Tests-Quicksort */, ); }; /* End PBXProject section */ @@ -145,14 +145,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; @@ -180,6 +188,8 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -191,14 +201,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; @@ -219,6 +237,8 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -230,7 +250,8 @@ 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_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -242,14 +263,15 @@ 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_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "Tests" */ = { + 7B2BBC6C1C779D710067B71D /* Build configuration list for PBXProject "Tests-Quicksort" */ = { isa = XCConfigurationList; buildConfigurations = ( 7B2BBC871C779D720067B71D /* Debug */, @@ -258,7 +280,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "Tests" */ = { + 7B2BBC8C1C779D720067B71D /* Build configuration list for PBXNativeTarget "Tests-Quicksort" */ = { isa = XCConfigurationList; buildConfigurations = ( 7B2BBC8D1C779D720067B71D /* Debug */, diff --git a/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..c948249d9 --- /dev/null +++ b/Quicksort/Tests/Tests-Quicksort.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Quicksort/Tests/Tests-Quicksort.xcodeproj/xcshareddata/xcschemes/Tests-Quicksort.xcscheme b/Quicksort/Tests/Tests-Quicksort.xcodeproj/xcshareddata/xcschemes/Tests-Quicksort.xcscheme new file mode 100644 index 000000000..ec81527d1 --- /dev/null +++ b/Quicksort/Tests/Tests-Quicksort.xcodeproj/xcshareddata/xcschemes/Tests-Quicksort.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.markdown b/README.markdown index bc6018aa7..fbcde7c86 100644 --- a/README.markdown +++ b/README.markdown @@ -1,4 +1,4 @@ -![Swift Algorithm Club](/Images/SwiftAlgorithm-410-transp.png) +![Swift Algorithm Club](Images/SwiftAlgorithm-410-transp.png) # Welcome to the Swift Algorithm Club! @@ -8,7 +8,7 @@ If you're a computer science student who needs to learn this stuff for exams -- The goal of this project is to **explain how algorithms work**. The focus is on clarity and readability of the code, not on making a reusable library that you can drop into your own projects. That said, most of the code should be ready for production use but you may need to tweak it to fit into your own codebase. -Most code is compatible with **Xcode 8.3** and **Swift 3**. We'll keep this updated with the latest version of Swift. +Code is compatible with **Xcode 10** and **Swift 4.2**. We'll keep this updated with the latest version of Swift. If you're interested in a GitHub pages version of the repo, check out [this](https://aquarchitect.github.io/swift-algorithm-club/). :heart_eyes: **Suggestions and contributions are welcome!** :heart_eyes: @@ -33,7 +33,7 @@ If you're new to algorithms and data structures, here are a few good ones to sta - [Insertion Sort](Insertion%20Sort/) - [Binary Search](Binary%20Search/) and [Binary Search Tree](Binary%20Search%20Tree/) - [Merge Sort](Merge%20Sort/) -- [Boyer-Moore string search](Boyer-Moore/) +- [Boyer-Moore string search](Boyer-Moore-Horspool/) ## The algorithms @@ -51,7 +51,7 @@ If you're new to algorithms and data structures, here are a few good ones to sta ### String Search - [Brute-Force String Search](Brute-Force%20String%20Search/). A naive method. -- [Boyer-Moore](Boyer-Moore/). A fast method to search for substrings. It skips ahead based on a look-up table, to avoid looking at every character in the text. +- [Boyer-Moore](Boyer-Moore-Horspool/). A fast method to search for substrings. It skips ahead based on a look-up table, to avoid looking at every character in the text. - [Knuth-Morris-Pratt](Knuth-Morris-Pratt/). A linear-time string algorithm that returns indexes of all occurrencies of a given pattern. - [Rabin-Karp](Rabin-Karp/) Faster search by using hashing. - [Longest Common Subsequence](Longest%20Common%20Subsequence/). Find the longest sequence of characters that appear in the same order in both strings. @@ -73,6 +73,10 @@ Fast sorts: - [Merge Sort](Merge%20Sort/) - [Heap Sort](Heap%20Sort/) +Hybrid sorts: + +- [Introsort](Introsort/) + Special-purpose sorts: - [Counting Sort](Counting%20Sort/) @@ -96,7 +100,8 @@ Bad sorting algorithms (don't use these!): - [Convex Hull](Convex%20Hull/). - [Miller-Rabin Primality Test](Miller-Rabin%20Primality%20Test/). Is the number a prime number? - [MinimumCoinChange](MinimumCoinChange/). A showcase for dynamic programming. - +- [Genetic](Genetic/). A simple example on how to slowly mutate a value to its ideal form, in the context of biological evolution. +- [Myers Difference Algorithm](Myers%20Difference%20Algorithm/). Finding the longest common subsequence of two sequences. ### Mathematics - [Greatest Common Divisor (GCD)](GCD/). Special bonus: the least common multiple. @@ -105,6 +110,7 @@ Bad sorting algorithms (don't use these!): - [Karatsuba Multiplication](Karatsuba%20Multiplication/). Another take on elementary multiplication. - [Haversine Distance](HaversineDistance/). Calculating the distance between 2 points from a sphere. - [Strassen's Multiplication Matrix](Strassen%20Matrix%20Multiplication/). Efficient way to handle matrix multiplication. +- [CounterClockWise](/CounterClockWise/). Determining the area of a simple polygon. ### Machine learning @@ -114,6 +120,8 @@ Bad sorting algorithms (don't use these!): - Logistic Regression - Neural Networks - PageRank +- [Naive Bayes Classifier](Naive%20Bayes%20Classifier/) +- [Simulated annealing](Simulated%20annealing/). Probabilistic technique for approximating the global maxima in a (often discrete) large search space. ## Data structures @@ -144,7 +152,7 @@ Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types ### Lists - [Linked List](Linked%20List/). A sequence of data items connected through links. Covers both singly and doubly linked lists. -- [Skip-List](Skip-List/). Skip List is a probablistic data-structure with same logarithmic time bound and efficiency as AVL/ or Red-Black tree and provides a clever compromise to efficiently support search and update operations. +- [Skip-List](Skip-List/). Skip List is a probabilistic data-structure with same logarithmic time bound and efficiency as AVL/ or Red-Black tree and provides a clever compromise to efficiently support search and update operations. ### Trees @@ -152,15 +160,18 @@ Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types - [Binary Tree](Binary%20Tree/). A tree where each node has at most two children. - [Binary Search Tree (BST)](Binary%20Search%20Tree/). A binary tree that orders its nodes in a way that allows for fast queries. - [Red-Black Tree](Red-Black%20Tree/). A self balancing binary search tree. -- Splay Tree +- [Splay Tree](Splay%20Tree/). A self balancing binary search tree that enables fast retrieval of recently updated elements. - [Threaded Binary Tree](Threaded%20Binary%20Tree/). A binary tree that maintains a few extra variables for cheap and fast in-order traversals. - [Segment Tree](Segment%20Tree/). Can quickly compute a function over a portion of an array. + - [Lazy Propagation](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Segment%20Tree/LazyPropagation) - kd-Tree +- [Sparse Table](Sparse%20Table/). Another take on quickly computing a function over a portion of an array, but this time we'll make it even quicker!. - [Heap](Heap/). A binary tree stored in an array, so it doesn't use pointers. Makes a great priority queue. - Fibonacci Heap - [Trie](Trie/). A special type of tree used to store associative data structures. - [B-Tree](B-Tree/). A self-balancing search tree, in which nodes can have more than two children. - [QuadTree](QuadTree/). A tree with 4 children. +- [Octree](Octree/). A tree with 8 children. ### Hashing @@ -171,7 +182,7 @@ Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types - [Bloom Filter](Bloom%20Filter/). A constant-memory data structure that probabilistically tests whether an element is in a set. - [Hash Set](Hash%20Set/). A set implemented using a hash table. -- Multiset +- [Multiset](Multiset/). A set where the number of times an element is added matters. (Also known as a bag.) - [Ordered Set](Ordered%20Set/). A set where the order of items matters. ### Graphs @@ -185,51 +196,43 @@ Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types - [Minimum Spanning Tree](Minimum%20Spanning%20Tree/) - [All-Pairs Shortest Paths](All-Pairs%20Shortest%20Paths/) - [Dijkstra's shortest path algorithm](Dijkstra%20Algorithm/) +- [A-Star](A-Star/) ## Puzzles A lot of software developer interview questions consist of algorithmic puzzles. Here is a small selection of fun ones. For more puzzles (with answers), see [here](http://elementsofprogramminginterviews.com/) and [here](http://www.crackingthecodinginterview.com). - [Two-Sum Problem](Two-Sum%20Problem/) +- [Three-Sum/Four-Sum Problem](3Sum%20and%204Sum/) - [Fizz Buzz](Fizz%20Buzz/) - [Monty Hall Problem](Monty%20Hall%20Problem/) - [Finding Palindromes](Palindromes/) - [Dining Philosophers](DiningPhilosophers/) +- [Egg Drop Problem](Egg%20Drop%20Problem/) +- [Encoding and Decoding Binary Tree](Encode%20and%20Decode%20Tree/) +- [Closest Pair](Closest%20Pair/) ## Learn more! -For more information, check out these great books: +Like what you see? Check out [Data Structures & Algorithms in Swift](https://store.raywenderlich.com/products/data-structures-and-algorithms-in-swift), the official book by the Swift Algorithm Club team! -- [Introduction to Algorithms](https://mitpress.mit.edu/books/introduction-algorithms) by Cormen, Leiserson, Rivest, Stein -- [The Algorithm Design Manual](http://www.algorist.com) by Skiena -- [Elements of Programming Interviews](http://elementsofprogramminginterviews.com) by Aziz, Lee, Prakash -- [Algorithms](http://www.cs.princeton.edu/~rs/) by Sedgewick -- [Grokking Algorithms](https://www.manning.com/books/grokking-algorithms) by Aditya Bhargava +![Data Structures & Algorithms in Swift Book](Images/DataStructuresAndAlgorithmsInSwiftBook.png) -The following books are available for free online: +You’ll start with the fundamental structures of linked lists, queues and stacks, and see how to implement them in a highly Swift-like way. Move on to working with various types of trees, including general purpose trees, binary trees, AVL trees, binary search trees, and tries. -- [Algorithms](http://www.beust.com/algorithms.pdf) by Dasgupta, Papadimitriou, Vazirani -- [Algorithms, Etc.](http://jeffe.cs.illinois.edu/teaching/algorithms/) by Erickson -- [Algorithms + Data Structures = Programs](http://www.ethoberon.ethz.ch/WirthPubl/AD.pdf) by Wirth -- Algorithms and Data Structures: The Basic Toolbox by Mehlhorn and Sanders -- [Open Data Structures](http://opendatastructures.org) by Pat Morin -- [Wikibooks: Algorithms and Implementations](https://en.wikibooks.org/wiki/Algorithm_Implementation) +Go beyond bubble and insertion sort with better-performing algorithms, including mergesort, radix sort, heap sort, and quicksort. Learn how to construct directed, non-directed and weighted graphs to represent many real-world models, and traverse graphs and trees efficiently with breadth-first, depth-first, Dijkstra’s and Prim’s algorithms to solve problems such as finding the shortest path or lowest cost in a network. -Other algorithm repositories: +By the end of this book, you’ll have hands-on experience solving common issues with data structures and algorithms — and you’ll be well on your way to developing your own efficient and useful implementations! -- [EKAlgorithms](https://github.com/EvgenyKarkan/EKAlgorithms). A great collection of algorithms in Objective-C. -- [@lorentey](https://github.com/lorentey/). Production-quality Swift implementations of common algorithms and data structures. -- [Rosetta Code](http://rosettacode.org). Implementations in pretty much any language you can think of. -- [AlgorithmVisualizer](http://jasonpark.me/AlgorithmVisualizer/). Visualize algorithms on your browser. -- [Swift Structures](https://github.com/waynewbishop/SwiftStructures) Data Structures with directions on how to use them [here](http://waynewbishop.com/swift) +You can find the book on the [raywenderlich.com store](https://store.raywenderlich.com/products/data-structures-and-algorithms-in-swift). ## Credits The Swift Algorithm Club was originally created by [Matthijs Hollemans](https://github.com/hollance). -It is now maintained by [Vincent Ngo](https://www.raywenderlich.com/u/jomoka), [Kelvin Lau](https://github.com/kelvinlauKL) and [Ross O'brien](https://www.raywenderlich.com/u/narrativium). +It is now maintained by [Vincent Ngo](https://www.raywenderlich.com/u/jomoka), [Kelvin Lau](https://github.com/kelvinlauKL), and [Richard Ash](https://github.com/richard-ash). -The Swift Algorithm Club is a collaborative effort from the [most algorithmic members](https://github.com/rwenderlich/swift-algorithm-club/graphs/contributors) of the [raywenderlich.com](https://www.raywenderlich.com) community. We're always looking for help - why not [join the club](.github/CONTRIBUTING.md)? :] +The Swift Algorithm Club is a collaborative effort from the [most algorithmic members](https://github.com/raywenderlich/swift-algorithm-club/graphs/contributors) of the [raywenderlich.com](https://www.raywenderlich.com) community. We're always looking for help - why not [join the club](.github/CONTRIBUTING.md)? :] ## License diff --git a/Rabin-Karp/README.markdown b/Rabin-Karp/README.markdown index 79699caa8..a618d3981 100644 --- a/Rabin-Karp/README.markdown +++ b/Rabin-Karp/README.markdown @@ -1,6 +1,6 @@ # Rabin-Karp string search algorithm -The Rabin-Karp string search alogrithm is used to search text for a pattern. +The Rabin-Karp string search algorithm is used to search text for a pattern. A practical application of the algorithm is detecting plagiarism. Given source material, the algorithm can rapidly search through a paper for instances of sentences from the source material, ignoring details such as case and punctuation. Because of the abundance of the sought strings, single-string searching algorithms are impractical. @@ -12,10 +12,10 @@ at a time (e.g. "he ") and subtracts out the previous hash from the "T". ## Algorithm -The Rabin-Karp alogrithm uses a sliding window the size of the search pattern. It starts by hashing the search pattern, then +The Rabin-Karp algorithm uses a sliding window the size of the search pattern. It starts by hashing the search pattern, then hashing the first x characters of the text string where x is the length of the search pattern. It then slides the window one character over and uses the previous hash value to calculate the new hash faster. Only when it finds a hash that matches the hash of the search pattern will it compare -the two strings it see if they are the same (prevent a hash collision from producing a false positive) +the two strings it see if they are the same (to prevent a hash collision from producing a false positive). ## The code @@ -24,8 +24,8 @@ The major search method is next. More implementation details are in rabin-karp. ```swift public func search(text: String , pattern: String) -> Int { // convert to array of ints - let patternArray = pattern.characters.flatMap { $0.asInt } - let textArray = text.characters.flatMap { $0.asInt } + let patternArray = pattern.flatMap { $0.asInt } + let textArray = text.flatMap { $0.asInt } if textArray.count < patternArray.count { return -1 @@ -37,7 +37,7 @@ public func search(text: String , pattern: String) -> Int { let firstHash = hash(array: firstChars) if (patternHash == firstHash) { - // Verify this was not a hash collison + // Verify this was not a hash collision if firstChars == patternArray { return 0 } @@ -76,4 +76,4 @@ This will return 13 since ump is in the 13 position of the zero based string. [Rabin-Karp Wikipedia](https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm) -*Written by [Bill Barbour](https://github.com/brbatwork)* \ No newline at end of file +*Written by [Bill Barbour](https://github.com/brbatwork)* diff --git a/Rabin-Karp/Rabin-Karp.playground/Contents.swift b/Rabin-Karp/Rabin-Karp.playground/Contents.swift index 4956fabe3..e087caa49 100644 --- a/Rabin-Karp/Rabin-Karp.playground/Contents.swift +++ b/Rabin-Karp/Rabin-Karp.playground/Contents.swift @@ -1,9 +1,14 @@ //: Taking our rabin-karp algorithm for a walk +// last checked with Xcode 9.4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + import UIKit struct Constants { - static let hashMultiplier = 69069 + static let hashMultiplier = 69061 } precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence } @@ -25,8 +30,8 @@ extension Character { // Find first position of pattern in the text using Rabin Karp algorithm public func search(text: String, pattern: String) -> Int { // convert to array of ints - let patternArray = pattern.characters.flatMap { $0.asInt } - let textArray = text.characters.flatMap { $0.asInt } + let patternArray = pattern.compactMap { $0.asInt } + let textArray = text.compactMap { $0.asInt } if textArray.count < patternArray.count { return -1 diff --git a/Rabin-Karp/Rabin-Karp.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Rabin-Karp/Rabin-Karp.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Rabin-Karp/Rabin-Karp.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Radix Sort/RadixSort.playground/Contents.swift b/Radix Sort/RadixSort.playground/Contents.swift index a4f7e3527..0b549fc9e 100644 --- a/Radix Sort/RadixSort.playground/Contents.swift +++ b/Radix Sort/RadixSort.playground/Contents.swift @@ -1,16 +1,10 @@ //: Playground - noun: a place where people can play -import Cocoa - // Test Radix Sort with small array of ten values var array: [Int] = [19, 4242, 2, 9, 912, 101, 55, 67, 89, 32] radixSort(&array) // Test Radix Sort with large array of 1000 random values -var bigArray = [Int](repeating: 0, count: 1000) -var i = 0 -while i < 1000 { - bigArray[i] = Int(arc4random_uniform(1000) + 1) - i += 1 -} +var bigArray = (0..<1000).map { _ in Int.random(in: 1...1000) } +bigArray radixSort(&bigArray) diff --git a/Radix Sort/RadixSort.playground/Sources/radixSort.swift b/Radix Sort/RadixSort.playground/Sources/radixSort.swift index 424d33fb9..af382f481 100644 --- a/Radix Sort/RadixSort.playground/Sources/radixSort.swift +++ b/Radix Sort/RadixSort.playground/Sources/radixSort.swift @@ -3,7 +3,6 @@ Sorting Algorithm that sorts an input array of integers digit by digit. */ -import Foundation // NOTE: This implementation does not handle negative numbers public func radixSort(_ array: inout [Int] ) { diff --git a/Radix Sort/Tests/Tests.xcodeproj/project.pbxproj b/Radix Sort/Tests/Tests.xcodeproj/project.pbxproj index 4b32f71b5..bef4ead9b 100644 --- a/Radix Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Radix Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -179,6 +179,7 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -220,6 +221,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -233,7 +235,7 @@ PRODUCT_BUNDLE_IDENTIFIER = Swift.Algorithm.Club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -246,7 +248,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 = 4.0; }; name = Release; }; diff --git a/Radix Tree/RadixTree.playground/Contents.swift b/Radix Tree/RadixTree.playground/Contents.swift index e161e48a2..a5e614849 100644 --- a/Radix Tree/RadixTree.playground/Contents.swift +++ b/Radix Tree/RadixTree.playground/Contents.swift @@ -1,3 +1,10 @@ +// Radix Tree + +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + var radix = RadixTree() var radixWiki = RadixTree() diff --git a/Radix Tree/RadixTree.playground/Sources/RadixTree.swift b/Radix Tree/RadixTree.playground/Sources/RadixTree.swift index 1914f57bc..77e289f7d 100644 --- a/Radix Tree/RadixTree.playground/Sources/RadixTree.swift +++ b/Radix Tree/RadixTree.playground/Sources/RadixTree.swift @@ -181,10 +181,10 @@ public class RadixTree { else if shared == e.label { currEdge = e var tempIndex = searchStr.startIndex - for _ in 1...shared.characters.count { - tempIndex = searchStr.characters.index(after: tempIndex) + for _ in 1...shared.count { + tempIndex = searchStr.index(after: tempIndex) } - searchStr = searchStr.substring(from: tempIndex) + searchStr = String(searchStr[tempIndex...]) found = true break } @@ -192,18 +192,18 @@ public class RadixTree { // If the child's label and the search string share a partial prefix, // then both the label and the search string need to be substringed // and a new branch needs to be created - else if shared.characters.count > 0 { - var labelIndex = e.label.characters.startIndex + else if shared.count > 0 { + var labelIndex = e.label.startIndex // Create index objects and move them to after the shared prefix - for _ in 1...shared.characters.count { - index = searchStr.characters.index(after: index) - labelIndex = e.label.characters.index(after: labelIndex) + for _ in 1...shared.count { + index = searchStr.index(after: index) + labelIndex = e.label.index(after: labelIndex) } // Substring both the search string and the label from the shared prefix - searchStr = searchStr.substring(from: index) - e.label = e.label.substring(from: labelIndex) + searchStr = String(searchStr[index...]) + e.label = String(e.label[labelIndex...]) // Create 2 new edges and update parent/children values let newEdge = Edge(e.label) @@ -266,16 +266,16 @@ public class RadixTree { if shared == c.label { currEdge = c var tempIndex = searchStr.startIndex - for _ in 1...shared.characters.count { - tempIndex = searchStr.characters.index(after: tempIndex) + for _ in 1...shared.count { + tempIndex = searchStr.index(after: tempIndex) } - searchStr = searchStr.substring(from: tempIndex) + searchStr = String(searchStr[tempIndex...]) found = true break } // If the shared string is empty, go to the next child - else if shared.characters.count == 0 { + else if shared.count == 0 { continue } @@ -287,7 +287,7 @@ public class RadixTree { // If the search string and the child's label only share some characters, // the string is not in the tree, return false else if shared[shared.startIndex] == c.label[c.label.startIndex] && - shared.characters.count < c.label.characters.count { + shared.count < c.label.count { return false } } @@ -340,10 +340,10 @@ public class RadixTree { if shared == currEdge.children[c].label { currEdge = currEdge.children[c] var tempIndex = searchStr.startIndex - for _ in 1...shared.characters.count { - tempIndex = searchStr.characters.index(after: tempIndex) + for _ in 1...shared.count { + tempIndex = searchStr.index(after: tempIndex) } - searchStr = searchStr.substring(from: tempIndex) + searchStr = String(searchStr[tempIndex...]) found = true break } @@ -366,13 +366,13 @@ public class RadixTree { // i.e. sharedPrefix("court", "coral") -> "co" public func sharedPrefix(_ str1: String, _ str2: String) -> String { var temp = "" - var c1 = str1.characters.startIndex - var c2 = str2.characters.startIndex - for _ in 0...min(str1.characters.count-1, str2.characters.count-1) { + var c1 = str1.startIndex + var c2 = str2.startIndex + for _ in 0...min(str1.count-1, str2.count-1) { if str1[c1] == str2[c2] { temp.append( str1[c1] ) - c1 = str1.characters.index(after:c1) - c2 = str2.characters.index(after:c2) + c1 = str1.index(after:c1) + c2 = str2.index(after:c2) } else { return temp } diff --git a/Radix Tree/RadixTree.swift b/Radix Tree/RadixTree.swift index 958ffb4e6..943000dd3 100644 --- a/Radix Tree/RadixTree.swift +++ b/Radix Tree/RadixTree.swift @@ -181,10 +181,10 @@ public class RadixTree { else if shared == e.label { currEdge = e var tempIndex = searchStr.startIndex - for _ in 1...shared.characters.count { - tempIndex = searchStr.characters.index(after: tempIndex) + for _ in 1...shared.count { + tempIndex = searchStr.index(after: tempIndex) } - searchStr = searchStr.substring(from: tempIndex) + searchStr = String(searchStr[tempIndex...]) found = true break } @@ -192,18 +192,18 @@ public class RadixTree { // If the child's label and the search string share a partial prefix, // then both the label and the search string need to be substringed // and a new branch needs to be created - else if shared.characters.count > 0 { - var labelIndex = e.label.characters.startIndex + else if shared.count > 0 { + var labelIndex = e.label.startIndex // Create index objects and move them to after the shared prefix - for _ in 1...shared.characters.count { - index = searchStr.characters.index(after: index) - labelIndex = e.label.characters.index(after: labelIndex) + for _ in 1...shared.count { + index = searchStr.index(after: index) + labelIndex = e.label.index(after: labelIndex) } // Substring both the search string and the label from the shared prefix - searchStr = searchStr.substring(from: index) - e.label = e.label.substring(from: labelIndex) + searchStr = String(searchStr[index...]) + e.label = String(e.label[labelIndex...]) // Create 2 new edges and update parent/children values let newEdge = Edge(e.label) @@ -266,16 +266,16 @@ public class RadixTree { if shared == c.label { currEdge = c var tempIndex = searchStr.startIndex - for _ in 1...shared.characters.count { - tempIndex = searchStr.characters.index(after: tempIndex) + for _ in 1...shared.count { + tempIndex = searchStr.index(after: tempIndex) } - searchStr = searchStr.substring(from: tempIndex) + searchStr = String(searchStr[tempIndex...]) found = true break } // If the shared string is empty, go to the next child - else if shared.characters.count == 0 { + else if shared.count == 0 { continue } @@ -287,7 +287,7 @@ public class RadixTree { // If the search string and the child's label only share some characters, // the string is not in the tree, return false else if shared[shared.startIndex] == c.label[c.label.startIndex] && - shared.characters.count < c.label.characters.count { + shared.count < c.label.count { return false } } @@ -340,10 +340,10 @@ public class RadixTree { if shared == currEdge.children[c].label { currEdge = currEdge.children[c] var tempIndex = searchStr.startIndex - for _ in 1...shared.characters.count { - tempIndex = searchStr.characters.index(after: tempIndex) + for _ in 1...shared.count { + tempIndex = searchStr.index(after: tempIndex) } - searchStr = searchStr.substring(from: tempIndex) + searchStr = String(searchStr[tempIndex...]) found = true break } @@ -366,13 +366,13 @@ public class RadixTree { // i.e. sharedPrefix("court", "coral") -> "co" public func sharedPrefix(_ str1: String, _ str2: String) -> String { var temp = "" - var c1 = str1.characters.startIndex - var c2 = str2.characters.startIndex - for _ in 0...min(str1.characters.count-1, str2.characters.count-1) { + var c1 = str1.startIndex + var c2 = str2.startIndex + for _ in 0...min(str1.count-1, str2.count-1) { if str1[c1] == str2[c2] { temp.append( str1[c1] ) - c1 = str1.characters.index(after:c1) - c2 = str2.characters.index(after:c2) + c1 = str1.index(after:c1) + c2 = str2.index(after:c2) } else { return temp } diff --git a/Red-Black Tree/README.markdown b/Red-Black Tree/README.markdown index 74fffa415..2108b9055 100644 --- a/Red-Black Tree/README.markdown +++ b/Red-Black Tree/README.markdown @@ -29,6 +29,7 @@ From [CLRS] ## Methods Nodes: +* `nodeX.getPredecessor()` Returns the inorder predecessor of nodeX * `nodeX.getSuccessor()` Returns the inorder successor of nodeX * `nodeX.minimum()` Returns the node with the minimum key of the subtree of nodeX * `nodeX.maximum()` Returns the node with the maximum key of the subtree of nodeX @@ -39,6 +40,9 @@ Tree: * `insert(key:)` Inserts the key value into the tree * `delete(key:)` Delete the node with the respective key value from the tree * `verify()` Verifies that the given tree fulfills the red-black tree properties +* `count()` Returns how many nodes in the tree +* `isEmpty()` Returns if the tree has no nodes +* `allElements()` Returns an array containing all nodes (in-order traversal) in the tree. The rotation, insertion and deletion algorithms are implemented based on the pseudo-code provided in [CLRS] @@ -186,4 +190,4 @@ The overall runtime of delete is O(log n). ## Resources: [CLRS] T. Cormen, C. Leiserson, R. Rivest, and C. Stein. "Introduction to Algorithms", Third Edition. 2009 -*Written for Swift Algorithm Club by Ute Schiehlen. Updated from Jaap Wijnen and Ashwin Raghuraman's contributions.* +*Written for Swift Algorithm Club by Ute Schiehlen. Updated from Jaap Wijnen and Ashwin Raghuraman's contributions. Swift 4.2 check by Bruno Scheele.* diff --git a/Red-Black Tree/RedBlackTree.playground/Contents.swift b/Red-Black Tree/RedBlackTree.playground/Contents.swift index 50d61167d..d1133bc98 100644 --- a/Red-Black Tree/RedBlackTree.playground/Contents.swift +++ b/Red-Black Tree/RedBlackTree.playground/Contents.swift @@ -27,4 +27,4 @@ for i in 0..<1000 { redBlackTree.verify() } } -redBlackTree.verify() \ No newline at end of file +redBlackTree.verify() diff --git a/Red-Black Tree/RedBlackTree.playground/Sources/RedBlackTree.swift b/Red-Black Tree/RedBlackTree.playground/Sources/RedBlackTree.swift index 59633031d..9b067de55 100644 --- a/Red-Black Tree/RedBlackTree.playground/Sources/RedBlackTree.swift +++ b/Red-Black Tree/RedBlackTree.playground/Sources/RedBlackTree.swift @@ -21,106 +21,150 @@ import Foundation private enum RBTreeColor { - case red - case black + case red + case black } private enum RotationDirection { - case left - case right + 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 + 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.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Red-Black Tree/RedBlackTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Red-Black Tree/RedBlackTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + 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/Ring Buffer/RingBuffer.playground/Contents.swift b/Ring Buffer/RingBuffer.playground/Contents.swift index 29b9917a0..adcf146b4 100644 --- a/Ring Buffer/RingBuffer.playground/Contents.swift +++ b/Ring Buffer/RingBuffer.playground/Contents.swift @@ -9,24 +9,25 @@ public struct RingBuffer { array = [T?](repeating: nil, count: count) } + /* Returns false if out of space. */ + @discardableResult public mutating func write(_ element: T) -> Bool { - if !isFull { - array[writeIndex % array.count] = element - writeIndex += 1 - return true - } else { - return false + guard !isFull else { return false } + defer { + writeIndex += 1 } + array[wrapped: writeIndex] = element + return true } - + + /* Returns nil if the buffer is empty. */ public mutating func read() -> T? { - if !isEmpty { - let element = array[readIndex % array.count] - readIndex += 1 - return element - } else { - return nil + guard !isEmpty else { return nil } + defer { + array[wrapped: readIndex] = nil + readIndex += 1 } + return array[wrapped: readIndex] } fileprivate var availableSpaceForReading: Int { @@ -46,6 +47,30 @@ public struct RingBuffer { } } +extension RingBuffer: Sequence { + public func makeIterator() -> AnyIterator { + var index = readIndex + return AnyIterator { + guard index < self.writeIndex else { return nil } + defer { + index += 1 + } + return self.array[wrapped: index] + } + } +} + +private extension Array { + subscript (wrapped index: Int) -> Element { + get { + return self[index % count] + } + set { + self[index % count] = newValue + } + } +} + var buffer = RingBuffer(count: 5) buffer.array // [nil, nil, nil, nil, nil] buffer.readIndex // 0 diff --git a/Ring Buffer/RingBuffer.swift b/Ring Buffer/RingBuffer.swift index 7e845aa74..0006afea6 100644 --- a/Ring Buffer/RingBuffer.swift +++ b/Ring Buffer/RingBuffer.swift @@ -16,29 +16,28 @@ public struct RingBuffer { private var writeIndex = 0 public init(count: Int) { - array = [T?](count: count, repeatedValue: nil) + array = [T?](repeating: nil, count: count) } /* Returns false if out of space. */ - public mutating func write(element: T) -> Bool { - if !isFull { - array[writeIndex % array.count] = element - writeIndex += 1 - return true - } else { - return false + @discardableResult + public mutating func write(_ element: T) -> Bool { + guard !isFull else { return false } + defer { + writeIndex += 1 } + array[wrapped: writeIndex] = element + return true } /* Returns nil if the buffer is empty. */ public mutating func read() -> T? { - if !isEmpty { - let element = array[readIndex % array.count] - readIndex += 1 - return element - } else { - return nil + guard !isEmpty else { return nil } + defer { + array[wrapped: readIndex] = nil + readIndex += 1 } + return array[wrapped: readIndex] } private var availableSpaceForReading: Int { @@ -57,3 +56,27 @@ public struct RingBuffer { return availableSpaceForWriting == 0 } } + +public extension RingBuffer: Sequence { + public func makeIterator() -> AnyIterator { + var index = readIndex + return AnyIterator { + guard index < self.writeIndex else { return nil } + defer { + index += 1 + } + return self.array[wrapped: index] + } + } +} + +private extension Array { + subscript (wrapped index: Int) -> Element { + get { + return self[index % count] + } + set { + self[index % count] = newValue + } + } +} diff --git a/Rootish Array Stack/RootishArrayStack.playground/Contents.swift b/Rootish Array Stack/RootishArrayStack.playground/Contents.swift index 8b630528d..ade32a3e7 100644 --- a/Rootish Array Stack/RootishArrayStack.playground/Contents.swift +++ b/Rootish Array Stack/RootishArrayStack.playground/Contents.swift @@ -1,5 +1,10 @@ //: Playground - noun: a place where people can play +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + import Darwin public struct RootishArrayStack { diff --git a/Rootish Array Stack/Tests/RootishArrayStackTests.swift b/Rootish Array Stack/Tests/RootishArrayStackTests.swift index 9d5972a41..fc045faf4 100755 --- a/Rootish Array Stack/Tests/RootishArrayStackTests.swift +++ b/Rootish Array Stack/Tests/RootishArrayStackTests.swift @@ -205,4 +205,11 @@ class RootishArrayStackTests: XCTestCase { XCTAssertEqual(list.first, 0) XCTAssertTrue(list.equal(toArray: array)) } + + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } } diff --git a/Rootish Array Stack/Tests/Tests.xcodeproj/project.pbxproj b/Rootish Array Stack/Tests/Tests.xcodeproj/project.pbxproj index cdfd35f71..bbc3967e5 100644 --- a/Rootish Array Stack/Tests/Tests.xcodeproj/project.pbxproj +++ b/Rootish Array Stack/Tests/Tests.xcodeproj/project.pbxproj @@ -226,7 +226,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 = 4.0; }; name = Debug; }; @@ -238,7 +238,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 = 4.0; }; name = Release; }; diff --git a/Run-Length Encoding/README.markdown b/Run-Length Encoding/README.markdown index f7d196161..b29a49dd1 100644 --- a/Run-Length Encoding/README.markdown +++ b/Run-Length Encoding/README.markdown @@ -51,6 +51,7 @@ extension Data { } return data } + } ``` How it works: diff --git a/Run-Length Encoding/RLE.playground/Contents.swift b/Run-Length Encoding/RLE.playground/Contents.swift index 26f7dca48..8d6a0a403 100644 --- a/Run-Length Encoding/RLE.playground/Contents.swift +++ b/Run-Length Encoding/RLE.playground/Contents.swift @@ -59,7 +59,7 @@ func testBufferWithoutSpans() -> Bool { // data ends up being longer. var bytes: [UInt8] = [] for i in 0..<1024 { - bytes.append(UInt8(i%256)) + bytes.append(UInt8(i % 256)) } return encodeAndDecode(bytes) } diff --git a/Run-Length Encoding/RLE.playground/timeline.xctimeline b/Run-Length Encoding/RLE.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Run-Length Encoding/RLE.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Segment Tree/LazyPropagation/Images/Segment-tree.png b/Segment Tree/LazyPropagation/Images/Segment-tree.png new file mode 100644 index 000000000..283bcca5d Binary files /dev/null and b/Segment Tree/LazyPropagation/Images/Segment-tree.png differ diff --git a/Segment Tree/LazyPropagation/Images/lazy-sample-2.png b/Segment Tree/LazyPropagation/Images/lazy-sample-2.png new file mode 100644 index 000000000..f8105fbc6 Binary files /dev/null and b/Segment Tree/LazyPropagation/Images/lazy-sample-2.png differ diff --git a/Segment Tree/LazyPropagation/Images/pushUp.png b/Segment Tree/LazyPropagation/Images/pushUp.png new file mode 100644 index 000000000..ae868037f Binary files /dev/null and b/Segment Tree/LazyPropagation/Images/pushUp.png differ diff --git a/Segment Tree/LazyPropagation/Images/pushdown.png b/Segment Tree/LazyPropagation/Images/pushdown.png new file mode 100644 index 000000000..0c763c782 Binary files /dev/null and b/Segment Tree/LazyPropagation/Images/pushdown.png differ diff --git a/Segment Tree/LazyPropagation/LazyPropagation.playground/Contents.swift b/Segment Tree/LazyPropagation/LazyPropagation.playground/Contents.swift new file mode 100644 index 000000000..38dd51859 --- /dev/null +++ b/Segment Tree/LazyPropagation/LazyPropagation.playground/Contents.swift @@ -0,0 +1,129 @@ +public class LazySegmentTree { + + private var value: Int + + private var leftBound: Int + + private var rightBound: Int + + private var leftChild: LazySegmentTree? + + private var rightChild: LazySegmentTree? + + // Interval Update Lazy Element + private var lazyValue: Int + + // MARK: - Push Up Operation + // Description: pushUp() - update items to the top + private func pushUp(lson: LazySegmentTree, rson: LazySegmentTree) { + self.value = lson.value + rson.value + } + + // MARK: - Push Down Operation + // Description: pushDown() - update items to the bottom + private func pushDown(round: Int, lson: LazySegmentTree, rson: LazySegmentTree) { + guard lazyValue != 0 else { return } + lson.lazyValue += lazyValue + rson.lazyValue += lazyValue + lson.value += lazyValue * (round - (round >> 1)) + rson.value += lazyValue * (round >> 1) + lazyValue = 0 + } + + public init(array: [Int], leftBound: Int, rightBound: Int) { + self.leftBound = leftBound + self.rightBound = rightBound + self.value = 0 + self.lazyValue = 0 + + guard leftBound != rightBound else { + value = array[leftBound] + return + } + + let middle = leftBound + (rightBound - leftBound) / 2 + leftChild = LazySegmentTree(array: array, leftBound: leftBound, rightBound: middle) + rightChild = LazySegmentTree(array: array, leftBound: middle + 1, rightBound: rightBound) + if let leftChild = leftChild, let rightChild = rightChild { + pushUp(lson: leftChild, rson: rightChild) + } + } + + public convenience init(array: [Int]) { + self.init(array: array, leftBound: 0, rightBound: array.count - 1) + } + + public func query(leftBound: Int, rightBound: Int) -> Int { + if leftBound <= self.leftBound && self.rightBound <= rightBound { + return value + } + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + + pushDown(round: self.rightBound - self.leftBound + 1, lson: leftChild, rson: rightChild) + + let middle = self.leftBound + (self.rightBound - self.leftBound) / 2 + var result = 0 + + if leftBound <= middle { result += leftChild.query(leftBound: leftBound, rightBound: rightBound) } + if rightBound > middle { result += rightChild.query(leftBound: leftBound, rightBound: rightBound) } + + return result + } + + // MARK: - One Item Update + public func update(index: Int, incremental: Int) { + guard self.leftBound != self.rightBound else { + self.value += incremental + return + } + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + + let middle = self.leftBound + (self.rightBound - self.leftBound) / 2 + + if index <= middle { leftChild.update(index: index, incremental: incremental) } + else { rightChild.update(index: index, incremental: incremental) } + pushUp(lson: leftChild, rson: rightChild) + } + + // MARK: - Interval Item Update + public func update(leftBound: Int, rightBound: Int, incremental: Int) { + if leftBound <= self.leftBound && self.rightBound <= rightBound { + self.lazyValue += incremental + self.value += incremental * (self.rightBound - self.leftBound + 1) + return + } + + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + + pushDown(round: self.rightBound - self.leftBound + 1, lson: leftChild, rson: rightChild) + + let middle = self.leftBound + (self.rightBound - self.leftBound) / 2 + + if leftBound <= middle { leftChild.update(leftBound: leftBound, rightBound: rightBound, incremental: incremental) } + if middle < rightBound { rightChild.update(leftBound: leftBound, rightBound: rightBound, incremental: incremental) } + + pushUp(lson: leftChild, rson: rightChild) + } + +} + +let array = [1, 2, 3, 4, 1, 3, 2] + +let sumSegmentTree = LazySegmentTree(array: array) + +print(sumSegmentTree.query(leftBound: 0, rightBound: 3)) // 10 = 1 + 2 + 3 + 4 +sumSegmentTree.update(index: 1, incremental: 2) +print(sumSegmentTree.query(leftBound: 0, rightBound: 3)) // 12 = 1 + 4 + 3 + 4 +sumSegmentTree.update(leftBound: 0, rightBound: 2, incremental: 2) +print(sumSegmentTree.query(leftBound: 0, rightBound: 3)) // 18 = 3 + 6 + 5 + 4 + +for index in 2 ... 5 { + sumSegmentTree.update(index: index, incremental: 3) +} + + +sumSegmentTree.update(leftBound: 0, rightBound: 5, incremental: 2) +print(sumSegmentTree.query(leftBound: 0, rightBound: 2)) \ No newline at end of file diff --git a/Segment Tree/LazyPropagation/LazyPropagation.playground/contents.xcplayground b/Segment Tree/LazyPropagation/LazyPropagation.playground/contents.xcplayground new file mode 100644 index 000000000..16f9952aa --- /dev/null +++ b/Segment Tree/LazyPropagation/LazyPropagation.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Segment Tree/LazyPropagation/LazyPropagation.playground/playground.xcworkspace/contents.xcworkspacedata b/Segment Tree/LazyPropagation/LazyPropagation.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Segment Tree/LazyPropagation/LazyPropagation.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Segment Tree/LazyPropagation/README.markdown b/Segment Tree/LazyPropagation/README.markdown new file mode 100644 index 000000000..c625a7dc8 --- /dev/null +++ b/Segment Tree/LazyPropagation/README.markdown @@ -0,0 +1,306 @@ +# Lazy Propagation in Segment Tree + +In previous implement about the Segment Tree by **Artur Antonov**, it's a strong data structure with *Generic* ``. And we can pass a closure parameter `function: (T, T) -> T` to reflect the relationship between parent and child node. And in particular, Generic can solve multiple strings stitching problem. It's just like the sample in Playground: + +```swift +stringSegmentTree.replaceItem(at: 0, withItem: "I") +stringSegmentTree.replaceItem(at: 1, withItem: " like") +stringSegmentTree.replaceItem(at: 2, withItem: " algorithms") +stringSegmentTree.replaceItem(at: 3, withItem: " and") +stringSegmentTree.replaceItem(at: 4, withItem: " swift") +stringSegmentTree.replaceItem(at: 5, withItem: "!") +print(stringSegmentTree.query(leftBound: 0, rightBound: 5)) +// "I like algorithms and swift!" +``` + +The use of `` is so exciting. But we seldom use the Segment Tree to solve string problem instead of *Suffix Array*. And the Segment Tree is a kind of *Interval Tree* to solve the Interval Problem in mathemtics and statistics, which is a structure for storing intervals, or segments, and allows querying which of the stored segments contain a given point. A segment tree for a set *I* of n intervals uses `O(nlogn)` storage and can be built in `O(nlogn)` time. Segment trees support searching for all the intervals that contain a query point in O(log n+k), k being the number of retrieved intervals or segments. + +But that is common Segment Tree. By **Lazy Propagation**, we can implement to modify an interval in `O(logn)` time. Let's explore together in following: + +## `PushUp` - update to the top + +At first, we reference the implement of **Artur Antonov** about Segment Tree. This code contained *build*, *single update* and *interval query* three operation. The implement of *build* and *single update* operation is following: + +```swift +// Author: Artur Antonov +public init(array: [T], leftBound: Int, rightBound: Int, function: @escaping (T, T) -> T) { + self.leftBound = leftBound + self.rightBound = rightBound + self.function = function + // ① + if leftBound == rightBound { + value = array[leftBound] + } + // ② + else { + let middle = (leftBound + rightBound) / 2 + leftChild = SegmentTree(array: array, leftBound: leftBound, rightBound: middle, function: function) + rightChild = SegmentTree(array: array, leftBound: middle+1, rightBound: rightBound, function: function) + value = function(leftChild!.value, rightChild!.value) + } +} +``` + +In position ①, it means the current node is *leaf* because its left bound data is equal to the right. So we assign a value to it directly. In position ②, it means the current node is *parent* (which has one or more children), and we need to recursion down and update current node's data in the follow-up process. + +And then, we have a look for *interval query* operation: + +```swift +// Author: Artur Antonov +public func query(leftBound: Int, rightBound: Int) -> T { + if self.leftBound == leftBound && self.rightBound == rightBound { + return self.value + } + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + // ① + if leftChild.rightBound < leftBound { + return rightChild.query(leftBound: leftBound, rightBound: rightBound) + } + // ② + else if rightChild.leftBound > rightBound { + return leftChild.query(leftBound: leftBound, rightBound: rightBound) + } + // ③ + else { + let leftResult = leftChild.query(leftBound: leftBound, rightBound: leftChild.rightBound) + let rightResult = rightChild.query(leftBound:rightChild.leftBound, rightBound: rightBound) + return function(leftResult, rightResult) + } +} +``` + +Position ① means that the left bound of current query interval is on the right of this right bound, so recurs to right direction. Position ② is opposite of position ①, recurs to left direction. Position ③ means our check interval is included the interval we need, so recurs deeply. + +![pushUp](Images/pushUp.png) + +There are common part from the two parts of code above - **recurs deeply below, and update data up**. So we can decouple this operation named `func pushUp(lson: LazySegmentTree, rson: LazySegmentTree)`: + +```swift +// MARK: - Push Up Operation +private func pushUp(lson: LazySegmentTree, rson: LazySegmentTree) { + self.value = lson.value + rson.value +} +``` + +(This code only describe the *Sum Segment Tree*) + +And then, we can update the implement before: + +```swift +public init(array: [Int], leftBound: Int, rightBound: Int) { + self.leftBound = leftBound + self.rightBound = rightBound + self.value = 0 + self.lazyValue = 0 + + guard leftBound != rightBound else { + value = array[leftBound] + return + } + + let middle = leftBound + (rightBound - leftBound) / 2 + leftChild = LazySegmentTree(array: array, leftBound: leftBound, rightBound: middle) + rightChild = LazySegmentTree(array: array, leftBound: middle + 1, rightBound: rightBound) + if let leftChild = leftChild, let rightChild = rightChild { + pushUp(lson: leftChild, rson: rightChild) + } +} +// MARK: - One Item Update +public func update(index: Int, incremental: Int) { + guard self.leftBound != self.rightBound else { + self.value += incremental + return + } + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + + let middle = self.leftBound + (self.rightBound - self.leftBound) / 2 + + if index <= middle { leftChild.update(index: index, incremental: incremental) } + else { rightChild.update(index: index, incremental: incremental) } + pushUp(lson: leftChild, rson: rightChild) +} +``` + +## `PushDown` - Lazy Propagation + +You may feel that the `pushUp` is so simple. In fact, this's just to lead `pushDown` this function. + +Before this, I want to talk about the topic about **interval operation**. The interval operation is a way to update all elements of a continuous subset. But these isn't in the version of **Artur Antonov**. You might disdain with this, because it's solved with a `for` loop: + +```swift +// Sample: update the elements with subscript [2, 5] +for index in 2 ... 5 { + sumSegmentTree.update(index: index, incremental: 3) +} +``` + +It is a `O(n)` time operation, which make the interval operation uses `O(nlogn)` time to update these elements. We need a `O(logn)` way to maintain the elegance of Segment Tree. + + Check the data structure of Segment Tree again: + + ![Segment-tree](Images/Segment-tree.png) + +We only catch the root node in programming. If we want to explore the bottom of the tree, and use `pushUp` to update every node, the task will be reached. So it asked us to traverse the tree, that spent `O(n)` time to do this with any way. This can't conform our expectations. + +Then we started to think about `pushDown` to update down from the root. **After we update the parent, the data continued to distributed to its children according to law.** But it still need `O(n)` time to do this. Keep thinking, we **only update the parent, and to update the children when `query` time**. Yeah, that's the key of **lazy propagation**. Because the recursing direct of the `query` and `update interval` is same. So we got it! 😁 Let's check this sample: + +![lazy-sample-2](Images/lazy-sample-2.png) + +`update` make the subscript 1...3 elements plus 2, so we make the 1st node in 2 depth and 3rd in 3 depth get a *lazy mark*, which means these node need to be updated. And we shouldn't add a *lazy mark* for root node, because it was updated before the `pushDown` in the first recursing. + +In `query` operation, we accord to the original method to recurs the tree, and find the 1st node held *lazy mark* in 2 depth, so to update it. It's the same situation about the 1st node in 3 depth. + +Do you understand the **lazy propagation**? **In short, we only update the wide range node data and add it a lazy mark. Then they will be update when we need to query them.** And the *Update Down* operation is the function of `pushDown`. + +This is the complete implementation about the Sum Segment Tree with interval update operation: + +```swift +public class LazySegmentTree { + + private var value: Int + + private var leftBound: Int + + private var rightBound: Int + + private var leftChild: LazySegmentTree? + + private var rightChild: LazySegmentTree? + + // Interval Update Lazy Element + private var lazyValue: Int + + // MARK: - Push Up Operation + // Description: pushUp() - update items to the top + private func pushUp(lson: LazySegmentTree, rson: LazySegmentTree) { + self.value = lson.value + rson.value + } + + // MARK: - Push Down Operation + // Description: pushDown() - update items to the bottom + private func pushDown(round: Int, lson: LazySegmentTree, rson: LazySegmentTree) { + guard lazyValue != 0 else { return } + lson.lazyValue += lazyValue + rson.lazyValue += lazyValue + lson.value += lazyValue * (round - (round >> 1)) + rson.value += lazyValue * (round >> 1) + lazyValue = 0 + } + + public init(array: [Int], leftBound: Int, rightBound: Int) { + self.leftBound = leftBound + self.rightBound = rightBound + self.value = 0 + self.lazyValue = 0 + + guard leftBound != rightBound else { + value = array[leftBound] + return + } + + let middle = leftBound + (rightBound - leftBound) / 2 + leftChild = LazySegmentTree(array: array, leftBound: leftBound, rightBound: middle) + rightChild = LazySegmentTree(array: array, leftBound: middle + 1, rightBound: rightBound) + if let leftChild = leftChild, let rightChild = rightChild { + pushUp(lson: leftChild, rson: rightChild) + } + } + + public convenience init(array: [Int]) { + self.init(array: array, leftBound: 0, rightBound: array.count - 1) + } + + public func query(leftBound: Int, rightBound: Int) -> Int { + if leftBound <= self.leftBound && self.rightBound <= rightBound { + return value + } + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + + pushDown(round: self.rightBound - self.leftBound + 1, lson: leftChild, rson: rightChild) + + let middle = self.leftBound + (self.rightBound - self.leftBound) / 2 + var result = 0 + + if leftBound <= middle { result += leftChild.query(leftBound: leftBound, rightBound: rightBound) } + if rightBound > middle { result += rightChild.query(leftBound: leftBound, rightBound: rightBound) } + + return result + } + + // MARK: - One Item Update + public func update(index: Int, incremental: Int) { + guard self.leftBound != self.rightBound else { + self.value += incremental + return + } + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + + let middle = self.leftBound + (self.rightBound - self.leftBound) / 2 + + if index <= middle { leftChild.update(index: index, incremental: incremental) } + else { rightChild.update(index: index, incremental: incremental) } + pushUp(lson: leftChild, rson: rightChild) + } + + // MARK: - Interval Item Update + public func update(leftBound: Int, rightBound: Int, incremental: Int) { + if leftBound <= self.leftBound && self.rightBound <= rightBound { + self.lazyValue += incremental + self.value += incremental * (self.rightBound - self.leftBound + 1) + return + } + + guard let leftChild = leftChild else { fatalError("leftChild should not be nil") } + guard let rightChild = rightChild else { fatalError("rightChild should not be nil") } + + pushDown(round: self.rightBound - self.leftBound + 1, lson: leftChild, rson: rightChild) + + let middle = self.leftBound + (self.rightBound - self.leftBound) / 2 + + if leftBound <= middle { leftChild.update(leftBound: leftBound, rightBound: rightBound, incremental: incremental) } + if middle < rightBound { rightChild.update(leftBound: leftBound, rightBound: rightBound, incremental: incremental) } + + pushUp(lson: leftChild, rson: rightChild) + } + +} +``` + +Explain some sample snippets: + +```swift +private var lazyValue: Int +``` + +Here we add a new property for Segment Tree to represent *lazy mark*. And it is a incremental value for Sum Segment Tree. If the `lazyValue` isn't equal to zero, the current node need to be updated. And its real value is equal to `value + lazyValue * (rightBound - leftBound + 1)`. + +```swift + // MARK: - Push Down Operation + // Description: pushDown() - update items to the bottom +private func pushDown(round: Int, lson: LazySegmentTree, rson: LazySegmentTree) { + guard lazyValue != 0 else { return } + lson.lazyValue += lazyValue + rson.lazyValue += lazyValue + lson.value += lazyValue * (round - (round >> 1)) + rson.value += lazyValue * (round >> 1) + lazyValue = 0 +} +``` + +![pushdown](Images/pushdown.png) + +At first we check whether the node needs to be updated. If the `lazyValue` isn't equal to zero, we need to `pushDown`. And the update rules are the `lazyValue * (rightBound - leftBound + 1)`. At last, reset the `lazyValue` to zero. + +## Conclusion + +This is the introduce of the Lazy Propagation in Segment Tree. You can also learn **Functional Segment Tree** to understand the Lazy Propagation deeply. In addition, I learn from the *notonlysuccess*'s code about Segment Tree described by C, this is the [link](http://www.cnblogs.com/Destiny-Gem/articles/3875243.html). + +In fact, the operation of Segment Tree is far more than that. It can also be used to handle problems between collections. I want to implement it with Swift in the future and make the Swift Segment Tree stronger and stronger. 😁 + +--- + +*Written for Swift Algorithm Club by [Desgard_Duan](https://github.com/desgard)* diff --git a/Segment Tree/README.markdown b/Segment Tree/README.markdown index 9897c9c3b..99ec85cd8 100644 --- a/Segment Tree/README.markdown +++ b/Segment Tree/README.markdown @@ -1,5 +1,7 @@ # Segment Tree +> For an example on lazy propagation, see this [article](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Segment%20Tree/LazyPropagation). + I'm pleased to present to you Segment Tree. It's actually one of my favorite data structures because it's very flexible and simple in realization. Let's suppose that you have an array **a** of some type and some associative function **f**. For example, the function can be sum, multiplication, min, max, [gcd](../GCD/), and so on. @@ -199,6 +201,8 @@ See the playground for more examples of how to use the segment tree. ## See also +[Lazy Propagation](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Segment%20Tree/LazyPropagation) implementation and explanation. + [Segment tree at PEGWiki](http://wcipeg.com/wiki/Segment_tree) *Written for Swift Algorithm Club by [Artur Antonov](https://github.com/goingreen)* diff --git a/Segment Tree/SegmentTree.playground/Contents.swift b/Segment Tree/SegmentTree.playground/Contents.swift index 3a758981f..25116c285 100644 --- a/Segment Tree/SegmentTree.playground/Contents.swift +++ b/Segment Tree/SegmentTree.playground/Contents.swift @@ -1,5 +1,10 @@ //: Playground - noun: a place where people can play +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + public class SegmentTree { private var value: T diff --git a/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift b/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift index c30bf2fa4..e1101a864 100644 --- a/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift +++ b/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift @@ -1,3 +1,8 @@ +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + // Compare each item to find minimum func minimum(_ array: [T]) -> T? { guard var minimum = array.first else { diff --git a/Selection Sampling/README.markdown b/Selection Sampling/README.markdown index bf17d93bd..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 requested: Int) -> [T] { var b = [T]() while selected < requested { // 1 - examined += 1 - let r = Double(arc4random()) / 0x100000000 // 2 - let leftToExamine = a.count - examined + 1 // 3 + let leftToExamine = a.count - examined // 3 let leftToAdd = requested - selected if Double(leftToExamine) * r < Double(leftToAdd) { // 4 selected += 1 - b.append(a[examined - 1]) + b.append(a[examined]) } + + examined += 1 } return b } @@ -193,7 +193,7 @@ print(output.count) The performance of this second algorithm is **O(n)** as it may require a pass through the entire input array. -> **Note:** If `k > n/2`, then it's more efficient to do it the other way around and choose `k` items to remove. +> **Note:** If `k > n/2`, then it's more efficient to do it the other way around and choose `a.count - k` items to remove. Based on code from Algorithm Alley, Dr. Dobb's Magazine, October 1993. diff --git a/Selection Sampling/SelectionSampling.playground/Contents.swift b/Selection Sampling/SelectionSampling.playground/Contents.swift index 4a2484162..7443db67c 100644 --- a/Selection Sampling/SelectionSampling.playground/Contents.swift +++ b/Selection Sampling/SelectionSampling.playground/Contents.swift @@ -1,5 +1,10 @@ //: Playground - noun: a place where people can play +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + import Foundation /* Returns a random integer in the range min...max, inclusive. */ @@ -27,19 +32,19 @@ func select(from a: [T], count requested: Int) -> [T] { var b = [T]() while selected < requested { - examined += 1 - // Calculate random variable 0.0 <= r < 1.0 (exclusive!). let r = Double(arc4random()) / 0x100000000 - let leftToExamine = a.count - examined + 1 + let leftToExamine = a.count - examined let leftToAdd = requested - selected // Decide whether to use the next record from the input. if Double(leftToExamine) * r < Double(leftToAdd) { selected += 1 - b.append(a[examined - 1]) + b.append(a[examined]) } + + examined += 1 } return b } diff --git a/Selection Sampling/SelectionSampling.swift b/Selection Sampling/SelectionSampling.swift index 1e6257908..711460f47 100644 --- a/Selection Sampling/SelectionSampling.swift +++ b/Selection Sampling/SelectionSampling.swift @@ -17,7 +17,7 @@ func select(from a: [T], count k: Int) -> [T] { for i in 0..(from a: [T], count requested: Int) -> [T] { var b = [T]() while selected < requested { - examined += 1 - // Calculate random variable 0.0 <= r < 1.0 (exclusive!). let r = Double(arc4random()) / 0x100000000 - let leftToExamine = a.count - examined + 1 + let leftToExamine = a.count - examined let leftToAdd = requested - selected // Decide whether to use the next record from the input. if Double(leftToExamine) * r < Double(leftToAdd) { selected += 1 - b.append(a[examined - 1]) + b.append(a[examined]) } + + examined += 1 } return b } diff --git a/Selection Sort/README.markdown b/Selection Sort/README.markdown index daebc64f4..b958da927 100644 --- a/Selection Sort/README.markdown +++ b/Selection Sort/README.markdown @@ -19,7 +19,7 @@ It works as follows: - Find the lowest number in the rest of the array, starting from index 2, and swap it with the one at index 2. Now, the array is sorted from index 0 to 2; this range contains the three lowest numbers in the array. - And continue until no numbers remain to be sorted. -It nis called a "selection" sort because at every step you search through the rest of the array to select the next lowest number. +It is called a "selection" sort because at every step you search through the rest of the array to select the next lowest number. ## An example @@ -74,7 +74,7 @@ func selectionSort(_ array: [Int]) -> [Int] { } if x != lowest { // 5 - swap(&a[x], &a[lowest]) + a.swapAt(x, lowest) } } return a @@ -108,7 +108,7 @@ The source file [SelectionSort.swift](SelectionSort.swift) has a version of this ## Performance -The selection sort is easy to understand but it performs slow as **O(n^2)**. It is worse than [insertion sort](../Insertion%20Sort/) but better than [bubble sort](../Bubble%20Sort/). Finding the lowest element in the rest of the array is slow, especially since the inner loop will be performed repeatedly . +The selection sort is easy to understand but it performs slow as **O(n^2)**. It is worse than [insertion sort](../Insertion%20Sort/) but better than [bubble sort](../Bubble%20Sort/). Finding the lowest element in the rest of the array is slow, especially since the inner loop will be performed repeatedly. The [Heap sort](../Heap%20Sort/) uses the same principle as selection sort but has a fast method for finding the minimum value in the rest of the array. The heap sort' performance is **O(n log n)**. diff --git a/Selection Sort/SelectionSort.playground/Contents.swift b/Selection Sort/SelectionSort.playground/Contents.swift index bd8c7cccf..a552c9edb 100644 --- a/Selection Sort/SelectionSort.playground/Contents.swift +++ b/Selection Sort/SelectionSort.playground/Contents.swift @@ -1,22 +1,6 @@ //: Playground - noun: a place where people can play -func selectionSort(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { - guard array.count > 1 else { return array } - var a = array - for x in 0 ..< a.count - 1 { - var lowest = x - for y in x + 1 ..< a.count { - if isOrderedBefore(a[y], a[lowest]) { - lowest = y - } - } - if x != lowest { - swap(&a[x], &a[lowest]) - } - } - return a -} - let list = [ 10, -1, 3, 9, 2, 27, 8, 5, 1, 3, 0, 26 ] +selectionSort(list) selectionSort(list, <) selectionSort(list, >) diff --git a/Selection Sort/SelectionSort.playground/Sources/SelectionSort.swift b/Selection Sort/SelectionSort.playground/Sources/SelectionSort.swift new file mode 100644 index 000000000..e157319b4 --- /dev/null +++ b/Selection Sort/SelectionSort.playground/Sources/SelectionSort.swift @@ -0,0 +1,35 @@ +/// Performs the Selection sort algorithm on a array +/// +/// - Parameter array: array of elements that conform to the Comparable protocol +/// - Returns: an array in ascending order +public func selectionSort(_ array: [T]) -> [T] { + return selectionSort(array, <) +} + +/// Performs the Selection sort algorithm on a array using the provided comparisson method +/// +/// - Parameters: +/// - array: array of elements that conform to the Comparable protocol +/// - isLowerThan: returns true if the two provided elements are in the correct order +/// - Returns: a sorted array +public func selectionSort(_ array: [T], _ isLowerThan: (T, T) -> Bool) -> [T] { + guard array.count > 1 else { return array } + + var a = array + for x in 0 ..< a.count - 1 { + + // Find the lowest value in the rest of the array. + var lowest = x + for y in x + 1 ..< a.count { + if isLowerThan(a[y], a[lowest]) { + lowest = y + } + } + + // Swap the lowest value with the current array index. + if x != lowest { + a.swapAt(x, lowest) + } + } + return a +} diff --git a/Selection Sort/SelectionSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Selection Sort/SelectionSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Selection Sort/SelectionSort.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Selection Sort/SelectionSort.playground/timeline.xctimeline b/Selection Sort/SelectionSort.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Selection Sort/SelectionSort.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Selection Sort/SelectionSort.swift b/Selection Sort/SelectionSort.swift index e02c87259..943ae628d 100644 --- a/Selection Sort/SelectionSort.swift +++ b/Selection Sort/SelectionSort.swift @@ -1,21 +1,21 @@ -func selectionSort(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { - guard array.count > 1 else { return array } +public func selectionSort(_ array: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { + guard array.count > 1 else { return array } - var a = array - for x in 0 ..< a.count - 1 { + var a = array + for x in 0 ..< a.count - 1 { - // Find the lowest value in the rest of the array. - var lowest = x - for y in x + 1 ..< a.count { - if isOrderedBefore(a[y], a[lowest]) { - lowest = y - } - } + // Find the lowest value in the rest of the array. + var lowest = x + for y in x + 1 ..< a.count { + if isOrderedBefore(a[y], a[lowest]) { + lowest = y + } + } - // Swap the lowest value with the current array index. - if x != lowest { - swap(&a[x], &a[lowest]) + // Swap the lowest value with the current array index. + if x != lowest { + a.swapAt(x, lowest) + } } - } - return a + return a } diff --git a/Set Cover (Unweighted)/SetCover.playground/Contents.swift b/Set Cover (Unweighted)/SetCover.playground/Contents.swift index dcf998a66..065f5fd4d 100644 --- a/Set Cover (Unweighted)/SetCover.playground/Contents.swift +++ b/Set Cover (Unweighted)/SetCover.playground/Contents.swift @@ -1,3 +1,5 @@ +// SetCover + let universe1 = Set(1...7) let array1 = randomArrayOfSets(covering: universe1) let cover1 = universe1.cover(within: array1) @@ -29,3 +31,5 @@ let emptySet = Set() let coverTest1 = emptySet.cover(within: array1) let coverTest2 = universe1.cover(within: Array>()) let coverTest3 = emptySet.cover(within: Array>()) + + diff --git a/Set Cover (Unweighted)/SetCover.playground/Sources/RandomArrayOfSets.swift b/Set Cover (Unweighted)/SetCover.playground/Sources/RandomArrayOfSets.swift index 9454c6383..ac4ff063e 100644 --- a/Set Cover (Unweighted)/SetCover.playground/Sources/RandomArrayOfSets.swift +++ b/Set Cover (Unweighted)/SetCover.playground/Sources/RandomArrayOfSets.swift @@ -14,10 +14,10 @@ public func randomArrayOfSets(covering universe: Set, while true { var generatedSet = Set() - let targetSetSize = Int(arc4random_uniform(UInt32(maxSetSize)) + 1) + let targetSetSize = Int.random(in: 0...maxSetSize) + 1 while true { - let randomUniverseIndex = Int(arc4random_uniform(UInt32(universe.count))) + let randomUniverseIndex = Int.random(in: 0...universe.count) for (setIndex, value) in universe.enumerated() { if setIndex == randomUniverseIndex { generatedSet.insert(value) diff --git a/Shell Sort/README.markdown b/Shell Sort/README.markdown index c21c29685..72375e51b 100644 --- a/Shell Sort/README.markdown +++ b/Shell Sort/README.markdown @@ -117,24 +117,10 @@ Here is an implementation of Shell Sort in Swift: var arr = [64, 20, 50, 33, 72, 10, 23, -1, 4, 5] public func shellSort(_ list: inout [Int]) { - var sublistCount = list.count / 2 - while sublistCount > 0 { - - for index in 0.. list[index + sublistCount] { - swap(&list[index], &list[index + sublistCount]) - } - - guard sublistCount == 1 && index > 0 else { continue } - - if list[index - 1] > list[index] { - swap(&list[index - 1], &list[index]) - } + for pos in 0..=4.0) + print("Hello, Swift 4!") +#endif + +public func insertionSort(_ list: inout [Int], start: Int, gap: Int) { + for i in stride(from: (start + gap), to: list.count, by: gap) { + let currentValue = list[i] + var pos = i + while pos >= gap && list[pos - gap] > currentValue { + list[pos] = list[pos - gap] + pos -= gap + } + list[pos] = currentValue + } +} + +public func shellSort(_ list: inout [Int]) { + var sublistCount = list.count / 2 + while sublistCount > 0 { + for pos in 0.. + + + \ No newline at end of file diff --git a/Shell Sort/Tests/ShellSortTests.swift b/Shell Sort/Tests/ShellSortTests.swift index f9404b781..494d6841e 100644 --- a/Shell Sort/Tests/ShellSortTests.swift +++ b/Shell Sort/Tests/ShellSortTests.swift @@ -1,6 +1,13 @@ import XCTest class ShellSortTests: XCTestCase { + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + func testShellSort() { checkSortAlgorithm { (a: [Int]) -> [Int] in var b = a diff --git a/Shell Sort/Tests/Tests.xcodeproj/project.pbxproj b/Shell Sort/Tests/Tests.xcodeproj/project.pbxproj index 1de1c7213..c2966af46 100644 --- a/Shell Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Shell Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -178,6 +178,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -214,6 +215,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -225,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 = 4.0; }; name = Debug; }; @@ -237,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 = 4.0; }; name = Release; }; diff --git a/Shortest Path (Unweighted)/ShortestPath.playground/Pages/Shortest path example.xcplaygroundpage/Contents.swift b/Shortest Path (Unweighted)/ShortestPath.playground/Pages/Shortest path example.xcplaygroundpage/Contents.swift index 9f2eda4f3..2404f5b8c 100644 --- a/Shortest Path (Unweighted)/ShortestPath.playground/Pages/Shortest path example.xcplaygroundpage/Contents.swift +++ b/Shortest Path (Unweighted)/ShortestPath.playground/Pages/Shortest path example.xcplaygroundpage/Contents.swift @@ -1,3 +1,8 @@ +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + func breadthFirstSearchShortestPath(graph: Graph, source: Node) -> Graph { let shortestPathGraph = graph.duplicate() diff --git a/Shortest Path (Unweighted)/Tests/ShortestPathTests.swift b/Shortest Path (Unweighted)/Tests/ShortestPathTests.swift index 71b9f88ab..4d79eeaa7 100644 --- a/Shortest Path (Unweighted)/Tests/ShortestPathTests.swift +++ b/Shortest Path (Unweighted)/Tests/ShortestPathTests.swift @@ -1,6 +1,13 @@ import XCTest class ShortestPathTests: XCTestCase { + + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } func testShortestPathWhenGivenTree() { let tree = Graph() diff --git a/Shortest Path (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj b/Shortest Path (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj index f1744fcf1..b601a1665 100644 --- a/Shortest Path (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj +++ b/Shortest Path (Unweighted)/Tests/Tests.xcodeproj/project.pbxproj @@ -182,6 +182,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -218,6 +219,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -231,7 +233,7 @@ PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -244,7 +246,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 = 4.0; }; name = Release; }; diff --git a/Shuffle/README.markdown b/Shuffle/README.markdown index ced138572..d9c35868a 100644 --- a/Shuffle/README.markdown +++ b/Shuffle/README.markdown @@ -33,19 +33,17 @@ You should see three different arrangements -- or [permutations](../Combinatoric This shuffle works *in place*, it modifies the contents of the original array. The algorithm works by creating a new array, `temp`, that is initially empty. Then we randomly choose an element from the original array and append it to `temp`, until the original array is empty. Finally, the temporary array is copied back into the original one. -> **Note:** `random()` is a helper function that returns a random integer between 0 and the given maximum. - This code works just fine but it's not very efficient. Removing an element from an array is an **O(n)** operation and we perform this **n** times, making the total algorithm **O(n^2)**. We can do better! ## The Fisher-Yates / Knuth shuffle -Here is a much improved version of the shuffle algorithm: +Here is a much-improved version of the shuffle algorithm: ```swift extension Array { public mutating func shuffle() { for i in stride(from: count - 1, through: 1, by: -1) { - let j = random(i + 1) + let j = Int.random(in: 0...i) if i != j { swap(&self[i], &self[j]) } @@ -54,9 +52,7 @@ extension Array { } ``` -Again, this picks objects at random. In the naive version we placed those objects into a new temporary array so we could keep track of which objects were already shuffled and which still remained to be done. In this improved algorithm, however, we'll move the shuffled objects to the end of the original array. - -> **Note**: When you write `random(x)`, the largest number it will return is `x - 1`. We want to have `j <= i`, not `j < i`, so the largest number from the random number generator needs to be `i`, not `i - 1`. That's why we do `random(i + 1)`. If we didn't add that 1 to compensate, it would make some shuffle orders more likely to occur than others. +Again, this picks objects at random. In the naive version, we placed those objects into a new temporary array so we could keep track of which objects were already shuffled and which still remained to be done. In this improved algorithm, however, we'll move the shuffled objects to the end of the original array. Let's walk through the example. We have the array: @@ -99,10 +95,9 @@ Here is the code: public func shuffledArray(_ n: Int) -> [Int] { var a = [Int](repeating: 0, count: n) for i in 0.. Int { - return Int(arc4random_uniform(UInt32(n))) -} - -/* Fisher-Yates / Knuth shuffle */ -extension Array { - public mutating func shuffle() { - for i in stride(from: count - 1, through: 1, by: -1) { - let j = random(i + 1) - if i != j { - swap(&self[i], &self[j]) - } - } - } -} - var list = [ "a", "b", "c", "d", "e", "f", "g" ] list.shuffle() list.shuffle() list.shuffle() -/* Create a new array of numbers that is already shuffled. */ -public func shuffledArray(_ n: Int) -> [Int] { - var a = [Int](repeating: 0, count: n) - for i in 0.. [Int] { + var a = Array(repeating: 0, count: n) + for i in 0.. - - - - diff --git a/Shuffle/Shuffle.swift b/Shuffle/Shuffle.swift index 8fe8b4e87..1d2758c11 100644 --- a/Shuffle/Shuffle.swift +++ b/Shuffle/Shuffle.swift @@ -1,32 +1,38 @@ import Foundation +/* Returns a random integer between 0 and n-1. */ +public func random(_ n: Int) -> Int { + return Int(arc4random_uniform(UInt32(n))) +} + extension Array { /* - Randomly shuffles the array in-place - This is the Fisher-Yates algorithm, also known as the Knuth shuffle. - Time complexity: O(n) - */ + Randomly shuffles the array in-place + This is the Fisher-Yates algorithm, also known as the Knuth shuffle. + Time complexity: O(n) + */ public mutating func shuffle() { - for i in (count - 1).stride(through: 1, by: -1) { + for i in (1...count-1).reversed() { let j = random(i + 1) if i != j { - swap(&self[i], &self[j]) + let t = self[i] + self[i] = self[j] + self[j] = t } } } } /* - Simultaneously initializes an array with the values 0...n-1 and shuffles it. -*/ -public func shuffledArray(n: Int) -> [Int] { - var a = [Int](count: n, repeatedValue: 0) + Simultaneously initializes an array with the values 0...n-1 and shuffles it. + */ +public func shuffledArray(_ n: Int) -> [Int] { + var a = Array(repeating: 0, count: n) for i in 0.. { - fileprivate var array = [T]() - - public init() { - - } - - public var isEmpty: Bool { - return array.isEmpty - } - - public var count: Int { - return array.count - } - - public mutating func push(_ element: T) { - array.append(element) - } - - public mutating func pop() -> T? { - return array.popLast() - } - - public var top: T? { - return array.last - } + fileprivate var array = [T]() + + public init() { + + } + + public var isEmpty: Bool { + return array.isEmpty + } + + public var count: Int { + return array.count + } + + public mutating func push(_ element: T) { + array.append(element) + } + + public mutating func pop() -> T? { + return array.popLast() + } + + public var top: T? { + return array.last + } } extension Stack: Sequence { - public func makeIterator() -> AnyIterator { - var curr = self - return AnyIterator { _ -> T? in - return curr.pop() - } - } + public func makeIterator() -> AnyIterator { + var curr = self + return AnyIterator { curr.pop() } + } } diff --git a/Shunting Yard/ShuntingYard.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Shunting Yard/ShuntingYard.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Shunting Yard/ShuntingYard.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Simulated annealing/README.md b/Simulated annealing/README.md new file mode 100644 index 000000000..3a36464d7 --- /dev/null +++ b/Simulated annealing/README.md @@ -0,0 +1,36 @@ +# Simulated annealing + +Simulated Annealing is a nature inspired global optimization technique and a metaheuristic to approximate global maxima in a (often discrete)large search space. The name comes from the process of annealing in metallurgy where a material is heated and cooled down under controlled conditions in order to improve its strength and durabilility. The objective is to find a minimum cost solution in the search space by exploiting properties of a thermodynamic system. +Unlike hill climbing techniques which usually gets stuck in a local maxima ( downward moves are not allowed ), simulated annealing can escape local maxima. The interesting property of simulated annealing is that probability of allowing downward moves is high at the high temperatures and gradually reduced as it cools down. In other words, high temperature relaxes the acceptance criteria for the search space and triggers chaotic behavior of acceptance function in the algorithm (e.x initial/high temperature stages) which should make it possible to escape from local maxima and cooler temperatures narrows it and focuses on improvements. + +Pseucocode + + Input: initial, temperature, coolingRate, acceptance + Output: Sbest + Scurrent <- CreateInitialSolution(initial) + Sbest <- Scurrent + while temperature is not minimum: + Snew <- FindNewSolution(Scurrent) + if acceptance(Energy(Scurrent), Energy(Snew), temperature) > Rand(): + Scurrent = Snew + if Energy(Scurrent) < Energy(Sbest): + Sbest = Scurrent + temperature = temperature * (1-coolingRate) + +Common acceptance criteria : + + P(accept) <- exp((e-ne)/T) where + e is the current energy ( current solution ), + ne is new energy ( new solution ), + T is current temperature. + + +We use this algorithm to solve a Travelling salesman problem instance with 20 cities. The code is in `simann_example.swift` + +#See also + +[Simulated annealing on Wikipedia](https://en.wikipedia.org/wiki/Simulated_annealing) + +[Travelling salesman problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) + +Written for Swift Algorithm Club by [Mike Taghavi](https://github.com/mitghi) diff --git a/Simulated annealing/simann.swift b/Simulated annealing/simann.swift new file mode 100644 index 000000000..5dc624d04 --- /dev/null +++ b/Simulated annealing/simann.swift @@ -0,0 +1,95 @@ +// The MIT License (MIT) +// Copyright (c) 2017 Mike Taghavi (mitghi[at]me.com) +// 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. + +#if os(OSX) + import Foundation +#elseif os(Linux) + import Glibc +#endif + +protocol Clonable { + init(current: Self) +} + +// MARK: - create a clone from instance + +extension Clonable { + func clone() -> Self { + return Self.init(current: self) + } +} + +protocol SAObject: Clonable { + var count: Int { get } + func randSwap(a: Int, b: Int) + func currentEnergy() -> Double + func shuffle() +} + +// MARK: - create a new copy of elements + +extension Array where Element: Clonable { + func clone() -> Array { + var newArray = Array() + for elem in self { + newArray.append(elem.clone()) + } + + return newArray + } +} + +typealias AcceptanceFunc = (Double, Double, Double) -> Double + +func SimulatedAnnealing(initial: T, temperature: Double, coolingRate: Double, acceptance: AcceptanceFunc) -> T { + // Step 1: + // Calculate the initial feasible solution based on a random permutation. + // Set best and current solutions to initial solution + + var temp: Double = temperature + var currentSolution = initial.clone() + currentSolution.shuffle() + var bestSolution = currentSolution.clone() + + // Step 2: + // Repeat while the system is still hot + // Randomly modify the current solution by swapping its elements + // Randomly decide if the new solution ( neighbor ) is acceptable and set current solution accordingly + // Update the best solution *iff* it had improved ( lower energy = improvement ) + // Reduce temperature + + while temp > 1 { + let newSolution: T = currentSolution.clone() + let pos1: Int = Int.random(in: 0 ..< newSolution.count) + let pos2: Int = Int.random(in: 0 ..< newSolution.count) + newSolution.randSwap(a: pos1, b: pos2) + let currentEnergy: Double = currentSolution.currentEnergy() + let newEnergy: Double = newSolution.currentEnergy() + + if acceptance(currentEnergy, newEnergy, temp) > Double.random(in: 0 ..< 1) { + currentSolution = newSolution.clone() + } + if currentSolution.currentEnergy() < bestSolution.currentEnergy() { + bestSolution = currentSolution.clone() + } + + temp *= 1-coolingRate + } + + return bestSolution +} diff --git a/Simulated annealing/simann_example.swift b/Simulated annealing/simann_example.swift new file mode 100644 index 000000000..e7933c89a --- /dev/null +++ b/Simulated annealing/simann_example.swift @@ -0,0 +1,206 @@ +// The MIT License (MIT) +// Copyright (c) 2017 Mike Taghavi (mitghi[at]me.com) +// 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. + +#if os(OSX) + import Foundation + import Cocoa +#elseif os(Linux) + import Glibc +#endif + +protocol Clonable { + init(current: Self) +} + +extension Clonable { + func clone() -> Self { + return Self.init(current: self) + } +} + +protocol SAObject: Clonable { + var count: Int { get } + func randSwap(a: Int, b: Int) + func currentEnergy() -> Double + func shuffle() +} + +// MARK: - create a new copy of elements + +extension Array where Element: Clonable { + func clone() -> Array { + var newArray = Array() + for elem in self { + newArray.append(elem.clone()) + } + + return newArray + } +} + +typealias Points = [Point] +typealias AcceptanceFunc = (Double, Double, Double) -> Double + +class Point: Clonable { + var x: Int + var y: Int + + init(x: Int, y: Int) { + self.x = x + self.y = y + } + + required init(current: Point){ + self.x = current.x + self.y = current.y + } +} + +// MARK: - string representation + +extension Point: CustomStringConvertible { + public var description: String { + return "Point(\(x), \(y))" + } +} + +// MARK: - return distance between two points using operator '<->' + +infix operator <->: AdditionPrecedence +extension Point { + static func <-> (left: Point, right: Point) -> Double { + let xDistance = (left.x - right.x) + let yDistance = (left.y - right.y) + + return Double((xDistance * xDistance) + (yDistance * yDistance)).squareRoot() + } +} + + +class Tour: SAObject { + var tour: Points + var energy: Double = 0.0 + var count: Int { + get { + return self.tour.count + } + } + + init(points: Points){ + self.tour = points.clone() + } + + required init(current: Tour) { + self.tour = current.tour.clone() + } +} + +// MARK: - calculate current tour distance ( energy ). + +extension Tour { + func randSwap(a: Int, b: Int) -> Void { + let (cpos1, cpos2) = (self[a], self[b]) + self[a] = cpos2 + self[b] = cpos1 + } + + func currentEnergy() -> Double { + if self.energy == 0 { + var tourEnergy: Double = 0.0 + for i in 0..destCity + tourEnergy = tourEnergy + e + + } + self.energy = tourEnergy + } + return self.energy + } + + func shuffle() { + self.tour.shuffle() + } +} + +// MARK: - subscript to manipulate elements of Tour. + +extension Tour { + subscript(index: Int) -> Point { + get { + return self.tour[index] + } + set(newValue) { + self.tour[index] = newValue + } + } +} + +func SimulatedAnnealing(initial: T, temperature: Double, coolingRate: Double, acceptance: AcceptanceFunc) -> T { + var temp: Double = temperature + var currentSolution = initial.clone() + currentSolution.shuffle() + var bestSolution = currentSolution.clone() + print("Initial solution: ", bestSolution.currentEnergy()) + + while temp > 1 { + let newSolution: T = currentSolution.clone() + let pos1: Int = Int.random(in: 0 ..< newSolution.count) + let pos2: Int = Int.random(in: 0 ..< newSolution.count) + newSolution.randSwap(a: pos1, b: pos2) + let currentEnergy: Double = currentSolution.currentEnergy() + let newEnergy: Double = newSolution.currentEnergy() + + if acceptance(currentEnergy, newEnergy, temp) > Double.random(in: 0 ..< 1) { + currentSolution = newSolution.clone() + } + if currentSolution.currentEnergy() < bestSolution.currentEnergy() { + bestSolution = currentSolution.clone() + } + + temp *= 1-coolingRate + } + + print("Best solution: ", bestSolution.currentEnergy()) + return bestSolution +} + +let points: [Point] = [ + (60 , 200), (180, 200), (80 , 180), (140, 180), + (20 , 160), (100, 160), (200, 160), (140, 140), + (40 , 120), (100, 120), (180, 100), (60 , 80) , + (120, 80) , (180, 60) , (20 , 40) , (100, 40) , + (200, 40) , (20 , 20) , (60 , 20) , (160, 20) , + ].map{ Point(x: $0.0, y: $0.1) } + +let acceptance : AcceptanceFunc = { + (e: Double, ne: Double, te: Double) -> Double in + if ne < e { + return 1.0 + } + return exp((e - ne) / te) +} + +let result: Tour = SimulatedAnnealing(initial : Tour(points: points), + temperature : 100000.0, + coolingRate : 0.003, + acceptance : acceptance) diff --git a/Single-Source Shortest Paths (Weighted)/SSSP.playground/Contents.swift b/Single-Source Shortest Paths (Weighted)/SSSP.playground/Contents.swift index 652b558b1..e897ada4a 100644 --- a/Single-Source Shortest Paths (Weighted)/SSSP.playground/Contents.swift +++ b/Single-Source Shortest Paths (Weighted)/SSSP.playground/Contents.swift @@ -1,6 +1,11 @@ import Graph import SSSP +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + let graph = AdjacencyMatrixGraph() let s = graph.createVertex("s") let t = graph.createVertex("t") diff --git a/Single-Source Shortest Paths (Weighted)/SSSP.xcodeproj/project.pbxproj b/Single-Source Shortest Paths (Weighted)/SSSP.xcodeproj/project.pbxproj index fb425a73e..44365e5aa 100644 --- a/Single-Source Shortest Paths (Weighted)/SSSP.xcodeproj/project.pbxproj +++ b/Single-Source Shortest Paths (Weighted)/SSSP.xcodeproj/project.pbxproj @@ -334,7 +334,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -378,7 +378,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -401,7 +401,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -421,7 +421,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.SSSP"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -433,7 +433,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.SSSPTests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -445,7 +445,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.SSSPTests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Single-Source Shortest Paths (Weighted)/SSSPTests/SSSPTests.swift b/Single-Source Shortest Paths (Weighted)/SSSPTests/SSSPTests.swift index 04584dfeb..924a487f5 100644 --- a/Single-Source Shortest Paths (Weighted)/SSSPTests/SSSPTests.swift +++ b/Single-Source Shortest Paths (Weighted)/SSSPTests/SSSPTests.swift @@ -10,6 +10,13 @@ import XCTest @testable import SSSP class SSSPTests: XCTestCase { + + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } /** See Figure 24.4 of “Introduction to Algorithms” by Cormen et al, 3rd ed., pg 652 diff --git a/Singly Linked List/Images/CopiedIndirectStorage.png b/Singly Linked List/Images/CopiedIndirectStorage.png new file mode 100644 index 000000000..7a2f7e68c Binary files /dev/null and b/Singly Linked List/Images/CopiedIndirectStorage.png differ diff --git a/Singly Linked List/Images/SharedIndirectStorage.png b/Singly Linked List/Images/SharedIndirectStorage.png new file mode 100644 index 000000000..51920c236 Binary files /dev/null and b/Singly Linked List/Images/SharedIndirectStorage.png differ diff --git a/Singly Linked List/KeyValuePair.swift b/Singly Linked List/KeyValuePair.swift new file mode 100644 index 000000000..9c6cacea4 --- /dev/null +++ b/Singly Linked List/KeyValuePair.swift @@ -0,0 +1,77 @@ +/// Conformers of this protocol represent a pair of comparable values +/// This can be useful in many data structures and algorithms where items +/// stored contain a value, but are ordered or retrieved according to a key. +public protocol KeyValuePair : Comparable { + + associatedtype K : Comparable, Hashable + associatedtype V : Comparable, Hashable + + // Identifier used in many algorithms to search by, order by, etc + var key : K {get set} + + // A data container + var value : V {get set} + + + /// Initializer + /// + /// - Parameters: + /// - key: Identifier used in many algorithms to search by, order by, etc. + /// - value: A data container. + init(key: K, value: V) + + + /// Creates a copy + /// + /// - Abstract: Conformers of this class can be either value or reference types. + /// Some algorithms might need to guarantee that a conformer instance gets copied. + /// This will perform an innecessary in the case of value types. + /// TODO: is there a better way? + /// - Returns: A new instance with the old one's values copied. + func copy() -> Self +} + + +/// Conformance to Equatable and Comparable protocols +extension KeyValuePair { + + // MARK: - Equatable protocol + public static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.key == rhs.key + } + + + + // MARK: - Comparable protocol + + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.key < rhs.key + } + + public static func <=(lhs: Self, rhs: Self) -> Bool { + return lhs.key <= rhs.key + } + + public static func >=(lhs: Self, rhs: Self) -> Bool { + return lhs.key >= rhs.key + } + + public static func >(lhs: Self, rhs: Self) -> Bool { + return lhs.key > rhs.key + } +} + + + +/// Concrete impletation of a KeyValuePair where both the key and the value +/// are Integers. +struct IntegerPair : KeyValuePair { + + // MARK - KeyValuePair protocol + var key : Int + var value : Int + + func copy() -> IntegerPair { + return IntegerPair(key: self.key, value: self.value) + } +} diff --git a/Singly Linked List/README.markdown b/Singly Linked List/README.markdown new file mode 100644 index 000000000..6d896db88 --- /dev/null +++ b/Singly Linked List/README.markdown @@ -0,0 +1,136 @@ +# Singly-Linked List + +#### How is this different to the Linked List implementation? +The existing Linked list implementation represents the same concept. However, the existing implementation has reference semantics and does not conform to the Collection protocol implemented in the Swift's standard Library. What SinglyLinkedList aims to contribute is a more idiomatic Swift implementation, that uses value semantics and copy-on-write as well as conforms to the collection protocol. + +#### Conceptual representation +A Singly linked list is a non-contiguous sequence of data items in memory. Each element links to the next via a memory reference. Additionally, this implementation keeps track of the last element, which can be retrived in order O(1). However, the list can only be traversed from head to tail. + + +--------+ +--------+ +--------+ +--------+ + | | | | | | | | + | node 0 |--->| node 1 |--->| node 2 |--->| node 3 |---> nil + | | | | | | | | + +--------+ +--------+ +--------+ +--------+ + ^ ^ + | | + Head Tail + +Each element in the list is represented with an instance of SinglyLinkedListNode class, which basically contains some data and a reference of optional type to the next node, which means that the last node's next reference is `nil`. + +#### In-memory representation +In Swift, data types can have value or reference semantics. This implementation of a singly-linked list uses value semantics. Support for copy-on-write has been added in order to improve performance and delay copying the elements of the array until strictly necessary. + +The image below shows how initially, after variable `l2` is assigned `l1`, a new instance of the struct SinglyLinkedList is created. Nevertheless, the indirect storage is still shared as indicated by the references that both l1 and l2 have pointing to a common area in memory. + +![alt text](Images/SharedIndirectStorage.png "Two linked lists sharing the indirect storage") + +Once a mutating operation happens --for example on `l2` to append a new element--, then the indirect storage and all nodes in the list is actually copied and the references from `l1` and `l2` are updated. This is ilustrated in the following figure. + +![alt text](Images/CopiedIndirectStorage.png "A copy is created after editing one of the lists") + +#### Implementation details +1. Direct access to the tail in O(1) by keeping a reference that gets updated only when an operation to the list modifies the tail. +2. Value semantics. This implementation of a singly-linked list uses a struct. When the list struct is assigned into another variable or passed as an argument to a function, a copy of the struct is made. +3. Copy-on write. Instances of SinglyLinkedList have an internal reference to an indirect storage allocated in the heap. When a copy of the list is made (according to its value semantics) the indirect storage is initialy shared between the copies. This means that a potentially large list can be accessed to read values in it without an expensive copy having to take place. However, as soon as there is a write access to the indirect storage when there are more than one instances referencing to it, a copy will be performed to guarantee value semantics. +4. Conformance to the Collection protocol. + + + + + +## Performance of linked lists + +EDITION +- *append*: Appends a new node to the end of the list. This method will modify the list's tail. If the list is empty, it will also modify the head. This operation's time complexity is *O(1)* since there's a reference to the tail node in this implementation. +- *prepend*: Inserts a new node at the start of the list. If the list is empty, it will also modify the head. This operation's time complexity is *O(1)* since there's a reference to the head node. +- *delete*: Finds a node in the list and deletes it. This operation's time complexity has an upper bound described by a linear function; O(n). + +SEARCH +- find k-th to last element. Given a list with `n` number of elements and `k` being the passed parameter with `0` <= `k` <= `n`, this method has *O(k)*. + +## Conforming to the Collection protocol +Collections are sequences, therefore the first step is to conform to the Sequence protocol. + +``` +extension SinglyLinkedList: Sequence { + public func makeIterator() -> SinglyLinkedListForwardIterator { + return SinglyLinkedListForwardIterator(head: self.storage.head) + } +} +``` + +We have used `SinglyLinkedListForwardIterator` an iterator class to keep track of the progress while iterating the structure: + +``` +public struct SinglyLinkedListForwardIterator : IteratorProtocol { + + public typealias Element = T + + private(set) var head: SinglyLinkedListNode? + + mutating public func next() -> T? { + let result = self.head?.value + self.head = self.head?.next + return result + } +} +``` + +Next, a collection needs to be indexable. Indexes are implemented by the data structure class. We have encapsulated this knowledge in instances of the class `SinglyLinkedListIndex`. + +``` +public struct SinglyLinkedListIndex: Comparable { + fileprivate let node: SinglyLinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: SinglyLinkedListIndex, rhs: SinglyLinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: SinglyLinkedListIndex, rhs: SinglyLinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} +``` + +Finally, the methods in the collection to manipulate indexes are implemented below: +- startIndex: Index of the first element. Can be calculated with O(1). +- endIndex: Index that follows the last valid index in the collection. That is, the index that follows the tail's index. Can be calculated with O(1) if the count of elements is kept internally. However, the implementation below is O(n) with `n` being the number of elements in the list. + +``` +extension SinglyLinkedList : Collection { + + public typealias Index = SinglyLinkedListIndex + + public var startIndex: Index { + get { + return SinglyLinkedListIndex(node: self.storage.head, tag: 0) + } + } + +public var endIndex: Index { + get { + if let h = self.storage.head { + let (_, numberOfElements) = findTail(in: h) + return SinglyLinkedListIndex(node: h, tag: numberOfElements) + } else { + return SinglyLinkedListIndex(node: nil, tag: self.startIndex.tag) + } + } +} + +public subscript(position: Index) -> T { + get { + return position.node!.value + } +} + +public func index(after idx: Index) -> Index { + return SinglyLinkedListIndex(node: idx.node?.next, tag: idx.tag+1) + } +} +``` + +Conforming to the Collection protocol allows our class SinglyLinkedList to take adventage of all the collection methods included in the Stardard Library. + +*Written by Borja Arias Drake* diff --git a/Singly Linked List/SinglyLinkedList.playground/Contents.swift b/Singly Linked List/SinglyLinkedList.playground/Contents.swift new file mode 100644 index 000000000..8c62f9e78 --- /dev/null +++ b/Singly Linked List/SinglyLinkedList.playground/Contents.swift @@ -0,0 +1,727 @@ +//: # Linked Lists + +// For best results, don't forget to select "Show Rendered Markup" from XCode's "Editor" menu + +//: Linked List Class Declaration: +// last checked with Xcode 9M136h +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + +//: We will start by defining a protocol that elements inserted into this implementation of a List will conform to. This is useful, because many algorithms are only interested in a key that all elements have. + +/// Conformers of this protocol represent a pair of comparable values +/// This can be useful in many data structures and algorithms where items +/// stored contain a value, but are ordered or retrieved according to a key. +public protocol KeyValuePair : Comparable { + + associatedtype K : Comparable, Hashable + associatedtype V : Comparable, Hashable + + // Identifier used in many algorithms to search by, order by, etc + var key : K {get set} + + // A data container + var value : V {get set} + + + /// Initializer + /// + /// - Parameters: + /// - key: Identifier used in many algorithms to search by, order by, etc. + /// - value: A data container. + init(key: K, value: V) + + + /// Creates a copy + /// + /// - Abstract: Conformers of this class can be either value or reference types. + /// Some algorithms might need to guarantee that a conformer instance gets copied. + /// This will perform an innecessary in the case of value types. + /// TODO: is there a better way? + /// - Returns: A new instance with the old one's values copied. + func copy() -> Self +} + + +/// Conformance to Equatable and Comparable protocols +extension KeyValuePair { + + // MARK: - Equatable protocol + public static func ==(lhs: Self, rhs: Self) -> Bool { + return lhs.key == rhs.key + } + + + + // MARK: - Comparable protocol + + public static func <(lhs: Self, rhs: Self) -> Bool { + return lhs.key < rhs.key + } + + public static func <=(lhs: Self, rhs: Self) -> Bool { + return lhs.key <= rhs.key + } + + public static func >=(lhs: Self, rhs: Self) -> Bool { + return lhs.key >= rhs.key + } + + public static func >(lhs: Self, rhs: Self) -> Bool { + return lhs.key > rhs.key + } +} + +//: Concrete impletation of a KeyValuePair where both the key and the value that we'll use in the examples in this playground + +struct IntegerPair : KeyValuePair { + + // MARK - KeyValuePair protocol + var key : Int + var value : Int + + func copy() -> IntegerPair { + return IntegerPair(key: self.key, value: self.value) + } +} + +//: Lists are usually used as queues. As an example, we'll define these expectations in a protocol and we'll make the the SinglyLinkedList class conform to it. + +/// Data structure that provides FIFO access +protocol Queue +{ + associatedtype Item + + + /// Adds an element to the queue + /// + /// - Parameter item: Item to be added + /// - Throws: There are cases where the operation might fail. For example if there is not enough space. + mutating func enqueue(item: Item) throws + + + /// Returns the oldest element in the queue. + /// + /// - Returns: The oldest element in the queue. It does not dequeue it. + func getFirst() -> Item? + + + /// Dequeues the oldest element in the queue. + /// + /// - Returns: The oldest element in the queue, which gets removed from it. + mutating func dequeue() -> Item? +} + + +//: Next, we are going to implement a list that has value semantics. Value semantics imply that the values will be copied when assigned and passed as a parameter to functions and methods. In order to prevent unnecesasry copies, Swift uses a technique called copy-on-write; copies are only performed when a mutating operation is performed on an instance that is referenced by more than one objects. In Swift, this is ahieved with an additional level of indirection, an internal class instance, therefore with reference semantics, that will be shared, until a mutating operation occurs. + + +/// Helper class to implement copy-on-write +fileprivate class IndirectStorage { + + var head: SinglyLinkedListNode? + + var tail: SinglyLinkedListNode? + + init(head: SinglyLinkedListNode?, tail: SinglyLinkedListNode?) { + self.head = head + self.tail = tail + } + + convenience init() { + self.init(head: nil, tail: nil) + } +} + + +//: Node of the list + + +// MARK: - NODE - + +/// A node is the building block of a linked list. +/// It can be used on its own to create linked lists. However users of this class, +/// will need to manipulate references directly. +public class SinglyLinkedListNode { + + /// Data container + public var value: T + + /// Link to the next node + public var next: SinglyLinkedListNode? + + /// Designated initializer + /// + /// - Parameter value: A value + public init(value: T) { + self.value = value + } +} + +//: The list with value semantics + +// MARK: - LINKED LIST - + +/// Data structure to hold a collection of items. +/// Each nodes contains a reference to the next node. +/// The last node does not reference any other node. +/// This class implements value semantics based on copy-on-write. +/// +/// However, the elements contained in the list, will be shallow copied if they +/// implement reference semantics. +public struct SinglyLinkedList +{ + // MARK: PROPERTIES + + // A level of inderiction, with reference semantics to allow easy + // detection of when there are more than one references of the storage. + private var storage: IndirectStorage + + /// Whenever there's a change that potentially can change the value, this reference to the + /// storage should be used to guarantee that a new copy is created and written on. + private var storageForWritting: IndirectStorage { + + mutating get { + if !isKnownUniquelyReferenced(&self.storage) { + self.storage = self.copyStorage() + } + return self.storage + } + } + + /// Returns the last element in the collection + public var last: T? { + get { + return self.storage.tail?.value + } + } + + + + // MARK: INITIALIZERS + + /// Creates a list with the given node. + /// NOTE: This method can break value semantics by accepting a node. + /// + /// - Parameter head: First node + internal init(head: SinglyLinkedListNode) + { + self.storage = IndirectStorage() + self.append(node: head) + } + + + /// Creates a list with a single element + /// + /// - Parameter value: element to populate the list with + public init(value: T) + { + let node = SinglyLinkedListNode(value: value) + self.init(head: node) + } + + + /// Creates an empty list + public init() + { + self.storage = IndirectStorage() + } + + + // MARK: EDITION + + /// Convenience method to append a value directly to the list + /// + /// - Parameter value: value to be added + public mutating func append(value: T) + { + let node = SinglyLinkedListNode(value: value) + self.append(node: node) + } + + + /// Convenience method to prepend a value directly to the list + /// + /// - Parameter value: value to be added as the new head of the list + public mutating func prepend(value: T) + { + let node = SinglyLinkedListNode(value: value) + self.prepend(node: node) + } + + + public mutating func deleteItem(at index:Int) -> T + { + precondition((index >= 0) && (index < self.count)) + + var previous: SinglyLinkedListNode? = nil + var current = self.storageForWritting.head + var i = 0 + var elementToDelete: SinglyLinkedListNode + + while (i < index) { + previous = current + current = current?.next + i += 1 + } + + // Current is now the element to delete (at index position.tag) + elementToDelete = current! + if (self.storage.head === current) { + self.storageForWritting.head = current?.next + } + + if (self.storage.tail === current) { + self.storageForWritting.tail = previous + } + + previous?.next = current?.next + + return elementToDelete.value + } + + + + // MARK: SEARCH + + /// Returns the node located at the k-th to last position + /// + /// - Parameter kthToLast: 1 <= k <= N + private func find(kthToLast: UInt, startingAt node: SinglyLinkedListNode?, count: UInt) -> SinglyLinkedListNode? + { + guard kthToLast <= count else { + return nil + } + + guard (node != nil) else { + return nil + } + + let i = (count - kthToLast) + + if (i == 0) { + return node + } + + return find(kthToLast: kthToLast, startingAt: node?.next, count: (count - 1)) + } + + + /// Returns the kth-to-last element in the list + /// + /// - Parameter kthToLast: Reversed ordinal number of the node to fetch. + public func find(kthToLast: UInt) -> SinglyLinkedListNode? + { + return self.find(kthToLast: kthToLast, startingAt: self.storage.head, count: UInt(self.count)) + } + + + + // MARK: LOOP DETECTION + + /// A singly linked list contains a loop if one node references back to a previous node. + /// + /// - Returns: Whether the linked list contains a loop + public func containsLoop() -> Bool + { + /// Advances a node at a time + var current = self.storage.head + + /// Advances twice as fast + var runner = self.storage.head + + while (runner != nil) && (runner?.next != nil) { + + current = current?.next + runner = runner?.next?.next + + if runner === current { + return true + } + } + + return false + } + +} + + + +// MARK:- Private Methods + +private extension SinglyLinkedList { + + /// Adds a new node to the current head. This method can easily break value semantics. It is left + /// for internal use. + /// + /// - Parameter node: the node that will be the new head of the list. + private mutating func prepend(node: SinglyLinkedListNode) + { + let (tailFromNewNode, _) = findTail(in: node) + tailFromNewNode.next = self.storageForWritting.head + self.storageForWritting.head = node + + if self.storage.tail == nil { + self.storageForWritting.tail = tailFromNewNode + } + } + + /// Appends a new node to the list. This method can easily break value semantics. It is left + /// for internal use. + /// - Discussion: If the node to be inserted contains a loop, the node is appended but tail is set to nil. + /// This is a private method, therefore this can only happen directly under the control of this class. + /// - Parameter node: node to be appended. (It can be a list, even contain loops). + private mutating func append(node: SinglyLinkedListNode) + { + if self.storage.tail != nil + { + // Copy on write: we are about to modify next a property in + // a potentially shared node. Make sure it's new if necessary. + self.storageForWritting.tail?.next = node + if !self.containsLoop() { + let (tail, _) = findTail(in: node) + self.storageForWritting.tail = tail // There + } else { + self.storageForWritting.tail = nil + } + } + else + { + // This also means that there's no head. + // Otherwise the state would be inconsistent. + // This will be checked when adding and deleting nodes. + self.storageForWritting.head = node + if !self.containsLoop() { + let (tail, _) = findTail(in: node) + self.storageForWritting.tail = tail // There + } else { + self.storageForWritting.tail = nil + } + } + } + + /// Creates a copy of the linked list in a diffent memory location. + /// + /// - Returns: The copy to a new storage or a reference to the old one if no copy was necessary. + private func copyStorage() -> IndirectStorage { + // If the list is empty, next time an item will be created, it won't affect + // other instances of the list that came from copies derived from value types. + // like assignments or parameters + guard (self.storage.head != nil) && (self.storage.tail != nil) else { + return IndirectStorage(head: nil, tail: nil) + } + + // Create a new position in memory. + // Note that we are shallow copying the value. If it was reference type + // we just make a copy of the reference. + let copiedHead = SinglyLinkedListNode(value: self.storage.head!.value) + var previousCopied: SinglyLinkedListNode = copiedHead + + // Iterate through current list of nodes and copy them. + var current: SinglyLinkedListNode? = self.storage.head?.next + + while (current != nil) { + // Create a copy + let currentCopy = SinglyLinkedListNode(value: current!.value) + + // Create links + previousCopied.next = currentCopy + + // Update pointers + current = current?.next + previousCopied = currentCopy + } + + return IndirectStorage(head: copiedHead, tail: previousCopied) + } +} + + + +// MARK:- Extensions when comparable + +extension SinglyLinkedList where T: Comparable +{ + /// Deletes node containing a given value + /// + /// - Parameter v: value of the node to be deleted. + public mutating func deleteNode(withValue v: T) { + + guard self.storage.head != nil else { + return + } + + var previous: SinglyLinkedListNode? = nil + var current = self.storage.head + + while (current != nil) && (current?.value != v) { + previous = current + current = current?.next + } + + if let foundNode = current { + + if (self.storage.head === foundNode) { + self.storageForWritting.head = foundNode.next + } + + if (self.storage.tail === foundNode) { + self.storage.tail = previous + } + + previous?.next = foundNode.next + foundNode.next = nil + } + } + + + /// Deletes duplicates without using additional structures like a set to keep track the visited nodes. + /// - Complexity: O(N^2) + public mutating func deleteDuplicatesInPlace() + { + // Copy on write: this updates self.storage if necessary. + var current = self.storageForWritting.head + + while (current != nil) + { + var previous: SinglyLinkedListNode? = current + var next = current?.next + + while (next != nil) + { + if (current?.value == next?.value) { + + if (self.storage.head === next) { + self.storage.head = next?.next + } + + if (self.storage.tail === next) { + self.storage.tail = previous + } + + // Delete next + previous?.next = next?.next + } + previous = next + next = next?.next + } + current = current?.next + } + } +} + + + +// MARK: - ITERATOR - + +public struct SinglyLinkedListForwardIterator : IteratorProtocol { + + public typealias Element = T + + private(set) var head: SinglyLinkedListNode? + + mutating public func next() -> T? + { + let result = self.head?.value + self.head = self.head?.next + return result + } +} + + + +// MARK: - SEQUENCE - + +extension SinglyLinkedList : Sequence +{ + public func makeIterator() -> SinglyLinkedListForwardIterator + { + return SinglyLinkedListForwardIterator(head: self.storage.head) + } +} + + + +// MARK: - COLLECTION - + +extension SinglyLinkedList : Collection { + + public typealias Index = SinglyLinkedListIndex + + public var startIndex: Index { + get { + return SinglyLinkedListIndex(node: self.storage.head, tag: 0) + } + } + + public var endIndex: Index { + get { + if let h = self.storage.head { + let (_, numberOfElements) = findTail(in: h) + return SinglyLinkedListIndex(node: h, tag: numberOfElements) + } else { + return SinglyLinkedListIndex(node: nil, tag: self.startIndex.tag) + } + } + } + + public subscript(position: Index) -> T { + get { + return position.node!.value + } + } + + public func index(after idx: Index) -> Index { + return SinglyLinkedListIndex(node: idx.node?.next, tag: idx.tag+1) + } +} + + + +// MARK: - QUEUE - + +extension SinglyLinkedList : Queue +{ + typealias Item = T + + /// Returns the oldest element in the queue. + /// + /// - Returns: The oldest element in the queue. It does not dequeue it. + func getFirst() -> T? { + return self.storage.head?.value + } + + /// Adds an element to the queue + /// + /// - Parameter item: Item to be added + /// - Throws: There are cases where the operation might fail. For example if there is not enough space. + mutating func enqueue(item: T) throws { + self.append(node: SinglyLinkedListNode(value: item)) + } + + /// Dequeues the oldest element in the queue. + /// + /// - Returns: The oldest element in the queue, which gets removed from it. + mutating func dequeue() -> T? + { + guard self.count > 0 else { + return nil + } + + return self.deleteItem(at: 0) + } +} + + + +// MARK: - EXPRESSIBLE-BY-ARRAY-LITERAL - + +extension SinglyLinkedList : ExpressibleByArrayLiteral +{ + public typealias Element = T + + public init(arrayLiteral elements: Element...) + { + var headSet = false + var current : SinglyLinkedListNode? + var numberOfElements = 0 + self.storage = IndirectStorage() + + for element in elements { + + numberOfElements += 1 + + if headSet == false { + self.storage.head = SinglyLinkedListNode(value: element) + current = self.storage.head + headSet = true + } else { + let newNode = SinglyLinkedListNode(value: element) + current?.next = newNode + current = newNode + } + } + self.storage.tail = current + } +} + + + +// MARK: - FORWARD-INDEX - + +public struct SinglyLinkedListIndex : Comparable +{ + fileprivate let node: SinglyLinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: SinglyLinkedListIndex, rhs: SinglyLinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: SinglyLinkedListIndex, rhs: SinglyLinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} + + +extension SinglyLinkedList where T : KeyValuePair { + + public func find(elementWithKey key: T.K) -> T? { + let searchResults = self.filter { (keyValuePair) -> Bool in + return keyValuePair.key == key + } + + return searchResults.first + } +} + +// MARK: - HELPERS - + +func findTail(in node: SinglyLinkedListNode) -> (tail: SinglyLinkedListNode, count: Int) +{ + // Assign the tail + // Note that the passed node can already be linking to other nodes, + // so the tail needs to be calculated. + var current: SinglyLinkedListNode? = node + var count = 1 + + while (current?.next != nil) { + current = current?.next + count += 1 + } + + if current != nil { + return (tail: current!, count: count) + } else { + return (tail: node, count: 1) + } +} + + + +//: EXAMPLES +//: Let's create a list + +var l1: SinglyLinkedList = [1,2,3,4,5,6,7] + +//: Because it has value semantics, if we assign it, a new value will be created. However, internally the storage is shared. + +var l2 = l1 + +//: If we modify l2, l1 will remain unaffected. +l2.append(value: 67) + +assert(l1.count == 7) +assert(l1.contains(67) == false) + +//: However, as expected, l2 has changed +assert(l2.count == 8) +assert(l2.contains(67) == true) + + +//: Notice that because our implementation conforms to the Collection protocol, we can use a lot of methods already implemented for us in Swift's standard library, for example, contains, dropLast, etc. + +let l3 = l2.dropLast() +assert(l3.count == 7) +assert(l3.contains(67) == false) diff --git a/Singly Linked List/SinglyLinkedList.playground/contents.xcplayground b/Singly Linked List/SinglyLinkedList.playground/contents.xcplayground new file mode 100644 index 000000000..fd676d5b4 --- /dev/null +++ b/Singly Linked List/SinglyLinkedList.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Singly Linked List/SinglyLinkedList.playground/playground.xcworkspace/contents.xcworkspacedata b/Singly Linked List/SinglyLinkedList.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Singly Linked List/SinglyLinkedList.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Singly Linked List/SinglyLinkedList.swift b/Singly Linked List/SinglyLinkedList.swift new file mode 100644 index 000000000..67e30eb7d --- /dev/null +++ b/Singly Linked List/SinglyLinkedList.swift @@ -0,0 +1,438 @@ +import Foundation + + +/// Helper class to implement copy-on-write +fileprivate class IndirectStorage { + + var head: SinglyLinkedListNode? + + var tail: SinglyLinkedListNode? + + init(head: SinglyLinkedListNode?, tail: SinglyLinkedListNode?) { + self.head = head + self.tail = tail + } + + convenience init() { + self.init(head: nil, tail: nil) + } +} + + + +// MARK: - NODE - + +/// A node is the building block of a linked list. +/// It can be used on its own to create linked lists. However users of this class, +/// will need to manipulate references directly. +public class SinglyLinkedListNode { + + /// Data container + public var value: T + + /// Link to the next node + public var next: SinglyLinkedListNode? + + /// Designated initializer + /// + /// - Parameter value: A value + public init(value: T) { + self.value = value + } +} + + + +// MARK: - LINKED LIST - + +/// Data structure to hold a collection of items. +/// Each nodes contains a reference to the next node. +/// The last node does not reference any other node. +/// This class implements value semantics based on copy-on-write. +/// +/// However, the elements contained in the list, will be shallow copied if they +/// implement reference semantics. +public struct SinglyLinkedList { + // MARK: PROPERTIES + + // A level of inderiction, with reference semantics to allow easy + // detection of when there are more than one references of the storage. + private var storage: IndirectStorage + + /// Whenever there's a change that potentially can change the value, this reference to the + /// storage should be used to guarantee that a new copy is created and written on. + private var storageForWritting: IndirectStorage { + + mutating get { + if !isKnownUniquelyReferenced(&storage) { + storage = copyStorage() + } + return storage + } + } + + /// Returns the last element in the collection + public var last: T? { + get { + return storage.tail?.value + } + } + + + + // MARK: INITIALIZERS + + /// Creates an empty list + public init() { + self.storage = IndirectStorage() + } + + + // MARK: EDITION + + /// Convenience method to append a value directly to the list + /// + /// - Parameter value: value to be added + public mutating func append(value: T) { + let node = SinglyLinkedListNode(value: value) + append(node: node) + } + + + /// Convenience method to prepend a value directly to the list + /// + /// - Parameter value: value to be added as the new head of the list + public mutating func prepend(value: T) { + let node = SinglyLinkedListNode(value: value) + prepend(node: node) + } + + + public mutating func deleteItem(at index:Int) -> T { + precondition((index >= 0) && (index < count)) + + var previous: SinglyLinkedListNode? = nil + var current = storageForWritting.head + var i = 0 + var elementToDelete: SinglyLinkedListNode + + while i < index { + previous = current + current = current?.next + i += 1 + } + + // Current is now the element to delete (at index position.tag) + elementToDelete = current! + if storage.head === current { + storageForWritting.head = current?.next + } + + if storage.tail === current { + storageForWritting.tail = previous + } + + previous?.next = current?.next + + return elementToDelete.value + } + + + + // MARK: SEARCH + + /// Returns the node located at the k-th to last position + /// + /// - Parameter kthToLast: 1 <= k <= N + private func find(kthToLast: UInt, startingAt node: SinglyLinkedListNode?, count: UInt) -> SinglyLinkedListNode? { + guard kthToLast <= count else { + return nil + } + + guard (node != nil) else { + return nil + } + + let i = (count - kthToLast) + + if i == 0 { + return node + } + + return find(kthToLast: kthToLast, startingAt: node?.next, count: (count - 1)) + } + + + /// Returns the kth-to-last element in the list + /// + /// - Parameter kthToLast: Reversed ordinal number of the node to fetch. + public func find(kthToLast: UInt) -> SinglyLinkedListNode? { + return find(kthToLast: kthToLast, startingAt: storage.head, count: UInt(count)) + } + +} + + + +// MARK:- Private Methods + +private extension SinglyLinkedList { + + /// Adds a new node to the current head. This method can easily break value semantics. It is left + /// for internal use. + /// + /// - Parameter node: the node that will be the new head of the list. + private mutating func prepend(node: SinglyLinkedListNode) { + let (tailFromNewNode, _) = findTail(in: node) + tailFromNewNode.next = storageForWritting.head + storageForWritting.head = node + + if storage.tail == nil { + storageForWritting.tail = tailFromNewNode + } + } + + /// Appends a new node to the list. This method can easily break value semantics. It is left + /// for internal use. + /// - Discussion: If the node to be inserted contains a loop, the node is appended but tail is set to nil. + /// This is a private method, therefore this can only happen directly under the control of this class. + /// - Parameter node: node to be appended. (It can be a list, even contain loops). + private mutating func append(node: SinglyLinkedListNode) { + if storage.tail != nil { + // Copy on write: we are about to modify next a property in + // a potentially shared node. Make sure it's new if necessary. + storageForWritting.tail?.next = node + let (tail, _) = findTail(in: node) + storageForWritting.tail = tail // There + } else { + // This also means that there's no head. + // Otherwise the state would be inconsistent. + // This will be checked when adding and deleting nodes. + storageForWritting.head = node + let (tail, _) = findTail(in: node) + storageForWritting.tail = tail // There + } + } + + /// Creates a copy of the linked list in a diffent memory location. + /// + /// - Returns: The copy to a new storage or a reference to the old one if no copy was necessary. + private func copyStorage() -> IndirectStorage { + // If the list is empty, next time an item will be created, it won't affect + // other instances of the list that came from copies derived from value types. + // like assignments or parameters + guard (storage.head != nil) && (storage.tail != nil) else { + return IndirectStorage(head: nil, tail: nil) + } + + // Create a new position in memory. + // Note that we are shallow copying the value. If it was reference type + // we just make a copy of the reference. + let copiedHead = SinglyLinkedListNode(value: storage.head!.value) + var previousCopied: SinglyLinkedListNode = copiedHead + + // Iterate through current list of nodes and copy them. + var current: SinglyLinkedListNode? = storage.head?.next + + while current != nil { + // Create a copy + let currentCopy = SinglyLinkedListNode(value: current!.value) + + // Create links + previousCopied.next = currentCopy + + // Update pointers + current = current?.next + previousCopied = currentCopy + } + + return IndirectStorage(head: copiedHead, tail: previousCopied) + } +} + + + +// MARK:- Extensions when comparable + +extension SinglyLinkedList where T: Comparable { + /// Deletes node containing a given value + /// + /// - Parameter v: value of the node to be deleted. + public mutating func deleteNode(withValue v: T) { + + guard storage.head != nil else { + return + } + + var previous: SinglyLinkedListNode? = nil + var current = storage.head + + while (current != nil) && (current?.value != v) { + previous = current + current = current?.next + } + + if let foundNode = current { + + if storage.head === foundNode { + storageForWritting.head = foundNode.next + } + + if storage.tail === foundNode { + storage.tail = previous + } + + previous?.next = foundNode.next + foundNode.next = nil + } + } + + + /// Deletes duplicates without using additional structures like a set to keep track the visited nodes. + /// - Complexity: O(N^2) + public mutating func deleteDuplicatesInPlace() { + // Copy on write: this updates storage if necessary. + var current = storageForWritting.head + + while current != nil { + var previous: SinglyLinkedListNode? = current + var next = current?.next + + while next != nil { + if current?.value == next?.value { + + if storage.head === next { + storage.head = next?.next + } + + if storage.tail === next { + storage.tail = previous + } + + // Delete next + previous?.next = next?.next + } + previous = next + next = next?.next + } + current = current?.next + } + } +} + + + +// MARK: - COLLECTION - + +extension SinglyLinkedList : Collection { + + public typealias Index = SinglyLinkedListIndex + + public var startIndex: Index { + get { + return SinglyLinkedListIndex(node: storage.head, tag: 0) + } + } + + public var endIndex: Index { + get { + if let h = storage.head { + let (_, numberOfElements) = findTail(in: h) + return SinglyLinkedListIndex(node: h, tag: numberOfElements) + } else { + return SinglyLinkedListIndex(node: nil, tag: startIndex.tag) + } + } + } + + public subscript(position: Index) -> T { + get { + return position.node!.value + } + } + + public func index(after idx: Index) -> Index { + return SinglyLinkedListIndex(node: idx.node?.next, tag: idx.tag+1) + } +} + + + +// MARK: - EXPRESSIBLE-BY-ARRAY-LITERAL - + +extension SinglyLinkedList : ExpressibleByArrayLiteral { + public typealias Element = T + + public init(arrayLiteral elements: Element...) { + var headSet = false + var current : SinglyLinkedListNode? + var numberOfElements = 0 + storage = IndirectStorage() + + for element in elements { + + numberOfElements += 1 + + if headSet == false { + storage.head = SinglyLinkedListNode(value: element) + current = storage.head + headSet = true + } else { + let newNode = SinglyLinkedListNode(value: element) + current?.next = newNode + current = newNode + } + } + storage.tail = current + } +} + + + +// MARK: - FORWARD-INDEX - + +public struct SinglyLinkedListIndex : Comparable { + fileprivate let node: SinglyLinkedListNode? + fileprivate let tag: Int + + public static func==(lhs: SinglyLinkedListIndex, rhs: SinglyLinkedListIndex) -> Bool { + return (lhs.tag == rhs.tag) + } + + public static func< (lhs: SinglyLinkedListIndex, rhs: SinglyLinkedListIndex) -> Bool { + return (lhs.tag < rhs.tag) + } +} + + +extension SinglyLinkedList where T : KeyValuePair { + + public func find(elementWithKey key: T.K) -> T? { + let searchResults = filter { (keyValuePair) -> Bool in + return keyValuePair.key == key + } + + return searchResults.first + } +} + +// MARK: - HELPERS - + +func findTail(in node: SinglyLinkedListNode) -> (tail: SinglyLinkedListNode, count: Int) { + // Assign the tail + // Note that the passed node can already be linking to other nodes, + // so the tail needs to be calculated. + var current: SinglyLinkedListNode? = node + var count = 1 + + while current?.next != nil { + current = current?.next + count += 1 + } + + if current != nil { + return (tail: current!, count: count) + } else { + return (tail: node, count: 1) + } +} diff --git a/Singly Linked List/Tests/Tests.xcodeproj/project.pbxproj b/Singly Linked List/Tests/Tests.xcodeproj/project.pbxproj new file mode 100644 index 000000000..8dddf6498 --- /dev/null +++ b/Singly Linked List/Tests/Tests.xcodeproj/project.pbxproj @@ -0,0 +1,280 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 006104711F361359007A6F50 /* SinglyLinkedListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006104701F361359007A6F50 /* SinglyLinkedListTests.swift */; }; + 006104791F36144E007A6F50 /* KeyValuePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006104761F36144E007A6F50 /* KeyValuePair.swift */; }; + 0061047B1F36144E007A6F50 /* SinglyLinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006104781F36144E007A6F50 /* SinglyLinkedList.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0061046D1F361359007A6F50 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 006104701F361359007A6F50 /* SinglyLinkedListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglyLinkedListTests.swift; sourceTree = ""; }; + 006104721F361359007A6F50 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 006104761F36144E007A6F50 /* KeyValuePair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KeyValuePair.swift; path = ../../KeyValuePair.swift; sourceTree = ""; }; + 006104781F36144E007A6F50 /* SinglyLinkedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SinglyLinkedList.swift; path = ../../SinglyLinkedList.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0061046A1F361359007A6F50 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 006104621F36104C007A6F50 = { + isa = PBXGroup; + children = ( + 0061046F1F361359007A6F50 /* Tests */, + 0061046E1F361359007A6F50 /* Products */, + ); + sourceTree = ""; + }; + 0061046E1F361359007A6F50 /* Products */ = { + isa = PBXGroup; + children = ( + 0061046D1F361359007A6F50 /* Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 0061046F1F361359007A6F50 /* Tests */ = { + isa = PBXGroup; + children = ( + 006104701F361359007A6F50 /* SinglyLinkedListTests.swift */, + 006104761F36144E007A6F50 /* KeyValuePair.swift */, + 006104781F36144E007A6F50 /* SinglyLinkedList.swift */, + 006104721F361359007A6F50 /* Info.plist */, + ); + path = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0061046C1F361359007A6F50 /* Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 006104731F361359007A6F50 /* Build configuration list for PBXNativeTarget "Tests" */; + buildPhases = ( + 006104691F361359007A6F50 /* Sources */, + 0061046A1F361359007A6F50 /* Frameworks */, + 0061046B1F361359007A6F50 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Tests; + productName = Tests; + productReference = 0061046D1F361359007A6F50 /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 006104631F36104C007A6F50 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 0830; + TargetAttributes = { + 0061046C1F361359007A6F50 = { + CreatedOnToolsVersion = 8.3.3; + LastSwiftMigration = 0900; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 006104661F36104C007A6F50 /* Build configuration list for PBXProject "Tests" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 006104621F36104C007A6F50; + productRefGroup = 0061046E1F361359007A6F50 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0061046C1F361359007A6F50 /* Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0061046B1F361359007A6F50 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 006104691F361359007A6F50 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 006104711F361359007A6F50 /* SinglyLinkedListTests.swift in Sources */, + 0061047B1F36144E007A6F50 /* SinglyLinkedList.swift in Sources */, + 006104791F36144E007A6F50 /* KeyValuePair.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 006104671F36104C007A6F50 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + 006104681F36104C007A6F50 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + 006104741F361359007A6F50 /* 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++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + 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; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 006104751F361359007A6F50 /* 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++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + 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; + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 006104661F36104C007A6F50 /* Build configuration list for PBXProject "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 006104671F36104C007A6F50 /* Debug */, + 006104681F36104C007A6F50 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 006104731F361359007A6F50 /* Build configuration list for PBXNativeTarget "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 006104741F361359007A6F50 /* Debug */, + 006104751F361359007A6F50 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 006104631F36104C007A6F50 /* Project object */; +} diff --git a/Singly Linked List/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Singly Linked List/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..6c0ea8493 --- /dev/null +++ b/Singly Linked List/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Singly Linked List/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Singly Linked List/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme new file mode 100644 index 000000000..a3659d5ab --- /dev/null +++ b/Singly Linked List/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Singly Linked List/Tests/Tests/Info.plist b/Singly Linked List/Tests/Tests/Info.plist new file mode 100644 index 000000000..6c6c23c43 --- /dev/null +++ b/Singly Linked List/Tests/Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Singly Linked List/Tests/Tests/SinglyLinkedListTests.swift b/Singly Linked List/Tests/Tests/SinglyLinkedListTests.swift new file mode 100644 index 000000000..5ad8ffcc4 --- /dev/null +++ b/Singly Linked List/Tests/Tests/SinglyLinkedListTests.swift @@ -0,0 +1,249 @@ +import XCTest + + +class FindTailInNodeTests: XCTestCase { + + func testExample() { + let n1 = SinglyLinkedListNode(value: 34) + let n2 = SinglyLinkedListNode(value: 35) + let n3 = SinglyLinkedListNode(value: 36) + let n4 = SinglyLinkedListNode(value: 37) + + n1.next = n2 + n2.next = n3 + n3.next = n4 + + XCTAssertTrue(findTail(in: n1).tail === n4) + XCTAssertTrue(findTail(in: n2).tail === n4) + XCTAssertTrue(findTail(in: n4).tail === n4) + XCTAssertTrue(findTail(in: n4).tail === n4) + } +} + +class SinglyLinkedListTests: XCTestCase { + + func testAppendOneNodeFromEmptyList() { + var list = SinglyLinkedList() + list.append(value: 34) + XCTAssertTrue(list.first == 34) + XCTAssertTrue(list.count == 1, "Found \(list.count)") + } + + func testAppendMultipleNodesFromEmptyList() { + var list = SinglyLinkedList() + list.append(value: 34) + list.append(value: 35) + list.append(value: 36) + list.append(value: 34) + XCTAssertTrue(list.first == 34) + let second = list.index(list.startIndex, offsetBy: 1) + let third = list.index(list.startIndex, offsetBy: 2) + let fouth = list.index(list.startIndex, offsetBy: 3) + XCTAssertTrue(list[second] == 35) + XCTAssertTrue(list[third] == 36) + XCTAssertTrue(list[fouth] == 34) + XCTAssertTrue(list.count == 4, "Found \(list.count)") + } + + func testDelete() { + var list: SinglyLinkedList = [1] + list.append(value: 2) + list.append(value: 3) + list.append(value: 4) + list.append(value: 5) + + list.deleteNode(withValue: 1) + var second = list.index(list.startIndex, offsetBy: 1) + var third = list.index(list.startIndex, offsetBy: 2) + XCTAssertTrue(list.first == 2) + XCTAssertTrue(list[second] == 3) + XCTAssertTrue(list[third] == 4) + XCTAssertTrue(list.last == 5) + XCTAssertTrue(list.count == 4) + + list.deleteNode(withValue: 5) + second = list.index(list.startIndex, offsetBy: 1) + third = list.index(list.startIndex, offsetBy: 2) + XCTAssertTrue(list.first == 2) + XCTAssertTrue(list[second] == 3) + XCTAssertTrue(list.last == 4) + XCTAssertTrue(list.count == 3) + + list.deleteNode(withValue: 3) + XCTAssertTrue(list.first == 2) + XCTAssertTrue(list.last == 4) + XCTAssertTrue(list.count == 2) + + list.deleteNode(withValue: 2) + XCTAssertTrue(list.first == 4) + XCTAssertTrue(list.last == 4) + XCTAssertTrue(list.count == 1) + + list.deleteNode(withValue: 4) + XCTAssertTrue(list.first == nil) + XCTAssertTrue(list.last == nil) + XCTAssertTrue(list.count == 0) + } + + func testDeleteDuplicatesInPlace() { + var list: SinglyLinkedList = [1] + list.append(value: 2) + list.append(value: 2) + list.append(value: 3) + list.append(value: 5) + list.append(value: 2) + list.append(value: 4) + list.append(value: 2) + list.append(value: 5) + + list.deleteDuplicatesInPlace() + let second = list.index(list.startIndex, offsetBy: 1) + let third = list.index(list.startIndex, offsetBy: 2) + let fourth = list.index(list.startIndex, offsetBy: 3) + XCTAssertTrue(list.first == 1) + XCTAssertTrue(list[second] == 2) + XCTAssertTrue(list[third] == 3) + XCTAssertTrue(list[fourth] == 5) + XCTAssertTrue(list.last == 4) + XCTAssertTrue(list.count == 5) + } + + func testFindKthToLast() { + let list: SinglyLinkedList = [2,2,3,5,2,4,2,5] + XCTAssertTrue(list.find(kthToLast: 1)?.value == 5) + XCTAssertTrue(list.find(kthToLast: 2)?.value == 2) + XCTAssertTrue(list.find(kthToLast: 3)?.value == 4) + XCTAssertTrue(list.find(kthToLast: 4)?.value == 2) + XCTAssertTrue(list.find(kthToLast: 5)?.value == 5) + XCTAssertTrue(list.find(kthToLast: 6)?.value == 3) + XCTAssertTrue(list.find(kthToLast: 7)?.value == 2) + XCTAssertTrue(list.find(kthToLast: 8)?.value == 2) + XCTAssertTrue(list.find(kthToLast: 9)?.value == nil) + } + + func testConstructorFromArrayLiteralWhenEmpty() { + let list: SinglyLinkedList = [] + XCTAssertTrue(list.first == nil) + XCTAssertTrue(list.last == nil) + XCTAssertTrue(list.count == 0, "Found \(list.count)") + } + + func testConstructorFromArrayLiteralWithSingleElement() { + let list: SinglyLinkedList = [5] + XCTAssertTrue(list.first == 5) + XCTAssertTrue(list.last == 5) + XCTAssertTrue(list.count == 1, "Found \(list.count)") + } + + func testAppendValue() { + var list = SinglyLinkedList() + list.append(value: 1) + list.append(value: 1) + list.append(value: 2) + list.append(value: 2) + list.append(value: 4) + + let result = string(from: list) + XCTAssertTrue(result == "11224", "Found \(result)") + XCTAssertTrue(list.count == 5, "Found \(list.count)") + XCTAssertTrue(list.last == 4, "Found \(String(describing: list.last))") + } + + func testPrependValue() { + var list = SinglyLinkedList() + list.prepend(value: 1) + list.prepend(value: 2) + list.prepend(value: 3) + list.prepend(value: 4) + list.prepend(value: 5) + list.prepend(value: 6) + + let result = string(from: list) + XCTAssertTrue(result == "654321", "Found \(result)") + XCTAssertTrue(list.count == 6, "Found \(list.count)") + XCTAssertTrue(list.last == 1, "Found \(String(describing: list.last))") + } + + func testDeleteHeadInListWithMultipleItems() { + var list: SinglyLinkedList = [1,2,3,4,5,6,7,8] + + let _ = list.deleteItem(at: 0) + let result = string(from: list) + XCTAssertTrue(result == "2345678", "Found \(result)") + XCTAssertTrue(list.first == 2, "Found \(String(describing: list.first))") + XCTAssertTrue(list.count == 7, "Found \(list.count)") + } + + func testDeleteTailInListWithMultipleItems() { + var list: SinglyLinkedList = [1,2,3,4,5,6,7,8] + + let _ = list.deleteItem(at: 7) + let result = string(from: list) + XCTAssertTrue(result == "1234567", "Found \(result)") + XCTAssertTrue(list.last == 7, "Found \(String(describing: list.last))") + XCTAssertTrue(list.count == 7, "Found \(list.count)") + } + + func testDeleteItemInListWithMultipleItems() { + var list: SinglyLinkedList = [1,2,3,4,5,6,7,8] + + let _ = list.deleteItem(at: 4) + let result = string(from: list) + XCTAssertTrue(result == "1234678", "Found \(result)") + XCTAssertTrue(list.first == 1, "Found \(String(describing: list.first))") + XCTAssertTrue(list.last == 8, "Found \(String(describing: list.last))") + XCTAssertTrue(list.count == 7, "Found \(list.count)") + } + + func testDeleteHeadInListWithSingleElement() { + var list: SinglyLinkedList = [1] + + let _ = list.deleteItem(at: 0) + let result = string(from: list) + XCTAssertTrue(result == "", "Found \(result)") + XCTAssertTrue(list.first == nil, "Found \(String(describing: list.first))") + XCTAssertTrue(list.last == nil, "Found \(String(describing: list.last))") + XCTAssertTrue(list.count == 0, "Found \(list.count)") + } + + func testDirectIndexAccess() { + let list: SinglyLinkedList = [1,2,3,4,5,6,7,8] + let fifthElementIndex = list.index(list.startIndex, offsetBy: 5) + XCTAssertTrue(list[fifthElementIndex] == 6 , "Found \(list.count)") + } + + + func string(from list: SinglyLinkedList) -> String { + var result = "" + var iterator = list.makeIterator() + while let current = iterator.next() { + result += String(describing: current) + } + + return result + } + + func testCopyOnWriteUsingLiterals() { + var l1: SinglyLinkedList = [1,2,3,4,5,6,7,8] + l1.append(value: 0) + var l2 = l1 + + _ = l2.deleteItem(at: 3) + XCTAssertTrue(l1.count == 9) + XCTAssertTrue(l2.count == 8) + + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + _ = l1.deleteItem(at: 0) + + XCTAssertTrue(l1.count == 0) + XCTAssertTrue(l2.count == 8) + } + +} diff --git a/Skip-List/SkipList.playground/Contents.swift b/Skip-List/SkipList.playground/Contents.swift new file mode 100644 index 000000000..55b9d3a23 --- /dev/null +++ b/Skip-List/SkipList.playground/Contents.swift @@ -0,0 +1,20 @@ +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + +// SkipList is ready for Swift 4. +// TODO: Add Test + +let k = SkipList() +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 new file mode 100644 index 000000000..186286977 --- /dev/null +++ b/Skip-List/SkipList.playground/Sources/SkipList.swift @@ -0,0 +1,270 @@ +// The MIT License (MIT) + +// Copyright (c) 2016 Mike Taghavi (mitghi[at]me.com) + +// 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 + +// Stack from : https://github.com/raywenderlich/swift-algorithm-club/tree/master/Stack +public struct Stack { + fileprivate var array: [T] = [] + + public var isEmpty: Bool { + return array.isEmpty + } + + public var count: Int { + return array.count + } + + public mutating func push(_ element: T) { + array.append(element) + } + + public mutating func pop() -> T? { + return array.popLast() + } + + public func peek() -> T? { + return array.last + } +} + +extension Stack: Sequence { + public func makeIterator() -> AnyIterator { + var curr = self + return AnyIterator { curr.pop() } + } +} + +private func coinFlip() -> Bool { + return arc4random_uniform(2) == 1 +} + +public class DataNode { + public typealias Node = DataNode + + var data: Payload? + fileprivate var key: Key? + var next: Node? + var down: Node? + + public init(key: Key, data: Payload) { + self.key = key + self.data = data + } + + public init(asHead head: Bool) {} + +} + +open class SkipList { + public typealias Node = DataNode + + fileprivate(set) var head: Node? + + public init() {} + +} + +// MARK: - Search lanes for a node with a given key + +extension SkipList { + + func findNode(key: Key) -> Node? { + var currentNode: Node? = head + var isFound: Bool = false + + while !isFound { + if let node = currentNode { + + switch node.next { + case .none: + + currentNode = node.down + case .some(let value) where value.key != nil: + + if value.key == key { + isFound = true + break + } else { + if key < value.key! { + currentNode = node.down + } else { + currentNode = node.next + } + } + + default: + continue + } + + } else { + break + } + } + + if isFound { + return currentNode + } else { + return nil + } + + } + + func search(key: Key) -> Payload? { + guard let node = findNode(key: key) else { + return nil + } + + return node.next!.data + } + +} + +// MARK: - Insert a node into lanes depending on skip list status ( bootstrap base-layer if head is empty / start insertion from current head ). + +extension SkipList { + private func bootstrapBaseLayer(key: Key, data: Payload) { + head = Node(asHead: true) + var node = Node(key: key, data: data) + + head!.next = node + + var currentTopNode = node + + while coinFlip() { + let newHead = Node(asHead: true) + node = Node(key: key, data: data) + node.down = currentTopNode + newHead.next = node + newHead.down = head + head = newHead + currentTopNode = node + } + + } + + private func insertItem(key: Key, data: Payload) { + var stack = Stack() + var currentNode: Node? = head + + while currentNode != nil { + + if let nextNode = currentNode!.next { + if nextNode.key! > key { + stack.push(currentNode!) + currentNode = currentNode!.down + } else { + currentNode = nextNode + } + + } else { + stack.push(currentNode!) + currentNode = currentNode!.down + } + + } + + let itemAtLayer = stack.pop() + var node = Node(key: key, data: data) + node.next = itemAtLayer!.next + itemAtLayer!.next = node + var currentTopNode = node + + while coinFlip() { + if stack.isEmpty { + let newHead = Node(asHead: true) + + node = Node(key: key, data: data) + node.down = currentTopNode + newHead.next = node + newHead.down = head + head = newHead + currentTopNode = node + + } else { + let nextNode = stack.pop() + + node = Node(key: key, data: data) + node.down = currentTopNode + node.next = nextNode!.next + nextNode!.next = node + currentTopNode = node + } + } + } + + public func insert(key: Key, data: Payload) { + if head != nil { + if let node = findNode(key: key) { + // replace, in case of key already exists. + var currentNode = node.next + while currentNode != nil && currentNode!.key == key { + currentNode!.data = data + currentNode = currentNode!.down + } + } else { + insertItem(key: key, data: data) + } + + } else { + bootstrapBaseLayer(key: key, data: data) + } + } + +} + +// MARK: - Remove a node with a given key. First, find its position in layers at the top, then remove it from each lane by traversing down to the base layer. + +extension SkipList { + public func remove(key: Key) { + guard let item = findNode(key: key) else { + return + } + + var currentNode = Optional(item) + + while currentNode != nil { + let node = currentNode!.next + + if node!.key != key { + currentNode = node + continue + } + + let nextNode = node!.next + + currentNode!.next = nextNode + currentNode = currentNode!.down + + } + + } +} + +// MARK: - Get associated payload from a node with a given key. + +extension SkipList { + + public func get(key: Key) -> Payload? { + return search(key: key) + } +} diff --git a/Skip-List/SkipList.playground/contents.xcplayground b/Skip-List/SkipList.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Skip-List/SkipList.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Skip-List/SkipList.playground/playground.xcworkspace/contents.xcworkspacedata b/Skip-List/SkipList.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Skip-List/SkipList.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Skip-List/SkipList.swift b/Skip-List/SkipList.swift index 43173f236..8a1959f70 100644 --- a/Skip-List/SkipList.swift +++ b/Skip-List/SkipList.swift @@ -50,18 +50,12 @@ public struct Stack { extension Stack: Sequence { public func makeIterator() -> AnyIterator { var curr = self - return AnyIterator { _ -> T? in - return curr.pop() - } + return AnyIterator { curr.pop() } } } private func coinFlip() -> Bool { - #if os(Linux) - return random() % 2 == 0 - #elseif os(OSX) - return arc4random_uniform(2) == 1 - #endif + return arc4random_uniform(2) == 1 } public class DataNode { @@ -218,10 +212,10 @@ 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. + // replace, in case of key already exists. var currentNode = node.next while currentNode != nil && currentNode!.key == key { currentNode!.data = data @@ -274,3 +268,4 @@ extension SkipList { return search(key: key) } } + 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/Contents.swift b/Slow Sort/SlowSort.playground/Contents.swift new file mode 100644 index 000000000..54c1e61ea --- /dev/null +++ b/Slow Sort/SlowSort.playground/Contents.swift @@ -0,0 +1,4 @@ +var numberList = [1, 12, 9, 17, 13, 12] + +slowSort(0, numberList.count-1, &numberList) +print(numberList) diff --git a/Slow Sort/SlowSort.playground/Sources/SlowSort.swift b/Slow Sort/SlowSort.playground/Sources/SlowSort.swift new file mode 100644 index 000000000..cf09dcc5d --- /dev/null +++ b/Slow Sort/SlowSort.playground/Sources/SlowSort.swift @@ -0,0 +1,15 @@ +import Foundation + +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) + if numberList[j] < numberList[m] { + let temp = numberList[j] + numberList[j] = numberList[m] + numberList[m] = temp + } + slowSort(i, j-1, &numberList) +} diff --git a/Slow Sort/SlowSort.playground/contents.xcplayground b/Slow Sort/SlowSort.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Slow Sort/SlowSort.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Slow Sort/SlowSort.playground/playground.xcworkspace/contents.xcworkspacedata b/Slow Sort/SlowSort.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Slow Sort/SlowSort.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Slow Sort/SlowSort.swift b/Slow Sort/SlowSort.swift index 6396e8b38..968f6841b 100644 --- a/Slow Sort/SlowSort.swift +++ b/Slow Sort/SlowSort.swift @@ -1,27 +1,20 @@ // // SlowSort.swift -// +// // // Created by Pope Lukas Schramm (Dabendorf Orthodox Religion) on 16-07-16. // // -var numberList = [1, 12, 9, 17, 13, 12] - -public func slowsort(_ i: Int, _ j: Int) { - if i>=j { - return - } - let m = (i+j)/2 - slowsort(i, m) - slowsort(m+1, j) - if numberList[j] < numberList[m] { - let temp = numberList[j] - numberList[j] = numberList[m] - numberList[m] = temp - } - slowsort(i, j-1) +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) + if numberList[j] < numberList[m] { + let temp = numberList[j] + numberList[j] = numberList[m] + numberList[m] = temp + } + slowSort(i, j-1, &numberList) } - -slowsort(0, numberList.count-1) -print(numberList) diff --git a/Sorted Set/README.markdown b/Sorted Set/README.markdown new file mode 100644 index 000000000..a8b5ad1ea --- /dev/null +++ b/Sorted Set/README.markdown @@ -0,0 +1,334 @@ +# Sorted Set + +## Sorted Array Version + +An Sorted Set is a collection of unique items in sorted order. Items are usually sorted from least to greatest. + +The Sorted Set data type is a hybrid of: + +- a [Set](https://en.wikipedia.org/wiki/Set_%28mathematics%29), a collection of unique items where the order does not matter, and +- a [Sequence](https://en.wikipedia.org/wiki/Sequence), an sorted list of items where each item may appear more than once. + +It's important to keep in mind that two items can have the same *value* but still may not be equal. For example, we could define "a" and "z" to have the same value (their lengths), but clearly "a" != "z". + +## Why use an sorted set? + +Sorted Sets should be considered when you need to keep your collection sorted at all times, and you do lookups on the collection much more frequently than inserting or deleting items. Many of the lookup operations for an Sorted Set are **O(1)**. + +A good example would be keeping track of the rankings of players in a scoreboard (see example 2 below). + +#### These are sorted sets + +A set of integers: + + [1, 2, 3, 6, 8, 10, 1000] + +A set of strings: + + ["a", "is", "set", "this"] + +The "value" of these strings could be their text content, but also for example their length. + +#### These are not sorted sets + +This set violates the property of uniqueness: + + [1, 1, 2, 3, 5, 8] + +This set violates the sorted property: + + [1, 11, 2, 3] + +## The code + +We'll start by creating our internal representation for the Sorted Set. Since the idea of a set is similar to that of an array, we will use an array to represent our set. Furthermore, since we'll need to keep the set sorted, we need to be able to compare the individual elements. Thus, any type must conform to the [Comparable Protocol](https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Comparable_Protocol/index.html). + +```swift +public struct SortedSet { + private var internalSet = [T]() + + // Returns the number of elements in the SortedSet. + public var count: Int { + return internalSet.count + } + ... +``` + +Lets take a look at the `insert()` function first. This first checks if the item already exists in the collection. If so, it returns and does not insert the item. Otherwise, it will insert the item through straightforward iteration. + +```swift + public mutating func insert(_ item: T){ + if exists(item) { + return // don't add an item if it already exists + } + + // Insert new the item just before the one that is larger. + for i in 0.. item { + internalSet.insert(item, at: i) + return + } + } + + // Append to the back if the new item is greater than any other in the set. + internalSet.append(item) + } +``` + +As we'll see later on, checking if the item is already in the set has an efficiency of **O(log(n) + k)** where **k** is the number of items with the same value as the item we are inserting. + +To insert the new item, the `for` loop starts from the beginning of the array, and checks to see if each item is larger than the item we want to insert. Once we find such an item, we insert the new one into its place. This shifts the rest of the array over to the right by 1 position. This loop is at worst **O(n)**. + +The total performance of the `insert()` function is therefore **O(n)**. + +Next up is the `remove()` function: + +```swift + public mutating func remove(_ item: T) { + if let index = index(of: item) { + internalSet.remove(at: index) + } + } +``` + +First this checks if the item exists and then removes it from the array. Because of the `removeAtIndex()` function, the efficiency for remove is **O(n)**. + +The next function is `indexOf()`, which takes in an object of type `T` and returns the index of the corresponding item if it is in the set, or `nil` if it is not. Since our set is sorted, we can use a binary search to quickly search for the item. + +```swift + public func index(of item: T) -> Int? { + var leftBound = 0 + var rightBound = count - 1 + + while leftBound <= rightBound { + let mid = leftBound + ((rightBound - leftBound) / 2) + + if internalSet[mid] > item { + rightBound = mid - 1 + } else if internalSet[mid] < item { + leftBound = mid + 1 + } else if internalSet[mid] == item { + return mid + } else { + // see below + } + } + return nil + } +``` + +> **Note:** If you are not familiar with the concept of binary search, we have an [article that explains all about it](../Binary%20Search). + +However, there is an important issue to deal with here. Recall that two objects can be unequal yet still have the same "value" for the purposes of comparing them. Since a set can contain multiple items with the same value, it is important to check that the binary search has landed on the correct item. + +For example, consider this sorted set of `Player` objects. Each `Player` has a name and a number of points: + + [ ("Bill", 50), ("Ada", 50), ("Jony", 50), ("Steve", 200), ("Jean-Louis", 500), ("Woz", 1000) ] + +We want the set to be sorted by points, from low to high. Multiple players can have the same number of points. The name of the player is not important for this ordering. However, the name *is* important for retrieving the correct item. + +Let's say we do `indexOf(bill)` where `bill` is player object `("Bill", 50)`. If we did a traditional binary search we'd land on index 2, which is the object `("Jony", 50)`. The value 50 matches, but it's not the object we're looking for! + +Therefore, we also need to check the items with the same value to the right and left of the midpoint. The code to check the left and right side looks like this: + +```swift + // Check to the right. + for j in mid.stride(to: count - 1, by: 1) { + if internalSet[j + 1] == item { + return j + 1 + } else if internalSet[j] < internalSet[j + 1] { + break + } + } + + // Check to the left. + for j in mid.stride(to: 0, by: -1) { + if internalSet[j - 1] == item { + return j - 1 + } else if internalSet[j] > internalSet[j - 1] { + break + } + } + + return nil +``` + +These loops start at the current `mid` value and then look at the neighboring values until we've found the correct object. + +The combined runtime for `indexOf()` is **O(log(n) + k)** where **n** is the length of the set, and **k** is the number of items with the same *value* as the one that is being searched for. + +Since the set is sorted, the following operations are all **O(1)**: + +```swift + // Returns the 'maximum' or 'largest' value in the set. + public func max() -> T? { + return count == 0 ? nil : internalSet[count - 1] + } + + // Returns the 'minimum' or 'smallest' value in the set. + public func min() -> T? { + return count == 0 ? nil : internalSet[0] + } + + // Returns the k-th largest element in the set, if k is in the range + // [1, count]. Returns nil otherwise. + public func kLargest(_ k: Int) -> T? { + return k > count || k <= 0 ? nil : internalSet[count - k] + } + + // Returns the k-th smallest element in the set, if k is in the range + // [1, count]. Returns nil otherwise. + public func kSmallest(_ k: Int) -> T? { + return k > count || k <= 0 ? nil : internalSet[k - 1] + } +``` + +## Examples + +Below are a few examples that can be found in the playground file. + +### Example 1 + +Here we create a set with random Integers. Printing the largest/smallest 5 numbers in the set is fairly easy. + +```swift +// Example 1 with type Int +var mySet = SortedSet() + +// Insert random numbers into the set +for _ in 0..<50 { + mySet.insert(randomNum(50, max: 500)) +} + +print(mySet) + +print(mySet.max()) +print(mySet.min()) + +// Print the 5 largest values +for k in 1...5 { + print(mySet.kLargest(k)) +} + +// Print the 5 lowest values +for k in 1...5 { + print(mySet.kSmallest(k)) +} +``` + +### Example 2 + +In this example we take a look at something a bit more interesting. We define a `Player` struct as follows: + +```swift +public struct Player: Comparable { + public var name: String + public var points: Int +} +``` + +The `Player` also gets its own `==` and `<` operators. The `<` operator is used to determine the sort order of the set, while `==` determines whether two objects are really equal. + +Note that `==` compares both the name and the points: + +```swifr +func ==(x: Player, y: Player) -> Bool { + return x.name == y.name && x.points == y.points +} +``` + +But `<` only compares the points: + +```swift +func <(x: Player, y: Player) -> Bool { + return x.points < y.points +} +``` + +Therefore, two `Player`s can each have the same value (the number of points), but are not guaranteed to be equal (they can have different names). + +We create a new set and insert 20 random players. The `Player()` constructor gives each player a random name and score: + +```swift +var playerSet = SortedSet() + +// Populate the set with random players. +for _ in 0..<20 { + playerSet.insert(Player()) +} +``` + +Insert another player: + +```swift +var anotherPlayer = Player() +playerSet.insert(anotherPlayer) +``` + +Now we use the `indexOf()` function to find out what rank `anotherPlayer` is. + +```swift +let level = playerSet.count - playerSet.indexOf(anotherPlayer)! +print("\(anotherPlayer.name) is ranked at level \(level) with \(anotherPlayer.points) points") +``` + +### Example 3 + +The final example demonstrates the need to look for the right item even after the binary search has completed. + +We insert 9 players into the set: + +```swift +var repeatedSet = SortedSet() + +repeatedSet.insert(Player(name: "Player 1", points: 100)) +repeatedSet.insert(Player(name: "Player 2", points: 100)) +repeatedSet.insert(Player(name: "Player 3", points: 100)) +repeatedSet.insert(Player(name: "Player 4", points: 100)) +repeatedSet.insert(Player(name: "Player 5", points: 100)) +repeatedSet.insert(Player(name: "Player 6", points: 50)) +repeatedSet.insert(Player(name: "Player 7", points: 200)) +repeatedSet.insert(Player(name: "Player 8", points: 250)) +repeatedSet.insert(Player(name: "Player 9", points: 25)) +``` + +Notice how several of these players have the same value of 100 points. + +The set looks something like this: + + [Player 9, Player 6, Player 1, Player 2, Player 3, Player 4, Player 5, Player 7, Player 8] + +The next line looks for `Player 2`: + +```swift +print(repeatedSet.index(of: Player(name: "Player 2", points: 100))) +``` + +After the binary search finishes, the value of `mid` is at index 5: + + [Player 9, Player 6, Player 1, Player 2, Player 3, Player 4, Player 5, Player 7, Player 8] + mid + +However, this is not `Player 2`. Both `Player 4` and `Player 2` have the same points, but a different name. The binary search only looked at the points, not the name. + +But we do know that `Player 2` must be either to the immediate left or the right of `Player 4`, so we check both sides of `mid`. We only need to look at the objects with the same value as `Player 4`. The others are replaced by `X`: + + [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] + mid + +The code then first checks on the right of `mid` (where the `*` is): + + [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] + mid * + +The right side did not contain the item, so we look at the left side: + + [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] + * mid + + [X, X, Player 1, Player 2, Player 3, Player 4, Player 5, X, X] + * mid + +Finally, we've found `Player 2`, and return index 3. + +*Written By Zain Humayun* diff --git a/Ordered Set/OrderedSet.playground/Pages/Example 1.xcplaygroundpage/Contents.swift b/Sorted Set/SortedSet.playground/Pages/Example 1.xcplaygroundpage/Contents.swift similarity index 93% rename from Ordered Set/OrderedSet.playground/Pages/Example 1.xcplaygroundpage/Contents.swift rename to Sorted Set/SortedSet.playground/Pages/Example 1.xcplaygroundpage/Contents.swift index 2420019fc..c81f4d915 100644 --- a/Ordered Set/OrderedSet.playground/Pages/Example 1.xcplaygroundpage/Contents.swift +++ b/Sorted Set/SortedSet.playground/Pages/Example 1.xcplaygroundpage/Contents.swift @@ -1,6 +1,6 @@ //: # Example 1 with type Int -var mySet = OrderedSet() +var mySet = SortedSet() // Insert random numbers into the set for _ in 0..<50 { diff --git a/Sorted Set/SortedSet.playground/Pages/Example 1.xcplaygroundpage/timeline.xctimeline b/Sorted Set/SortedSet.playground/Pages/Example 1.xcplaygroundpage/timeline.xctimeline new file mode 100644 index 000000000..dffc42643 --- /dev/null +++ b/Sorted Set/SortedSet.playground/Pages/Example 1.xcplaygroundpage/timeline.xctimeline @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/Ordered Set/OrderedSet.playground/Pages/Example 2.xcplaygroundpage/Contents.swift b/Sorted Set/SortedSet.playground/Pages/Example 2.xcplaygroundpage/Contents.swift similarity index 95% rename from Ordered Set/OrderedSet.playground/Pages/Example 2.xcplaygroundpage/Contents.swift rename to Sorted Set/SortedSet.playground/Pages/Example 2.xcplaygroundpage/Contents.swift index c30260fff..2408d35d2 100644 --- a/Ordered Set/OrderedSet.playground/Pages/Example 2.xcplaygroundpage/Contents.swift +++ b/Sorted Set/SortedSet.playground/Pages/Example 2.xcplaygroundpage/Contents.swift @@ -2,7 +2,7 @@ //: # Example 2 with Player objects -var playerSet = OrderedSet() +var playerSet = SortedSet() // Populate the set with random players. for _ in 0..<20 { diff --git a/Ordered Set/OrderedSet.playground/Pages/Example 3.xcplaygroundpage/Contents.swift b/Sorted Set/SortedSet.playground/Pages/Example 3.xcplaygroundpage/Contents.swift similarity index 95% rename from Ordered Set/OrderedSet.playground/Pages/Example 3.xcplaygroundpage/Contents.swift rename to Sorted Set/SortedSet.playground/Pages/Example 3.xcplaygroundpage/Contents.swift index 52f154c05..63edd11d2 100644 --- a/Ordered Set/OrderedSet.playground/Pages/Example 3.xcplaygroundpage/Contents.swift +++ b/Sorted Set/SortedSet.playground/Pages/Example 3.xcplaygroundpage/Contents.swift @@ -2,7 +2,7 @@ //: # Example 3: multiple entries with the same value -var repeatedSet = OrderedSet() +var repeatedSet = SortedSet() repeatedSet.insert(Player(name: "Player 1", points: 100)) repeatedSet.insert(Player(name: "Player 2", points: 100)) diff --git a/Ordered Set/OrderedSet.playground/Sources/Player.swift b/Sorted Set/SortedSet.playground/Sources/Player.swift similarity index 95% rename from Ordered Set/OrderedSet.playground/Sources/Player.swift rename to Sorted Set/SortedSet.playground/Sources/Player.swift index 26835b0a8..0960b2400 100644 --- a/Ordered Set/OrderedSet.playground/Sources/Player.swift +++ b/Sorted Set/SortedSet.playground/Sources/Player.swift @@ -3,12 +3,12 @@ public struct Player: Comparable { public var name: String public var points: Int - + public init() { self.name = String.random() self.points = random(min: 0, max: 5000) } - + public init(name: String, points: Int) { self.name = name self.points = points @@ -31,7 +31,7 @@ public func print(player: Player) { print("Player: \(player.name) | Points: \(player.points)") } -public func print(set: OrderedSet) { +public func print(set: SortedSet) { for i in 0.. String { let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" var randomString: String = "" - + for _ in 0.. { + private var internalSet = [T]() + + public init() { } + + // Returns the number of elements in the SortedSet. + public var count: Int { + return internalSet.count + } + + // Inserts an item. Performance: O(n) + public mutating func insert(_ item: T) { + if exists(item) { + return // don't add an item if it already exists + } + + // Insert new the item just before the one that is larger. + for i in 0.. item { + internalSet.insert(item, at: i) + return + } + } + + // Append to the back if the new item is greater than any other in the set. + internalSet.append(item) + } + + // Removes an item if it exists. Performance: O(n) + public mutating func remove(_ item: T) { + if let index = index(of: item) { + internalSet.remove(at: index) + } + } + + // Returns true if and only if the item exists somewhere in the set. + public func exists(_ item: T) -> Bool { + return index(of: item) != nil + } + + // Returns the index of an item if it exists, or -1 otherwise. + public func index(of item: T) -> Int? { + var leftBound = 0 + var rightBound = count - 1 + + while leftBound <= rightBound { + let mid = leftBound + ((rightBound - leftBound) / 2) + + if internalSet[mid] > item { + rightBound = mid - 1 + } else if internalSet[mid] < item { + leftBound = mid + 1 + } else if internalSet[mid] == item { + return mid + } else { + // When we get here, we've landed on an item whose value is equal to the + // value of the item we're looking for, but the items themselves are not + // equal. We need to check the items with the same value to the right + // and to the left in order to find an exact match. + + // Check to the right. + for j in stride(from: mid, to: count - 1, by: 1) { + if internalSet[j + 1] == item { + return j + 1 + } else if internalSet[j] < internalSet[j + 1] { + break + } + } + + // Check to the left. + for j in stride(from: mid, to: 0, by: -1) { + if internalSet[j - 1] == item { + return j - 1 + } else if internalSet[j] > internalSet[j - 1] { + break + } + } + return nil + } + } + return nil + } + + // Returns the item at the given index. + // Assertion fails if the index is out of the range of [0, count). + public subscript(index: Int) -> T { + assert(index >= 0 && index < count) + return internalSet[index] + } + + // Returns the 'maximum' or 'largest' value in the set. + public func max() -> T? { + return count == 0 ? nil : internalSet[count - 1] + } + + // Returns the 'minimum' or 'smallest' value in the set. + public func min() -> T? { + return count == 0 ? nil : internalSet[0] + } + + // Returns the k-th largest element in the set, if k is in the range + // [1, count]. Returns nil otherwise. + public func kLargest(_ k: Int) -> T? { + return k > count || k <= 0 ? nil : internalSet[count - k] + } + + // Returns the k-th smallest element in the set, if k is in the range + // [1, count]. Returns nil otherwise. + public func kSmallest(_ k: Int) -> T? { + return k > count || k <= 0 ? nil : internalSet[k - 1] + } +} + diff --git a/Sorted Set/SortedSet.playground/contents.xcplayground b/Sorted Set/SortedSet.playground/contents.xcplayground new file mode 100644 index 000000000..18c02c912 --- /dev/null +++ b/Sorted Set/SortedSet.playground/contents.xcplayground @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Sorted Set/SortedSet.playground/playground.xcworkspace/contents.xcworkspacedata b/Sorted Set/SortedSet.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Sorted Set/SortedSet.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Sorted Set/SortedSet.swift b/Sorted Set/SortedSet.swift new file mode 100644 index 000000000..69546da0d --- /dev/null +++ b/Sorted Set/SortedSet.swift @@ -0,0 +1,118 @@ +/* + An Sorted Set is a collection where all items in the set follow an ordering, + usually sorted from 'least' to 'most'. The way you value and compare items + can be user-defined. + */ +public struct SortedSet { + private var internalSet = [T]() + + public init() { } + + // Returns the number of elements in the SortedSet. + public var count: Int { + return internalSet.count + } + + // Inserts an item. Performance: O(n) + public mutating func insert(_ item: T) { + if exists(item) { + return // don't add an item if it already exists + } + + // Insert new the item just before the one that is larger. + for i in 0.. item { + internalSet.insert(item, at: i) + return + } + } + + // Append to the back if the new item is greater than any other in the set. + internalSet.append(item) + } + + // Removes an item if it exists. Performance: O(n) + public mutating func remove(_ item: T) { + if let index = index(of: item) { + internalSet.remove(at: index) + } + } + + // Returns true if and only if the item exists somewhere in the set. + public func exists(_ item: T) -> Bool { + return index(of: item) != nil + } + + // Returns the index of an item if it exists, or -1 otherwise. + public func index(of item: T) -> Int? { + var leftBound = 0 + var rightBound = count - 1 + + while leftBound <= rightBound { + let mid = leftBound + ((rightBound - leftBound) / 2) + + if internalSet[mid] > item { + rightBound = mid - 1 + } else if internalSet[mid] < item { + leftBound = mid + 1 + } else if internalSet[mid] == item { + return mid + } else { + // When we get here, we've landed on an item whose value is equal to the + // value of the item we're looking for, but the items themselves are not + // equal. We need to check the items with the same value to the right + // and to the left in order to find an exact match. + + // Check to the right. + for j in stride(from: mid, to: count - 1, by: 1) { + if internalSet[j + 1] == item { + return j + 1 + } else if internalSet[j] < internalSet[j + 1] { + break + } + } + + // Check to the left. + for j in stride(from: mid, to: 0, by: -1) { + if internalSet[j - 1] == item { + return j - 1 + } else if internalSet[j] > internalSet[j - 1] { + break + } + } + return nil + } + } + return nil + } + + // Returns the item at the given index. + // Assertion fails if the index is out of the range of [0, count). + public subscript(index: Int) -> T { + assert(index >= 0 && index < count) + return internalSet[index] + } + + // Returns the 'maximum' or 'largest' value in the set. + public func max() -> T? { + return count == 0 ? nil : internalSet[count - 1] + } + + // Returns the 'minimum' or 'smallest' value in the set. + public func min() -> T? { + return count == 0 ? nil : internalSet[0] + } + + // Returns the k-th largest element in the set, if k is in the range + // [1, count]. Returns nil otherwise. + public func kLargest(_ k: Int) -> T? { + return k > count || k <= 0 ? nil : internalSet[count - k] + } + + // Returns the k-th smallest element in the set, if k is in the range + // [1, count]. Returns nil otherwise. + public func kSmallest(_ k: Int) -> T? { + return k > count || k <= 0 ? nil : internalSet[k - 1] + } +} + diff --git a/Sparse Table/Images/idempotency.png b/Sparse Table/Images/idempotency.png new file mode 100644 index 000000000..88c8e7a8d Binary files /dev/null and b/Sparse Table/Images/idempotency.png differ diff --git a/Sparse Table/Images/query.png b/Sparse Table/Images/query.png new file mode 100644 index 000000000..fc1c42fb7 Binary files /dev/null and b/Sparse Table/Images/query.png differ diff --git a/Sparse Table/Images/query_example.png b/Sparse Table/Images/query_example.png new file mode 100644 index 000000000..c588828ee Binary files /dev/null and b/Sparse Table/Images/query_example.png differ diff --git a/Sparse Table/Images/recursion.png b/Sparse Table/Images/recursion.png new file mode 100644 index 000000000..6928e372c Binary files /dev/null and b/Sparse Table/Images/recursion.png differ diff --git a/Sparse Table/Images/recursion_examples.png b/Sparse Table/Images/recursion_examples.png new file mode 100644 index 000000000..737b95bf9 Binary files /dev/null and b/Sparse Table/Images/recursion_examples.png differ diff --git a/Sparse Table/Images/structure.png b/Sparse Table/Images/structure.png new file mode 100644 index 000000000..88902df0c Binary files /dev/null and b/Sparse Table/Images/structure.png differ diff --git a/Sparse Table/Images/structure_examples.png b/Sparse Table/Images/structure_examples.png new file mode 100644 index 000000000..69fd57218 Binary files /dev/null and b/Sparse Table/Images/structure_examples.png differ diff --git a/Sparse Table/README.markdown b/Sparse Table/README.markdown new file mode 100644 index 000000000..31bd5a781 --- /dev/null +++ b/Sparse Table/README.markdown @@ -0,0 +1,310 @@ +# Sparse Table + +I'm excited to present **Sparse Tables**. Despite being somewhat niche, Sparse Tables are simple to implement and extremely powerful. + +### The Problem + +Let's suppose: +- we have an array **a** of some type +- we have some associative binary function **f** [\*]. The function can be: min, max, [gcd](../GCD/), boolean AND, boolean OR ... + +*[\*] where **f** is also "idempotent". Don't worry, I'll explain this in a moment.* + +Our task is as follows: + +- Given two indices **l** and **r**, answer a **query** for the interval `[l, r)` by performing `f(a[l], a[l + 1], a[l + 2], ..., a[r - 1])`; taking all the elements in the range and applying **f** to them +- There will be a *huge* number **Q** of these queries to answer ... so we should be able to answer each query *quickly*! + +For example, if we have an array of numbers: + +```swift +var a = [ 20, 3, -1, 101, 14, 29, 5, 61, 99 ] +``` +and our function **f** is the *min* function. + +Then we may be given a query for interval `[3, 8)`. That means we look at the elements: + +``` +101, 14, 29, 5, 61 +``` + +because these are the elements of **a** with indices +that lie in our range `[3, 8)` – elements from index 3 up to, but not including, index 8. +We then we pass all of these numbers into the min function, +which takes the minimum. The answer to the query is `5`, because that's the result of `min(101, 14, 29, 5, 61)`. + +Imagine we have millions of these queries to process. +> - *Query 1*: Find minimum of all elements between 2 and 5 +> - *Query 2*: Find minimum of all elements between 3 and 9 +> - ... +> - *Query 1000000*: Find minimum of all elements between 1 and 4 + +And our array is very large. Here, let's say **Q** = 1000000 and **N** = 500000. Both numbers are *huge*. We want to make sure that we can answer each query really quickly, or else the number of queries will overwhelm us! + +*So that's the problem.* + +The naive solution to this problem is to perform a `for` loop +to compute the answer for each query. However, for very large **Q** and very large **N** this +will be too slow. We can speed up the time to compute the answer by using a data structure called +a **Sparse Table**. You'll notice that so far, our problem is exactly the same as that of the [Segment Tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Segment%20Tree) +(assuming you're familiar). However! ... there's one crucial difference between Segment Trees +and Sparse Tables ... and it concerns our choice of **f**. + + +### A small gotcha ... Idempotency + +Suppose we wanted to find the answer to **`[A, D)`**. +And we already know the answer to two ranges **`[A, B)`** and **`[C, D)`**. +And importantly here, ... *these ranges overlap*!! We have **C** < **B**. + +![Overlapping ranges](Images/idempotency.png) + + +So what? Well, for **f** = minimum function, we can take our answers for **`[A, B)`** and **`[C, D)`** +and combine them! +We can just take the minimum of the two answers: `result = min(x1, x2)` ... *voilà!*, we have the minimum for **`[A, D)`**. +It didn't matter that the intervals overlap - we still found the correct minimum. +But now suppose **f** is the addition operation `+`. Ok, so now we're taking sums over ranges. +If we tried the same approach again, it wouldn't work. That is, +if we took our answers for **`[A, B)`** and **`[C, D)`** +and added them together we'd get a wrong answer for **`[A, D)`**. +*Why?* Well, we'd have counted some elements twice because of the overlap. + +Later, we'll see that in order to answer queries, Sparse Tables use this very technique. +They combine answers in the same way as shown above. Unfortunately this means +we have to exclude certain binary operators from being **f**, including `+`, `*`, XOR, ... +because they don't work with this technique. +In order to get the best speed of a Sparse Table, +we need to make sure that the **f** we're using is an **[idempotent](https://en.wikipedia.org/wiki/Idempotence)** binary operator. +Mathematically, these are operators that satisfy `f(x, x) = x` for all possible **x** that could be in **a**. +Practically speaking, these are the only operators that work; allowing us to combine answers from overlapping ranges. +Examples of idempotent **f**'s are min, max, gcd, boolean AND, boolean OR, bitwise AND and bitwise OR. +Note that for Segment Trees, **f** does not have to be idempotent. That's the crucial difference between +Segment Trees and Sparse Tables. + +*Phew!* Now that we've got that out of the way, let's dive in! + +## Structure of a Sparse Table + +Let's use **f** = min and use the array: + +```swift +var a = [ 10, 6, 5, -7, 9, -8, 2, 4, 20 ] +``` + +In this case, the Sparse Table looks like this: + +![Sparse Table](Images/structure.png) + +What's going on here? There seems to be loads of intervals. +*Correct!* Sparse tables are preloaded with the answers for lots of queries `[l, r)`. +Here's the idea. Before we process our **Q** queries, we'll pre-populate our Sparse Table `table` +with answers to loads of queries; +making it act a bit like a *cache*. When we come to answer one of our queries, we can break the query +down into smaller "sub-queries", each having an answer that's already in the cache. +We lookup the cached answers for the sub-queries in +`table` in constant time +and combine the answers together +to give the overall answer to the original query in speedy time. + +The problem is, we can't store the answers for every single possible query that we could ever have ... +or else our table would be too big! After all, our Sparse Table needs to be *sparse*. So what do we do? +We only pick the "best" intervals to store answers for. And as it turns out, the "best" intervals are those +that have a **width that is a power of two**! + +For example, the answer for the query `[10, 18)` is in our table +because the interval width: `18 - 10 = 8 = 2**3` is a power of two (`**` is the [exponentiation operator](https://en.wikipedia.org/wiki/Exponentiation)). +Also, the answer for `[15, 31)` is in our table because its width: `31 - 15 = 16 = 2**4` is again a power of two. +However, the answer for `[1, 6)` is *not* in there because the interval's width: `6 - 1 = 5` is *not* a power of two. +Consequently, we don't store answers for *all* possible intervals that fit inside **a** – +only the ones with a width that is a power of two. +This is true irrespective of where the interval starts within **a**. +We'll gradually see that this approach works and that relatively speaking, it uses very little space. + +A **Sparse Table** is a table where `table[w][l]` contains the answer for `[l, l + 2**w)`. +It has entries `table[w][l]` where: +- **w** tells us our **width** ... the number of elements or the *width* is `2**w` +- **l** tells us the **lower bound** ... it's the starting point of our interval + +Some examples: +- `table[3][0] = -8`: our width is `2**3`, we start at `l = 0` so our query is `[0, 0 + 2**3) = [0, 8)`. + The answer for this query is `min(10, 6, 5, -7, 9, -8, 2, 4, 20) = -8`. +- `table[2][1] = -7`: our width is `2**2`, we start at `l = 1` so our query is `[1, 1 + 2**2) = [1, 5)`. + The answer for this query is `min(6, 5, -7, 9) = -7`. +- `table[1][7] = 4`: our width is `2**1`, we start at `l = 7` so our query is `[7, 7 + 2**1) = [7, 9)`. + The answer for this query is `min(4, 20) = 4`. +- `table[0][8] = 20`: our width is `2**0`, we start at `l = 8` so our query is`[8, 8 + 2**0) = [8, 9)`. + The answer for this query is `min(20) = 20`. + + +![Sparse Table](Images/structure_examples.png) + + +A Sparse Table can be implemented using a [two-dimentional array](../2D%20Array). + +```swift +public class SparseTable { + private var table: [[T]] + + public init(array: [T], function: @escaping (T, T) -> T, defaultT: T) { + table = [[T]](repeating: [T](repeating: defaultT, count: N), count: W) + } + // ... +} +``` + + +## Building a Sparse Table + +To build a Sparse Table, we compute each table entry starting from the bottom-left and moving up towards +the top-right (in accordance with the diagram). +First we'll compute all the intervals for **w** = 0, then compute all the intervals +and for **w** = 1 and so on. We'll continue up until **w** is big enough such that our intervals are can cover at least half the array. +For each **w**, we compute the interval for **l** = 0, 1, 2, 3, ... until we reach **N**. +This is all achieved using a double `for`-`in` loop: + +```swift +for w in 0.. 0)**: We need to find out the answer to `[l, l + 2**w)` for some **l**. +This interval, like all of our intervals in our table has a width that +is a power of two (e.g. 2, 4, 8, 16) ... so we can cut it into two equal halves. + - Our interval with width ``2**w`` is cut into two intervals, each of width ``2**(w - 1)``. + - Because each half has a width that is a power of two, we can look them up in our Sparse Table. + - We combine them together using **f**. + ``` + table[w][l] = f(table[w - 1][l], table[w - 1][l + 2 ** (w - 1)]) + ``` + + +![Sparse Table](Images/recursion.png) + +For example for `a = [ 10, 6, 5, -7, 9, -8, 2, 4, 20 ]` and **f** = *min*: + +- we compute `table[0][2] = 5`. We just had to look at `a[2]` because the range has a width of one. +- we compute `table[1][7] = 4`. We looked at `table[0][7]` and `table[0][8]` and apply **f** to them. +- we compute `table[3][1] = -8`. We looked at `table[2][1]` and `table[2][5]` and apply **f** to them. + + +![Sparse Table](Images/recursion_examples.png) + + + +```swift +public init(array: [T], function: @escaping (T, T) -> T, defaultT: T) { + let N = array.count + let W = Int(ceil(log2(Double(N)))) + table = [[T]](repeating: [T](repeating: defaultT, count: N), count: W) + self.function = function + self.defaultT = defaultT + + for w in 0.. T { + let width = r - l + let W = Int(floor(log2(Double(width)))) + let lo = table[W][l] + let hi = table[W][r - (1 << W)] + return function(lo, hi) + } + ``` + +Finding answers to queries takes **O(1)** time. + +## Analysing Sparse Tables + +- **Query Time** - Both table lookups take constant time. All other operations inside `query` take constant time. +So answering a single query also takes constant time: **O(1)**. But instead of one query we're actually answering **Q** queries, +and we'll need time to built the table before-hand. +Overall time is: **O(NlgN + Q)** to build the table and answer all queries. +The naive approach is to do a for loop for each query. The overall time for the naive approach is: **O(NQ)**. +For very large **Q**, the naive approach will scale poorly. For example if `Q = O(N*N)` +then the naive approach is `O(N*N*N)` where a Sparse Table takes time `O(N*N)`. +- **Space**- The number of possible **w** is **lgN** and the number of possible **l** our table is **N**. So the table +has uses **O(NlgN)** additional space. + +### Comparison with Segment Trees + +- **Pre-processing** - Segment Trees take **O(N)** time to build and use **O(N)** space. Sparse Tables take **O(NlgN)** time to build and use **O(NlgN)** space. +- **Queries** - Segment Tree queries are **O(lgN)** time for any **f** (idempotent or not idempotent). Sparse Table queries are **O(1)** time if **f** is idempotent and are not supported if **f** is not idempotent. [†] +- **Replacing Items** - Segment Trees allow us to efficiently update an element in **a** and update the segment tree in **O(lgN)** time. Sparse Tables do not allow this to be done efficiently. If we were to update an element in **a**, we'd have to rebuild the Sparse Table all over again in **O(NlgN)** time. + + +[†] *Although technically, it's possible to rewrite the `query` method +to add support for non-idempotent functions. But in doing so, we'd bump up the time up from O(1) to O(lgn), +completely defeating the original purpose of Sparse Tables - supporting lightening quick queries. +In such a case, we'd be better off using a Segment Tree (or a Fenwick Tree)* + +## Summary + +That's it! See the playground for more examples involving Sparse Tables. +You'll see examples for: min, max, gcd, boolean operators and logical operators. + +### See also + +- [Segment Trees (Swift Algorithm Club)](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Segment%20Tree) +- [How to write O(lgn) time query function to support non-idempontent functions (GeeksForGeeks)](https://www.geeksforgeeks.org/range-sum-query-using-sparse-table/) +- [Fenwick Trees / Binary Indexed Trees (TopCoder)](https://www.topcoder.com/community/data-science/data-science-tutorials/binary-indexed-trees/) +- [Semilattice (Wikipedia)](https://en.wikipedia.org/wiki/Semilattice) + +*Written for Swift Algorithm Club by [James Lawson](https://github.com/jameslawson)* + diff --git a/Sparse Table/Sparse Table.playground/Contents.swift b/Sparse Table/Sparse Table.playground/Contents.swift new file mode 100644 index 000000000..ef83263b0 --- /dev/null +++ b/Sparse Table/Sparse Table.playground/Contents.swift @@ -0,0 +1,144 @@ +// +// Swift Algorithm Club - Sparse Table +// Author: James Lawson (github.com/jameslawson) +// + +import Foundation + +// Last checked with Xcode Version 9.2 (9C40b) +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + +public class SparseTable { + private var defaultT: T + private var table: [[T]] + private var function: (T, T) -> T + + public init(array: [T], function: @escaping (T, T) -> T, defaultT: T) { + let N = array.count + let W = Int(ceil(log2(Double(N)))) + table = [[T]](repeating: [T](repeating: defaultT, count: N), count: W) + self.function = function + self.defaultT = defaultT + + for w in 0.. T { + let width = r - l + let N = table[0].count + if width <= 0 || l >= N { + return defaultT + } + let r = min(N, r) + let W = Int(floor(log2(Double(width)))) + let lo = table[W][l] + let hi = table[W][r - (1 << W)] + return function(lo, hi) + } +} + +print("---------------------------- EXAMPLE 1 -------------------------------------") +// Here we have an array of integers and we're repeatedly +// finding the minimum over various ranges + +let intArray = [1, -11, -7, 3, 2, 4] +let minIntTable = SparseTable(array: intArray, function: min, defaultT: Int.max) +print(minIntTable.query(from: 0, until: 6)) // min(1, 3, -11, 3, 2, 4) = -11 +print(minIntTable.query(from: 3, until: 5)) // min(3, 2) = 2 +print(minIntTable.query(from: 2, until: 6)) // min(-7, 3, 2, 4) = -7 +print(minIntTable.query(from: 0, until: 1)) // min(1) = 1 +print(minIntTable.query(from: 0, until: 0)) // min() = Int.max +print("----------------------------------------------------------------------------\n\n") + + +print("---------------------------- EXAMPLE 2 -------------------------------------") +// Now we have an array of doubles and we're repeatedly +// finding the maximum over various ranges + +let doubleArray = [1.5, 20.0, 3.5, 15.0, 18.0, -10.0, 5.5] +let maxDoubleTable = SparseTable(array: doubleArray, function: max, defaultT: -.infinity) +print(maxDoubleTable.query(from: 0, until: 4)) // max(1.5, 20.0, 3.5, 15.0) = 20.0 +print(maxDoubleTable.query(from: 3, until: 4)) // max(3.5, 15.0) = 15.0 +print(maxDoubleTable.query(from: 4, until: 6)) // max(18.0, -10.0, 5.5) = 18.0 +print(maxDoubleTable.query(from: 1, until: 2)) // max(20.0) = 20.0 +print(maxDoubleTable.query(from: 0, until: 0)) // max() = -inf +print("----------------------------------------------------------------------------\n\n") + + +print("---------------------------- EXAMPLE 3 -------------------------------------") +// An array of booleans and we're repeatedly +// finding the boolean AND over various ranges + +let boolArray = [true, false, true, true, true, false, false] +func and(_ x: Bool, _ y: Bool) -> Bool { return x && y } + +let maxBoolTable = SparseTable(array: boolArray, function: and, defaultT: false) +print(maxBoolTable.query(from: 0, until: 4)) // and(T, F, T, T) = F +print(maxBoolTable.query(from: 2, until: 5)) // and(T, T, T) = T +print(maxBoolTable.query(from: 2, until: 6)) // and(T, T, T, F) = F +print(maxBoolTable.query(from: 0, until: 1)) // and(T) = T +print(maxBoolTable.query(from: 1, until: 2)) // and(F) = F +print(maxBoolTable.query(from: 0, until: 0)) // and() = F +print("----------------------------------------------------------------------------\n\n") + +print("---------------------------- EXAMPLE 4 -------------------------------------") +// An array of positive integers and we're repeatedly finding +// the gcd (greatest common divisor) over various ranges. The gcd operator is +// associative and idempotent so we can use it with sparse tables + +let posIntArray = [7, 2, 3, 4, 6, 5, 25, 75, 100] +func gcd(_ m: Int, _ n: Int) -> Int { + var a = 0 + var b = max(m, n) + var r = min(m, n) + + while r != 0 { + a = b + b = r + r = a % b + } + return b +} + +let gcdTable = SparseTable(array: posIntArray, function: gcd, defaultT: 1) +print(gcdTable.query(from: 0, until: 4)) // gcd(7, 2, 3) = 1 +print(gcdTable.query(from: 3, until: 5)) // gcd(4, 6) = 2 +print(gcdTable.query(from: 5, until: 7)) // gcd(5, 25, 75) = 5 +print(gcdTable.query(from: 6, until: 9)) // gcd(25, 75, 100) = 25 +print(gcdTable.query(from: 3, until: 4)) // gcd(4) = 4 +print(gcdTable.query(from: 0, until: 0)) // gcd() = 1 +print("------------------------------------------------------------------------\n\n") + + + +print("---------------------------- EXAMPLE 5 -------------------------------------") +// An array of nonnegative integers where for each integer we consider its binary representation. +// We're repeatedly finding the binary OR (|) over various ranges. The binary operator is +// associative and idempotent so we can use it with sparse tables + +let binArray = [0b1001, 0b1100, 0b0000, 0b0001, 0b0010, 0b0100, 0b0000, 0b1111] + + +let orTable = SparseTable(array: binArray, function: |, defaultT: 0b0000) +print(String(orTable.query(from: 0, until: 2), radix: 2)) // binary_or(1001, 1100) = 1101 +print(String(orTable.query(from: 3, until: 5), radix: 2)) // binary_or(0001, 0010) = 0011 + +print(String(orTable.query(from: 3, until: 6), radix: 2)) // binary_or(0001, 0010, 0100) = 0111 +print(String(orTable.query(from: 6, until: 8), radix: 2)) // binary_or(0000, 1111) = 1111 +print(String(orTable.query(from: 1, until: 5), radix: 2)) // binary_or(1100, 0000, 0001, 0010) = 1111 +print(String(orTable.query(from: 0, until: 1), radix: 2)) // binary_or(1001) = 1001 +print(String(orTable.query(from: 0, until: 0), radix: 2)) // binary_or() = 0000 +print("----------------------------------------------------------------------------\n\n") diff --git a/Sparse Table/Sparse Table.playground/contents.xcplayground b/Sparse Table/Sparse Table.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Sparse Table/Sparse Table.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Sparse Table/Sparse Table.playground/playground.xcworkspace/contents.xcworkspacedata b/Sparse Table/Sparse Table.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Sparse Table/Sparse Table.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Splay Tree/Images/SplayTreesWorstCaseExamples.svg b/Splay Tree/Images/SplayTreesWorstCaseExamples.svg new file mode 100644 index 000000000..c07312af2 --- /dev/null +++ b/Splay Tree/Images/SplayTreesWorstCaseExamples.svg @@ -0,0 +1,2 @@ + +
1
1
2
2
1
1
2
2
1
1
2
2
3
3
1
1
2
2
3
3
1
1
2
2
3
3
4
4
1
1
2
2
3
3
4
4
1
1
2
2
3
3
4
4
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
5
5
6
6
7
7
8
8
5
5
6
6
7
7
8
8
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
\ No newline at end of file diff --git a/Splay Tree/Images/example-zigzig-1.png b/Splay Tree/Images/example-zigzig-1.png new file mode 100644 index 000000000..48a08c17c Binary files /dev/null and b/Splay Tree/Images/example-zigzig-1.png differ diff --git a/Splay Tree/Images/example-zigzig-2.png b/Splay Tree/Images/example-zigzig-2.png new file mode 100644 index 000000000..e10ed1f80 Binary files /dev/null and b/Splay Tree/Images/example-zigzig-2.png differ diff --git a/Splay Tree/Images/example-zigzig-3.png b/Splay Tree/Images/example-zigzig-3.png new file mode 100644 index 000000000..905b4d4bd Binary files /dev/null and b/Splay Tree/Images/example-zigzig-3.png differ diff --git a/Splay Tree/Images/example-zigzig-4.png b/Splay Tree/Images/example-zigzig-4.png new file mode 100644 index 000000000..1d46315cf Binary files /dev/null and b/Splay Tree/Images/example-zigzig-4.png differ diff --git a/Splay Tree/Images/example-zigzig-5.png b/Splay Tree/Images/example-zigzig-5.png new file mode 100644 index 000000000..002ca2833 Binary files /dev/null and b/Splay Tree/Images/example-zigzig-5.png differ diff --git a/Splay Tree/Images/example1-1.png b/Splay Tree/Images/example1-1.png new file mode 100644 index 000000000..1bd406248 Binary files /dev/null and b/Splay Tree/Images/example1-1.png differ diff --git a/Splay Tree/Images/example1-2.png b/Splay Tree/Images/example1-2.png new file mode 100644 index 000000000..46dd35bcd Binary files /dev/null and b/Splay Tree/Images/example1-2.png differ diff --git a/Splay Tree/Images/example1-3.png b/Splay Tree/Images/example1-3.png new file mode 100644 index 000000000..af2c6ee9d Binary files /dev/null and b/Splay Tree/Images/example1-3.png differ diff --git a/Splay Tree/Images/examplezig.svg b/Splay Tree/Images/examplezig.svg new file mode 100644 index 000000000..800ef1886 --- /dev/null +++ b/Splay Tree/Images/examplezig.svg @@ -0,0 +1,2 @@ + +
2
2
6
6
10
10
2
2
6
6
10
10
\ No newline at end of file diff --git a/Splay Tree/Images/examplezig1.png b/Splay Tree/Images/examplezig1.png new file mode 100644 index 000000000..dda9d6de9 Binary files /dev/null and b/Splay Tree/Images/examplezig1.png differ diff --git a/Splay Tree/Images/examplezig2.png b/Splay Tree/Images/examplezig2.png new file mode 100644 index 000000000..ba8a48867 Binary files /dev/null and b/Splay Tree/Images/examplezig2.png differ diff --git a/Splay Tree/Images/examplezigzig.svg b/Splay Tree/Images/examplezigzig.svg new file mode 100644 index 000000000..7bd4dcf6f --- /dev/null +++ b/Splay Tree/Images/examplezigzig.svg @@ -0,0 +1,2 @@ + +
4
4
3
3
9
9
2
2
7
7
20
20
2 - ZIG
2 - ZIG
1 - ZIG
1 - ZIG
9
9
7
7
20
20
4
4
2
2
3
3
9
9
7
7
20
20
4
4
2
2
3
3
\ No newline at end of file diff --git a/Splay Tree/Images/examplezigzig1.png b/Splay Tree/Images/examplezigzig1.png new file mode 100644 index 000000000..11ec330f2 Binary files /dev/null and b/Splay Tree/Images/examplezigzig1.png differ diff --git a/Splay Tree/Images/examplezigzig2.png b/Splay Tree/Images/examplezigzig2.png new file mode 100644 index 000000000..0a9c64593 Binary files /dev/null and b/Splay Tree/Images/examplezigzig2.png differ diff --git a/Splay Tree/Images/examplezigzig3.png b/Splay Tree/Images/examplezigzig3.png new file mode 100644 index 000000000..dbc40a3ba Binary files /dev/null and b/Splay Tree/Images/examplezigzig3.png differ diff --git a/Splay Tree/Images/worst-case-1.png b/Splay Tree/Images/worst-case-1.png new file mode 100644 index 000000000..deda6d96f Binary files /dev/null and b/Splay Tree/Images/worst-case-1.png differ diff --git a/Splay Tree/Images/worst-case-2.png b/Splay Tree/Images/worst-case-2.png new file mode 100644 index 000000000..46870f229 Binary files /dev/null and b/Splay Tree/Images/worst-case-2.png differ diff --git a/Splay Tree/Images/worst-case-3.png b/Splay Tree/Images/worst-case-3.png new file mode 100644 index 000000000..65af205a8 Binary files /dev/null and b/Splay Tree/Images/worst-case-3.png differ diff --git a/Splay Tree/Images/worst-case-4.png b/Splay Tree/Images/worst-case-4.png new file mode 100644 index 000000000..ac007e7d8 Binary files /dev/null and b/Splay Tree/Images/worst-case-4.png differ diff --git a/Splay Tree/Images/worst-case-5.png b/Splay Tree/Images/worst-case-5.png new file mode 100644 index 000000000..4009a2592 Binary files /dev/null and b/Splay Tree/Images/worst-case-5.png differ diff --git a/Splay Tree/Images/worst-case-6.png b/Splay Tree/Images/worst-case-6.png new file mode 100644 index 000000000..b162346d3 Binary files /dev/null and b/Splay Tree/Images/worst-case-6.png differ diff --git a/Splay Tree/Images/zig.png b/Splay Tree/Images/zig.png new file mode 100644 index 000000000..e21522b6f Binary files /dev/null and b/Splay Tree/Images/zig.png differ diff --git a/Splay Tree/Images/zigzag1.png b/Splay Tree/Images/zigzag1.png new file mode 100644 index 000000000..1bd406248 Binary files /dev/null and b/Splay Tree/Images/zigzag1.png differ diff --git a/Splay Tree/Images/zigzag2.png b/Splay Tree/Images/zigzag2.png new file mode 100644 index 000000000..5f8e4c63f Binary files /dev/null and b/Splay Tree/Images/zigzag2.png differ diff --git a/Splay Tree/Images/zigzig-wrongrotated.png b/Splay Tree/Images/zigzig-wrongrotated.png new file mode 100644 index 000000000..fc9af14c2 Binary files /dev/null and b/Splay Tree/Images/zigzig-wrongrotated.png differ diff --git a/Splay Tree/Images/zigzig1.png b/Splay Tree/Images/zigzig1.png new file mode 100644 index 000000000..11ec330f2 Binary files /dev/null and b/Splay Tree/Images/zigzig1.png differ diff --git a/Splay Tree/Images/zigzig2.png b/Splay Tree/Images/zigzig2.png new file mode 100644 index 000000000..1e843c9af Binary files /dev/null and b/Splay Tree/Images/zigzig2.png differ diff --git a/Splay Tree/SplayTree.playground/Contents.swift b/Splay Tree/SplayTree.playground/Contents.swift new file mode 100644 index 000000000..28a201b37 --- /dev/null +++ b/Splay Tree/SplayTree.playground/Contents.swift @@ -0,0 +1,14 @@ +//: Playground - Splay Tree Implementation + +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + +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 new file mode 100644 index 000000000..f450eaced --- /dev/null +++ b/Splay Tree/SplayTree.playground/Sources/SplayTree.swift @@ -0,0 +1,557 @@ +/* + * Splay Tree + * + * Based on Binary Search Tree Implementation written by Nicolas Ameghino and Matthijs Hollemans for Swift Algorithms Club + * https://github.com/raywenderlich/swift-algorithm-club/blob/master/Binary%20Search%20Tree + * And extended for the specifics of a Splay Tree by Barbara Martina Rodeker + * + */ + +/** + Represent the 3 possible operations (combinations of rotations) that + could be performed during the Splay phase in Splay Trees + + - zigZag Left child of a right child OR right child of a left child + - zigZig Left child of a left child OR right child of a right child + - zig Only 1 parent and that parent is the root + + */ +public enum SplayOperation { + case zigZag + case zigZig + case zig + + + /** + Splay the given node up to the root of the tree + + - Parameters: + - node SplayTree node to move up to the root + */ + public static func splay(node: Node) { + + while (node.parent != nil) { + operation(forNode: node).apply(onNode: node) + } + } + + /** + Compares the node and its parent and determine + if the rotations should be performed in a zigZag, zigZig or zig case. + + - Parmeters: + - forNode SplayTree node to be checked + - Returns + - Operation Case zigZag - zigZig - zig + */ + 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) { + return .zigZag + } + return .zigZig + } + return .zig + } + + /** + Applies the rotation associated to the case + Modifying the splay tree and briging the received node further to the top of the tree + + - Parameters: + - onNode Node to splay up. Should be alwayas the node that needs to be splayed, neither its parent neither it's grandparent + */ + public func apply(onNode node: Node) { + switch self { + case .zigZag: + assert(node.parent != nil && node.parent!.parent != nil, "Should be at least 2 nodes up in the tree") + rotate(child: node, parent: node.parent!) + rotate(child: node, parent: node.parent!) + + case .zigZig: + assert(node.parent != nil && node.parent!.parent != nil, "Should be at least 2 nodes up in the tree") + rotate(child: node.parent!, parent: node.parent!.parent!) + rotate(child: node, parent: node.parent!) + + case .zig: + assert(node.parent != nil && node.parent!.parent == nil, "There should be a parent which is the root") + rotate(child: node, parent: node.parent!) + } + } + + /** + Performs a single rotation from a node to its parent + re-arranging the children properly + */ + public func rotate(child: Node, parent: Node) { + + assert(child.parent != nil && child.parent!.value == parent.value, "Parent and child.parent should match here") + + var grandchildToMode: Node? + + if child.isLeftChild { + + grandchildToMode = child.right + parent.left = grandchildToMode + grandchildToMode?.parent = parent + + let grandParent = parent.parent + child.parent = grandParent + + if parent.isLeftChild { + grandParent?.left = child + } else { + grandParent?.right = child + } + + child.right = parent + parent.parent = child + + + } else { + + grandchildToMode = child.left + parent.right = grandchildToMode + grandchildToMode?.parent = parent + + let grandParent = parent.parent + child.parent = grandParent + + if parent.isLeftChild { + grandParent?.left = child + } else { + grandParent?.right = child + } + + child.left = parent + parent.parent = child + + } + + + } +} + +public class Node { + + fileprivate(set) public var value: T? + fileprivate(set) public var parent: Node? + fileprivate(set) public var left: Node? + fileprivate(set) public var right: Node? + + init(value: T) { + self.value = value + } + + public var isRoot: Bool { + return parent == nil + } + + public var isLeaf: Bool { + return left == nil && right == nil + } + + public var isLeftChild: Bool { + return parent?.left === self + } + + public var isRightChild: Bool { + return parent?.right === self + } + + public var hasLeftChild: Bool { + return left != nil + } + + public var hasRightChild: Bool { + return right != nil + } + + public var hasAnyChild: Bool { + return hasLeftChild || hasRightChild + } + + public var hasBothChildren: Bool { + return hasLeftChild && hasRightChild + } + + /* How many nodes are in this subtree. Performance: O(n). */ + public var count: Int { + return (left?.count ?? 0) + 1 + (right?.count ?? 0) + } +} + +public class SplayTree { + + internal var root: Node? + + var value: T? { + return root?.value + } + + //MARK: - Initializer + + public init(value: T) { + self.root = Node(value:value) + } + + public func insert(value: T) { + if let root = root { + self.root = root.insert(value: value) + } else { + root = Node(value: value) + } + } + + public func remove(value: T) { + root = root?.remove(value: value) + } + + public func search(value: T) -> Node? { + root = root?.search(value: value) + return root + } + + public func minimum() -> Node? { + root = root?.minimum(splayed: true) + return root + } + + public func maximum() -> Node? { + root = root?.maximum(splayed: true) + return root + } + +} + +// MARK: - Adding items + +extension Node { + + /* + Inserts a new element into the node tree. + + - Parameters: + - value T value to be inserted. Will be splayed to the root position + + - Returns: + - Node inserted + */ + public func insert(value: T) -> Node { + if let selfValue = self.value { + if value < selfValue { + if let left = left { + return left.insert(value: value) + } else { + + left = Node(value: value) + left?.parent = self + + if let left = left { + SplayOperation.splay(node: left) + return left + } + } + } else { + + if let right = right { + return right.insert(value: value) + } else { + + right = Node(value: value) + right?.parent = self + + if let right = right { + SplayOperation.splay(node: right) + return right + } + } + } + } + return self + } +} + +// MARK: - Deleting items + +extension Node { + + /* + Deletes the given node from the nodes tree. + Return the new tree generated by the removal. + The removed node (not necessarily the one containing the value), will be splayed to the root. + + - Parameters: + - value To be removed + + - Returns: + - Node Resulting from the deletion and the splaying of the removed node + + */ + fileprivate func remove(value: T) -> Node? { + guard let target = search(value: value) else { return self } + + if let left = target.left, let right = target.right { + let largestOfLeftChild = left.maximum() + left.parent = nil + right.parent = nil + + SplayOperation.splay(node: largestOfLeftChild) + largestOfLeftChild.right = right + + return largestOfLeftChild + + } else if let left = target.left { + replace(node: target, with: left) + return left + + } else if let right = target.right { + replace(node: target, with: right) + return right + + } else { + return nil + } + } + + private func replace(node: Node, with newNode: Node?) { + guard let sourceParent = sourceNode.parent else { return } + + if sourceNode.isLeftChild { + sourceParent.left = newNode + } else { + sourceParent.right = newNode + } + + newNode?.parent = sourceParent + } +} + +// MARK: - Searching + +extension Node { + + /* + Finds the "highest" node with the specified value. + Performance: runs in O(h) time, where h is the height of the tree. + */ + public func search(value: T) -> Node? { + var node: Node? = self + var nodeParent: Node? = self + while case let n? = node, n.value != nil { + if value < n.value! { + if n.left != nil { nodeParent = n.left } + node = n.left + } else if value > n.value! { + node = n.right + if n.right != nil { nodeParent = n.right } + } else { + break + } + } + + if let node = node { + SplayOperation.splay(node: node) + return node + } else if let nodeParent = nodeParent { + SplayOperation.splay(node: nodeParent) + return nodeParent + } + + return nil + } + + public func contains(value: T) -> Bool { + return search(value: value) != nil + } + + /* + Returns the leftmost descendent. O(h) time. + */ + public func minimum(splayed: Bool = false) -> Node { + var node = self + while case let next? = node.left { + node = next + } + + if splayed == true { + SplayOperation.splay(node: node) + } + + return node + } + + /* + Returns the rightmost descendent. O(h) time. + */ + public func maximum(splayed: Bool = false) -> Node { + var node = self + while case let next? = node.right { + node = next + } + + if splayed == true { + SplayOperation.splay(node: node) + } + + return node + } + + /* + Calculates the depth of this node, i.e. the distance to the root. + Takes O(h) time. + */ + public func depth() -> Int { + var node = self + var edges = 0 + while case let parent? = node.parent { + node = parent + edges += 1 + } + return edges + } + + /* + Calculates the height of this node, i.e. the distance to the lowest leaf. + Since this looks at all children of this node, performance is O(n). + */ + public func height() -> Int { + if isLeaf { + return 0 + } else { + return 1 + max(left?.height() ?? 0, right?.height() ?? 0) + } + } + + /* + Finds the node whose value precedes our value in sorted order. + */ + public func predecessor() -> Node? { + if let left = left { + return left.maximum() + } else { + var node = self + while case let parent? = node.parent, parent.value != nil, value != nil { + if parent.value! < value! { return parent } + node = parent + } + return nil + } + } + + /* + Finds the node whose value succeeds our value in sorted order. + */ + public func successor() -> Node? { + if let right = right { + return right.minimum() + } else { + var node = self + while case let parent? = node.parent, parent.value != nil , value != nil { + if parent.value! > value! { return parent } + node = parent + } + return nil + } + } +} + +// MARK: - Traversal +extension Node { + + public func traverseInOrder(process: (T) -> Void) { + left?.traverseInOrder(process: process) + process(value!) + right?.traverseInOrder(process: process) + } + + public func traversePreOrder(process: (T) -> Void) { + process(value!) + left?.traversePreOrder(process: process) + right?.traversePreOrder(process: process) + } + + public func traversePostOrder(process: (T) -> Void) { + left?.traversePostOrder(process: process) + right?.traversePostOrder(process: process) + process(value!) + } + + /* + Performs an in-order traversal and collects the results in an array. + */ + public func map(formula: (T) -> T) -> [T] { + var a = [T]() + if let left = left { a += left.map(formula: formula) } + a.append(formula(value!)) + if let right = right { a += right.map(formula: formula) } + return a + } +} + +/* + Is this binary tree a valid binary search tree? + */ +extension Node { + + public func isBST(minValue: T, maxValue: T) -> Bool { + if let value = value { + 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 + return leftBST && rightBST + } + return false + } +} + +// MARK: - Debugging + +extension Node: CustomStringConvertible { + public var description: String { + var s = "" + if let left = left { + s += "left: (\(left.description)) <- " + } + if let v = value { + s += "\(v)" + } + if let right = right { + s += " -> (right: \(right.description))" + } + return s + } +} + +extension SplayTree: CustomStringConvertible { + public var description: String { + return root?.description ?? "Empty tree" + } +} + +extension Node: CustomDebugStringConvertible { + public var debugDescription: String { + var s = "value: \(String(describing: value))" + if let parent = parent, let v = parent.value { + s += ", parent: \(v)" + } + if let left = left { + s += ", left = [" + left.debugDescription + "]" + } + if let right = right { + s += ", right = [" + right.debugDescription + "]" + } + return s + } + + public func toArray() -> [T] { + return map { $0 } + } +} + +extension SplayTree: CustomDebugStringConvertible { + public var debugDescription: String { + return root?.debugDescription ?? "Empty tree" + } +} diff --git a/Splay Tree/SplayTree.playground/contents.xcplayground b/Splay Tree/SplayTree.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Splay Tree/SplayTree.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Splay Tree/SplayTree.playground/playground.xcworkspace/contents.xcworkspacedata b/Splay Tree/SplayTree.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Splay Tree/SplayTree.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Splay Tree/SplayTree.swift b/Splay Tree/SplayTree.swift new file mode 100644 index 000000000..0f1fa48f0 --- /dev/null +++ b/Splay Tree/SplayTree.swift @@ -0,0 +1,557 @@ +/* + * Splay Tree + * + * Based on Binary Search Tree Implementation written by Nicolas Ameghino and Matthijs Hollemans for Swift Algorithms Club + * https://github.com/raywenderlich/swift-algorithm-club/blob/master/Binary%20Search%20Tree + * And extended for the specifics of a Splay Tree by Barbara Martina Rodeker + * + */ + +/** + Represent the 3 possible operations (combinations of rotations) that + could be performed during the Splay phase in Splay Trees + + - zigZag Left child of a right child OR right child of a left child + - zigZig Left child of a left child OR right child of a right child + - zig Only 1 parent and that parent is the root + + */ +public enum SplayOperation { + case zigZag + case zigZig + case zig + + + /** + Splay the given node up to the root of the tree + + - Parameters: + - node SplayTree node to move up to the root + */ + public static func splay(node: Node) { + + while (node.parent != nil) { + operation(forNode: node).apply(onNode: node) + } + } + + /** + Compares the node and its parent and determine + if the rotations should be performed in a zigZag, zigZig or zig case. + + - Parmeters: + - forNode SplayTree node to be checked + - Returns + - Operation Case zigZag - zigZig - zig + */ + 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) { + return .zigZag + } + return .zigZig + } + return .zig + } + + /** + Applies the rotation associated to the case + Modifying the splay tree and briging the received node further to the top of the tree + + - Parameters: + - onNode Node to splay up. Should be alwayas the node that needs to be splayed, neither its parent neither it's grandparent + */ + public func apply(onNode node: Node) { + switch self { + case .zigZag: + assert(node.parent != nil && node.parent!.parent != nil, "Should be at least 2 nodes up in the tree") + rotate(child: node, parent: node.parent!) + rotate(child: node, parent: node.parent!) + + case .zigZig: + assert(node.parent != nil && node.parent!.parent != nil, "Should be at least 2 nodes up in the tree") + rotate(child: node.parent!, parent: node.parent!.parent!) + rotate(child: node, parent: node.parent!) + + case .zig: + assert(node.parent != nil && node.parent!.parent == nil, "There should be a parent which is the root") + rotate(child: node, parent: node.parent!) + } + } + + /** + Performs a single rotation from a node to its parent + re-arranging the children properly + */ + public func rotate(child: Node, parent: Node) { + + assert(child.parent != nil && child.parent!.value == parent.value, "Parent and child.parent should match here") + + var grandchildToMode: Node? + + if child.isLeftChild { + + grandchildToMode = child.right + parent.left = grandchildToMode + grandchildToMode?.parent = parent + + let grandParent = parent.parent + child.parent = grandParent + + if parent.isLeftChild { + grandParent?.left = child + } else { + grandParent?.right = child + } + + child.right = parent + parent.parent = child + + + } else { + + grandchildToMode = child.left + parent.right = grandchildToMode + grandchildToMode?.parent = parent + + let grandParent = parent.parent + child.parent = grandParent + + if parent.isLeftChild { + grandParent?.left = child + } else { + grandParent?.right = child + } + + child.left = parent + parent.parent = child + + } + + + } +} + +public class Node { + + fileprivate(set) public var value: T? + fileprivate(set) public var parent: Node? + fileprivate(set) public var left: Node? + fileprivate(set) public var right: Node? + + init(value: T) { + self.value = value + } + + public var isRoot: Bool { + return parent == nil + } + + public var isLeaf: Bool { + return left == nil && right == nil + } + + public var isLeftChild: Bool { + return parent?.left === self + } + + public var isRightChild: Bool { + return parent?.right === self + } + + public var hasLeftChild: Bool { + return left != nil + } + + public var hasRightChild: Bool { + return right != nil + } + + public var hasAnyChild: Bool { + return hasLeftChild || hasRightChild + } + + public var hasBothChildren: Bool { + return hasLeftChild && hasRightChild + } + + /* How many nodes are in this subtree. Performance: O(n). */ + public var count: Int { + return (left?.count ?? 0) + 1 + (right?.count ?? 0) + } +} + +public class SplayTree { + + internal var root: Node? + + var value: T? { + return root?.value + } + + //MARK: - Initializer + + public init(value: T) { + self.root = Node(value:value) + } + + public func insert(value: T) { + if let root = root { + self.root = root.insert(value: value) + } else { + root = Node(value: value) + } + } + + public func remove(value: T) { + root = root?.remove(value: value) + } + + public func search(value: T) -> Node? { + root = root?.search(value: value) + return root + } + + public func minimum() -> Node? { + root = root?.minimum(splayed: true) + return root + } + + public func maximum() -> Node? { + root = root?.maximum(splayed: true) + return root + } + +} + +// MARK: - Adding items + +extension Node { + + /* + Inserts a new element into the node tree. + + - Parameters: + - value T value to be inserted. Will be splayed to the root position + + - Returns: + - Node inserted + */ + public func insert(value: T) -> Node { + if let selfValue = self.value { + if value < selfValue { + if let left = left { + return left.insert(value: value) + } else { + + left = Node(value: value) + left?.parent = self + + if let left = left { + SplayOperation.splay(node: left) + return left + } + } + } else { + + if let right = right { + return right.insert(value: value) + } else { + + right = Node(value: value) + right?.parent = self + + if let right = right { + SplayOperation.splay(node: right) + return right + } + } + } + } + return self + } +} + +// MARK: - Deleting items + +extension Node { + + /* + Deletes the given node from the nodes tree. + Return the new tree generated by the removal. + The removed node (not necessarily the one containing the value), will be splayed to the root. + + - Parameters: + - value To be removed + + - Returns: + - Node Resulting from the deletion and the splaying of the removed node + + */ + fileprivate func remove(value: T) -> Node? { + guard let target = search(value: value) else { return self } + + if let left = target.left, let right = target.right { + let largestOfLeftChild = left.maximum() + left.parent = nil + right.parent = nil + + SplayOperation.splay(node: largestOfLeftChild) + largestOfLeftChild.right = right + + return largestOfLeftChild + + } else if let left = target.left { + replace(node: target, with: left) + return left + + } else if let right = target.right { + replace(node: target, with: right) + return right + + } else { + return nil + } + } + + private func replace(node: Node, with newNode: Node?) { + guard let sourceParent = sourceNode.parent else { return } + + if sourceNode.isLeftChild { + sourceParent.left = newNode + } else { + sourceParent.right = newNode + } + + newNode?.parent = sourceParent + } +} + +// MARK: - Searching + +extension Node { + + /* + Finds the "highest" node with the specified value. + Performance: runs in O(h) time, where h is the height of the tree. + */ + public func search(value: T) -> Node? { + var node: Node? = self + var nodeParent: Node? = self + while case let n? = node, n.value != nil { + if value < n.value! { + if n.left != nil { nodeParent = n.left } + node = n.left + } else if value > n.value! { + node = n.right + if n.right != nil { nodeParent = n.right } + } else { + break + } + } + + if let node = node { + SplayOperation.splay(node: node) + return node + } else if let nodeParent = nodeParent { + SplayOperation.splay(node: nodeParent) + return nodeParent + } + + return nil + } + + public func contains(value: T) -> Bool { + return search(value: value) != nil + } + + /* + Returns the leftmost descendent. O(h) time. + */ + public func minimum(splayed: Bool = false) -> Node { + var node = self + while case let next? = node.left { + node = next + } + + if splayed == true { + SplayOperation.splay(node: node) + } + + return node + } + + /* + Returns the rightmost descendent. O(h) time. + */ + public func maximum(splayed: Bool = false) -> Node { + var node = self + while case let next? = node.right { + node = next + } + + if splayed == true { + SplayOperation.splay(node: node) + } + + return node + } + + /* + Calculates the depth of this node, i.e. the distance to the root. + Takes O(h) time. + */ + public func depth() -> Int { + var node = self + var edges = 0 + while case let parent? = node.parent { + node = parent + edges += 1 + } + return edges + } + + /* + Calculates the height of this node, i.e. the distance to the lowest leaf. + Since this looks at all children of this node, performance is O(n). + */ + public func height() -> Int { + if isLeaf { + return 0 + } else { + return 1 + max(left?.height() ?? 0, right?.height() ?? 0) + } + } + + /* + Finds the node whose value precedes our value in sorted order. + */ + public func predecessor() -> Node? { + if let left = left { + return left.maximum() + } else { + var node = self + while case let parent? = node.parent, parent.value != nil, value != nil { + if parent.value! < value! { return parent } + node = parent + } + return nil + } + } + + /* + Finds the node whose value succeeds our value in sorted order. + */ + public func successor() -> Node? { + if let right = right { + return right.minimum() + } else { + var node = self + while case let parent? = node.parent, parent.value != nil , value != nil { + if parent.value! > value! { return parent } + node = parent + } + return nil + } + } +} + +// MARK: - Traversal +extension Node { + + public func traverseInOrder(process: (T) -> Void) { + left?.traverseInOrder(process: process) + process(value!) + right?.traverseInOrder(process: process) + } + + public func traversePreOrder(process: (T) -> Void) { + process(value!) + left?.traversePreOrder(process: process) + right?.traversePreOrder(process: process) + } + + public func traversePostOrder(process: (T) -> Void) { + left?.traversePostOrder(process: process) + right?.traversePostOrder(process: process) + process(value!) + } + + /* + Performs an in-order traversal and collects the results in an array. + */ + public func map(formula: (T) -> T) -> [T] { + var a = [T]() + if let left = left { a += left.map(formula: formula) } + a.append(formula(value!)) + if let right = right { a += right.map(formula: formula) } + return a + } +} + +/* + Is this binary tree a valid binary search tree? + */ +extension Node { + + public func isBST(minValue: T, maxValue: T) -> Bool { + if let value = value { + 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 + return leftBST && rightBST + } + return false + } +} + +// MARK: - Debugging + +extension Node: CustomStringConvertible { + public var description: String { + var s = "" + if let left = left { + s += "left: (\(left.description)) <- " + } + if let v = value { + s += "\(v)" + } + if let right = right { + s += " -> (right: \(right.description))" + } + return s + } +} + +extension SplayTree: CustomStringConvertible { + public var description: String { + return root?.description ?? "Empty tree" + } +} + +extension Node: CustomDebugStringConvertible { + public var debugDescription: String { + var s = "value: \(value)" + if let parent = parent, let v = parent.value { + s += ", parent: \(v)" + } + if let left = left { + s += ", left = [" + left.debugDescription + "]" + } + if let right = right { + s += ", right = [" + right.debugDescription + "]" + } + return s + } + + public func toArray() -> [T] { + return map { $0 } + } +} + +extension SplayTree: CustomDebugStringConvertible { + public var debugDescription: String { + return root?.debugDescription ?? "Empty tree" + } +} diff --git a/Splay Tree/Tests/Info.plist b/Splay Tree/Tests/Info.plist new file mode 100644 index 000000000..6c6c23c43 --- /dev/null +++ b/Splay Tree/Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Splay Tree/Tests/SplayTreeTests.swift b/Splay Tree/Tests/SplayTreeTests.swift new file mode 100644 index 000000000..01201a108 --- /dev/null +++ b/Splay Tree/Tests/SplayTreeTests.swift @@ -0,0 +1,76 @@ +import XCTest + +class SplayTreeTests: XCTestCase { + + var tree1: SplayTree! + var tree2: SplayTree! + + override func setUp() { + super.setUp() + tree1 = SplayTree(value: 1) + + tree2 = SplayTree(value: 1) + tree2.insert(value: 10) + tree2.insert(value: 20) + tree2.insert(value: 3) + tree2.insert(value: 6) + tree2.insert(value: 100) + tree2.insert(value: 44) + } + + func testInsertion() { + tree1.insert(value: 10) + assert(tree1.value == 10) + + tree2.insert(value: 2) + assert(tree2.root?.value == 2) + } + + func testSearchNonExisting() { + let t = tree2.search(value: 5) + assert(t?.value == 10) + } + + func testSearchExisting() { + let t = tree2.search(value: 6) + assert(t?.value == 6) + } + + func testDeleteExistingOnlyLeftChild() { + tree2.remove(value: 3) + assert(tree2.value == 6) + } + + func testDeleteExistingOnly2Children() { + tree2.remove(value: 6) + assert(tree2.value == 20) + } + + func testDeleteRoot() { + tree2.remove(value: 44) + assert(tree2.value == 100) + } + + func testMinimum() { + let v = tree2.minimum() + assert(v?.value == 1) + } + + func testMaximum() { + let v = tree2.maximum() + assert(v?.value == 100) + } + + func testInsertionRemovals() { + 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) + + assert(splayTree.value == 2) + } + +} diff --git a/Splay Tree/Tests/Tests-Bridging-Header.h b/Splay Tree/Tests/Tests-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/Splay Tree/Tests/Tests-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/Splay Tree/Tests/Tests.xcodeproj/project.pbxproj b/Splay Tree/Tests/Tests.xcodeproj/project.pbxproj new file mode 100644 index 000000000..8a900fcd3 --- /dev/null +++ b/Splay Tree/Tests/Tests.xcodeproj/project.pbxproj @@ -0,0 +1,279 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 763F9E771E59DAEF00AC5031 /* SplayTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 763F9E761E59DAEF00AC5031 /* SplayTree.swift */; }; + 763F9E791E59DAFE00AC5031 /* SplayTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 763F9E781E59DAFE00AC5031 /* SplayTreeTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 056E92A21E25D04D00B30F52 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 056E92A61E25D04D00B30F52 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 763F9E761E59DAEF00AC5031 /* SplayTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SplayTree.swift; path = ../SplayTree.swift; sourceTree = ""; }; + 763F9E781E59DAFE00AC5031 /* SplayTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplayTreeTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 056E929F1E25D04D00B30F52 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 056E92851E25D03300B30F52 = { + isa = PBXGroup; + children = ( + 056E92A31E25D04D00B30F52 /* Tests */, + 056E928F1E25D03300B30F52 /* Products */, + ); + sourceTree = ""; + }; + 056E928F1E25D03300B30F52 /* Products */ = { + isa = PBXGroup; + children = ( + 056E92A21E25D04D00B30F52 /* Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 056E92A31E25D04D00B30F52 /* Tests */ = { + isa = PBXGroup; + children = ( + 056E92A61E25D04D00B30F52 /* Info.plist */, + 763F9E781E59DAFE00AC5031 /* SplayTreeTests.swift */, + 763F9E761E59DAEF00AC5031 /* SplayTree.swift */, + ); + name = Tests; + sourceTree = SOURCE_ROOT; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 056E92A11E25D04D00B30F52 /* Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 056E92A71E25D04D00B30F52 /* Build configuration list for PBXNativeTarget "Tests" */; + buildPhases = ( + 056E929E1E25D04D00B30F52 /* Sources */, + 056E929F1E25D04D00B30F52 /* Frameworks */, + 056E92A01E25D04D00B30F52 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Tests; + productName = Tests; + productReference = 056E92A21E25D04D00B30F52 /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 056E92861E25D03300B30F52 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = "Swift Algorithm Club"; + TargetAttributes = { + 056E92A11E25D04D00B30F52 = { + CreatedOnToolsVersion = 8.2; + LastSwiftMigration = 0820; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 056E92891E25D03300B30F52 /* Build configuration list for PBXProject "Tests" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 056E92851E25D03300B30F52; + productRefGroup = 056E928F1E25D03300B30F52 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 056E92A11E25D04D00B30F52 /* Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 056E92A01E25D04D00B30F52 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 056E929E1E25D04D00B30F52 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 763F9E771E59DAEF00AC5031 /* SplayTree.swift in Sources */, + 763F9E791E59DAFE00AC5031 /* SplayTreeTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 056E92991E25D03300B30F52 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + 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.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 056E929A1E25D03300B30F52 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + 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.12; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + 056E92A81E25D04D00B30F52 /* 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_OBJC_BRIDGING_HEADER = "Tests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 056E92A91E25D04D00B30F52 /* 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_OBJC_BRIDGING_HEADER = "Tests-Bridging-Header.h"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 056E92891E25D03300B30F52 /* Build configuration list for PBXProject "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 056E92991E25D03300B30F52 /* Debug */, + 056E929A1E25D03300B30F52 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 056E92A71E25D04D00B30F52 /* Build configuration list for PBXNativeTarget "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 056E92A81E25D04D00B30F52 /* Debug */, + 056E92A91E25D04D00B30F52 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 056E92861E25D03300B30F52 /* Project object */; +} diff --git a/Splay Tree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Splay Tree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..6c0ea8493 --- /dev/null +++ b/Splay Tree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Quicksort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Splay Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme similarity index 76% rename from Quicksort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme rename to Splay Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index dfcf6de42..b3f6d696a 100644 --- a/Quicksort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Splay Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -9,12 +9,12 @@ + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> @@ -32,13 +32,22 @@ skipped = "NO"> + + + + @@ -52,15 +61,16 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + @@ -70,15 +80,16 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - + - + diff --git a/Splay Tree/readme.md b/Splay Tree/readme.md new file mode 100644 index 000000000..8a852c950 --- /dev/null +++ b/Splay Tree/readme.md @@ -0,0 +1,277 @@ +# Splay Tree +Splay tree is a data structure, structurally identitical to a balanced binary search tree. Every operation performed on a Splay Tree causes a readjustment in order to provide fast access to recently operated values. On every access, the tree is rearranged and the node accessed is moved to the root of the tree using a set of specific rotations, which together are referred to as **Splaying**. + + +## Rotations + +There are 3 types of rotations that can form an **Splaying**: + +- ZigZig +- ZigZag +- Zig + +### Zig-Zig + +Given a node *a* if *a* is not the root, and *a* has a child *b*, and both *a* and *b* are left children or right children, a **Zig-Zig** is performed. + +### Case both nodes right children +![ZigZigCase1](Images/zigzig1.png) + +### Case both nodes left children +![ZigZigCase2](Images/zigzig2.png) + +**IMPORTANT** is to note that a *ZigZig* performs first the rotation of the middle node with its parent (call it the grandparent) and later the rotation of the remaining node (grandchild). Doing that helps to keep the trees balanced even if it was first created by inserted a sequence of increasing values (see below worst case scenario followed by an explanation about why ZigZig rotates first to the grandparent). + +### Zig-Zag + +Given a node *a* if *a* is not the root, and *a* has a child *b*, and *b* is the left child of *a* being the right child (or the opposite), a **Zig-Zag** is performed. + +### Case right - left +![ZigZagCase1](Images/zigzag1.png) + +### Case left - right +![ZigZagCase2](Images/zigzag2.png) + +**IMPORTANT** A *ZigZag* performs first the rotation of the grandchild node and later the same node with its new parent again. + +### Zig + +A **Zig** is performed when the node *a* to be rotated has the root as parent. + +![ZigCase](Images/zig.png) + + +## Splaying + +Splaying consists in making so many rotations as needed until the node affected by the operation is at the top and becomes the root of the tree. + +``` +while (node.parent != nil) { + operation(forNode: node).apply(onNode: node) +} +``` + +Where operation returns the required rotation to be applied. + +``` +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) { + return .zigZag + } + return .zigZig + } + return .zig +} +``` + +During the applying phase, the algorithms determines which nodes are involved depending on the rotation to be applied and proceeding to re-arrange the node with its parent. + +``` +public func apply(onNode node: Node) { + switch self { + case .zigZag: + assert(node.parent != nil && node.parent!.parent != nil, "Should be at least 2 nodes up in the tree") + rotate(child: node, parent: node.parent!) + rotate(child: node, parent: node.parent!) + + case .zigZig: + assert(node.parent != nil && node.parent!.parent != nil, "Should be at least 2 nodes up in the tree") + rotate(child: node.parent!, parent: node.parent!.parent!) + rotate(child: node, parent: node.parent!) + + case .zig: + assert(node.parent != nil && node.parent!.parent == nil, "There should be a parent which is the root") + rotate(child: node, parent: node.parent!) + } +} +``` + + +## Operations on an Splay Tree + +### Insertion + +To insert a value: + +- Insert it as in a binary search tree +- Splay the value to the root + +### Deletion + +To delete a value: + +- 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 + +To search a value: + +- Search for it as in a binary search tree +- Splay the node containing the value to the root +- If not found splay the node that would had been the parent of the searched value + +### Minimum and maximum + +- Search the tree for the required value +- Splay the node to the root + +## Examples + +### Example 1 + +Lets suppose a *find(20)* operation was performed and now the values **20** needs to be splayed to the root. +The sequence of steps will be the following: + + +1. Since we are in a *ZigZig* case, we need to rotate **9** to **4** + +![ZiggEx1](Images/examplezigzig1.png) + +2. We got the following tree after the first rotation: + +![ZiggEx2](Images/examplezigzig2.png) + +3. And finally the **20** is rotated to the **9** + +![ZiggEx3](Images/examplezigzig3.png) + + +### Example 2 + +Now suppose a *insert(7)* operation was performed and we're in a *ZigZag* case. + + +1. First **7** is rotated to **9** + +![ZigggEx21](Images/example1-1.png) + +2. And the result tree is: + +![ZigggEx22](Images/example1-2.png) + +3. Finally **7** is rotated to **4** + +![ZigggEx23](Images/example1-3.png) + + +## Advantages + +Splay trees provide an efficient way to quickly access elements that are frequently requested. This characteristic makes then a good choice to implement, for example, caches or garbage collection algorithms, or in any other problem involving frequent access to a certain numbers of elements from a data set. + +## Disadvantages + +Splay tree are not perfectly balanced always, so in case of accessing all the elements in the tree in an increasing order, the height of the tree becomes *n*. + +## Time complexity + +| Case | Performance | +| ------------- |:-------------:| +| Average | O(log n) | +| Worst | n | + +With *n* being the number of items in the tree. + +# An example of the Worst Case Performance + +Suppose the a sequence of consecutive values are inserted in an Splay Tree. +Let's take for example [1,2,3,4,5,6,7,8]. + +The tree construction will be like following: + + +1. Insert the number **1** + +2. Insert **2** + + +![WorstCase1](Images/worst-case-1.png) + + +3. Splay **2** to the root + + +![WorstCase2](Images/worst-case-2.png) + + +4. Insert **3** + + +![WorstCase3](Images/worst-case-3.png) + +5. Splay **3** to the root + + +![WorstCase4](Images/worst-case-4.png) + + +6. Insert **4** + + +![WorstCase5](Images/worst-case-5.png) + + +7. After inserting the rest of the values the tree will look like this: + + +![WorstCase6](Images/worst-case-6.png) + + + +If we keep insert number following the same sequence, that tree becomes inbalanced and have a height of **n** with **n** being the numbers of values inserted. +After getting this tree, a *find(1)* operation for example will take **O(n)** + +## ZigZig rotation order: first grandparent + +But thanks to the properties of the **Splay Tree** and the *ZigZig* rotations after the *find(1)* operation the tree becomes balanced again. This only happens if we respect the order of the *ZigZig* rotation, and the rotation to the grandparent happens first. + +The sequence of *ZigZigs* rotations will look like follows: + +1. Rotate **2** to **3** + +![ZigZig1](Images/example-zigzig-1.png) + +2. Rotate **1** to **2** + +![ZigZig2](Images/example-zigzig-2.png) + +3. Rotate **4** to **5** + +![ZigZig3](Images/example-zigzig-3.png) + +4. Rotate **1** to **4** + +![ZigZig4](Images/example-zigzig-4.png) + +5. Finally after splaying **1** to the root the tree will look like this: + +![ZigZig5](Images/example-zigzig-5.png) + + +Based on the example above, we can see why it's important to rotate first to the grandparent. We got a tree of height = 6, from an initial tree of height = 8. If the tree would had been taller, we would have achieved almost half of the initial height after the splaying operation. + +## ZigZig wrong rotation order + +If the rotations would had been taking first the parent and not the grandparent we would have finished with the following, yet unbalanced tree, just inverting the side of the elements. + +![ZigZigWrong](Images/zigzig-wrongrotated.png) + + +## See also + +[Splay Tree on Wikipedia](https://en.wikipedia.org/wiki/Splay_tree) + +[Splay Tree by University of California in Berkeley - CS 61B Lecture 34](https://www.youtube.com/watch?v=8Zs1lj_bUV0) + + +---------------- + +*Written for Swift Algorithm Club by Barbara Martina Rodeker* + +---------------- + diff --git a/Stack/README.markdown b/Stack/README.markdown index 1a3ca2638..b84b585df 100644 --- a/Stack/README.markdown +++ b/Stack/README.markdown @@ -1,5 +1,7 @@ # Stack +> This topic has been tutorialized [here](https://www.raywenderlich.com/149213/swift-algorithm-club-swift-stack-data-structure) + A stack is like an array but with limited functionality. You can only *push* to add a new element to the top of the stack, *pop* to remove the element from the top, and *peek* at the top element without popping it off. Why would you want to do this? Well, in many algorithms you want to add objects to a temporary list at some point and then pull them off this list again at a later time. Often the order in which you add and remove these objects matters. diff --git a/Stack/Stack.playground/Contents.swift b/Stack/Stack.playground/Contents.swift index 1eb705f20..e0632f8b4 100644 --- a/Stack/Stack.playground/Contents.swift +++ b/Stack/Stack.playground/Contents.swift @@ -1,4 +1,4 @@ -/* +/** Stack A stack is like an array but with limited functionality. You can only push @@ -9,27 +9,51 @@ last is the first one to come off with the next pop. Push and pop are O(1) operations. + + ## Usage + ``` + var myStack = Stack(array: []) + myStack.push(10) + myStack.push(3) + myStack.push(57) + myStack.pop() // 57 + myStack.pop() // 3 + ``` */ - public struct Stack { + + /// Datastructure consisting of a generic item. fileprivate var array = [T]() + /// The number of items in the stack. public var count: Int { return array.count } + /// Verifies if the stack is empty. public var isEmpty: Bool { return array.isEmpty } + /** + Pushes an item to the top of the stack. + + - Parameter element: The item being pushed. + */ public mutating func push(_ element: T) { array.append(element) } + /** + Removes and returns the item at the top of the stack. + + - Returns: The item at the top of the stack. + */ public mutating func pop() -> T? { return array.popLast() } + /// Returns the item at the top of the stack. public var top: T? { return array.last } diff --git a/Stack/Stack.playground/contents.xcplayground b/Stack/Stack.playground/contents.xcplayground index 5da2641c9..a40c0f554 100644 --- a/Stack/Stack.playground/contents.xcplayground +++ b/Stack/Stack.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Stack/Stack.swift b/Stack/Stack.swift index c3f42ea44..b12f713f5 100644 --- a/Stack/Stack.swift +++ b/Stack/Stack.swift @@ -1,37 +1,36 @@ /* - Last-in first-out stack (LIFO) - - Push and pop are O(1) operations. -*/ + Last-in first-out stack (LIFO) + Push and pop are O(1) operations. + */ public struct Stack { fileprivate var array = [T]() - + public var isEmpty: Bool { return array.isEmpty } - + public var count: Int { return array.count } - + public mutating func push(_ element: T) { array.append(element) } - + public mutating func pop() -> T? { return array.popLast() } - + public var top: T? { return array.last } } extension Stack: Sequence { - public func makeIterator() -> AnyIterator { - var curr = self - return AnyIterator { _ -> T? in - return curr.pop() - } + public func makeIterator() -> AnyIterator { + var curr = self + return AnyIterator { + return curr.pop() } + } } diff --git a/Ternary Search Tree/TST.playground/Contents.swift b/Ternary Search Tree/TST.playground/Contents.swift index bacf56aee..0f576835b 100644 --- a/Ternary Search Tree/TST.playground/Contents.swift +++ b/Ternary Search Tree/TST.playground/Contents.swift @@ -3,6 +3,11 @@ import Cocoa import Foundation +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + let treeOfStrings = TernarySearchTree() var testStrings: [(key: String, data: String)] = [] diff --git a/Ternary Search Tree/Tests/TernarySearchTreeTests.swift b/Ternary Search Tree/Tests/TernarySearchTreeTests.swift index 4a1816412..a595e40a3 100644 --- a/Ternary Search Tree/Tests/TernarySearchTreeTests.swift +++ b/Ternary Search Tree/Tests/TernarySearchTreeTests.swift @@ -10,61 +10,68 @@ import XCTest class TernarySearchTreeTests: XCTestCase { - let testCount = 30 + let testCount = 30 - func testCanFindStringInTree() { - var testStrings: [(key: String, data: String)] = [] - let treeOfStrings = TernarySearchTree() + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } - for _ in (1...testCount) { - var randomLength = Int(arc4random_uniform(10)) + func testCanFindStringInTree() { + var testStrings: [(key: String, data: String)] = [] + let treeOfStrings = TernarySearchTree() - var key = Utils.shared.randomAlphaNumericString(withLength: randomLength) + for _ in (1...testCount) { + var randomLength = Int(arc4random_uniform(10)) - while testStrings.contains(where: { $0.key == key}) { - //That key is taken, so we generate a new one with another length - randomLength = Int(arc4random_uniform(10)) - key = Utils.shared.randomAlphaNumericString(withLength: randomLength) - } - let data = Utils.shared.randomAlphaNumericString(withLength: randomLength) - // print("Key: \(key) Data: \(data)") + var key = Utils.shared.randomAlphaNumericString(withLength: randomLength) - if key != "" && data != "" { - testStrings.append((key, data)) - treeOfStrings.insert(data: data, withKey: key) - } - } + while testStrings.contains(where: { $0.key == key}) { + //That key is taken, so we generate a new one with another length + randomLength = Int(arc4random_uniform(10)) + key = Utils.shared.randomAlphaNumericString(withLength: randomLength) + } + let data = Utils.shared.randomAlphaNumericString(withLength: randomLength) + // print("Key: \(key) Data: \(data)") - for aTest in testStrings { - let data = treeOfStrings.find(key: aTest.key) - XCTAssertNotNil(data) - XCTAssertEqual(data, aTest.data) - } + if key != "" && data != "" { + testStrings.append((key, data)) + treeOfStrings.insert(data: data, withKey: key) + } } - func testCanFindNumberInTree() { - var testNums: [(key: String, data: Int)] = [] - let treeOfInts = TernarySearchTree() - for _ in (1...testCount) { - let randomNum = Int(arc4random_uniform(UInt32.max)) - var randomLength = Int(arc4random_uniform(10)) - var key = Utils.shared.randomAlphaNumericString(withLength: randomLength) - while testNums.contains(where: { $0.key == key}) { - //That key is taken, so we generate a new one with another length - randomLength = Int(arc4random_uniform(10)) - key = Utils.shared.randomAlphaNumericString(withLength: randomLength) - } + for aTest in testStrings { + let data = treeOfStrings.find(key: aTest.key) + XCTAssertNotNil(data) + XCTAssertEqual(data, aTest.data) + } + } + + func testCanFindNumberInTree() { + var testNums: [(key: String, data: Int)] = [] + let treeOfInts = TernarySearchTree() + for _ in (1...testCount) { + let randomNum = Int(arc4random_uniform(UInt32.max)) + var randomLength = Int(arc4random_uniform(10)) + var key = Utils.shared.randomAlphaNumericString(withLength: randomLength) + while testNums.contains(where: { $0.key == key}) { + //That key is taken, so we generate a new one with another length + randomLength = Int(arc4random_uniform(10)) + key = Utils.shared.randomAlphaNumericString(withLength: randomLength) + } - if key != "" { - testNums.append((key, randomNum)) - treeOfInts.insert(data: randomNum, withKey: key) - } - } + if key != "" { + testNums.append((key, randomNum)) + treeOfInts.insert(data: randomNum, withKey: key) + } + } - for aTest in testNums { - let data = treeOfInts.find(key: aTest.key) - XCTAssertNotNil(data) - XCTAssertEqual(data, aTest.data) - } + for aTest in testNums { + let data = treeOfInts.find(key: aTest.key) + XCTAssertNotNil(data) + XCTAssertEqual(data, aTest.data) } + } } diff --git a/Ternary Search Tree/Tests/Tests.xcodeproj/project.pbxproj b/Ternary Search Tree/Tests/Tests.xcodeproj/project.pbxproj index 7afaca944..1d48e705a 100644 --- a/Ternary Search Tree/Tests/Tests.xcodeproj/project.pbxproj +++ b/Ternary Search Tree/Tests/Tests.xcodeproj/project.pbxproj @@ -220,7 +220,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -233,7 +233,7 @@ PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Ternary Search Tree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Ternary Search Tree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Ternary Search Tree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift b/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift index c85f56a68..5c7671688 100644 --- a/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift +++ b/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift @@ -217,7 +217,7 @@ extension ThreadedBinaryTree { */ public func search(_ value: T) -> ThreadedBinaryTree? { var node: ThreadedBinaryTree? = self - while case let n? = node { + while let n = node { if value < n.value { node = n.left } else if value > n.value { @@ -252,7 +252,7 @@ extension ThreadedBinaryTree { */ public func minimum() -> ThreadedBinaryTree { var node = self - while case let next? = node.left { + while let next = node.left { node = next } return node @@ -263,7 +263,7 @@ extension ThreadedBinaryTree { */ public func maximum() -> ThreadedBinaryTree { var node = self - while case let next? = node.right { + while let next = node.right { node = next } return node @@ -276,7 +276,7 @@ extension ThreadedBinaryTree { public func depth() -> Int { var node = self var edges = 0 - while case let parent? = node.parent { + while let parent = node.parent { node = parent edges += 1 } diff --git a/Threaded Binary Tree/ThreadedBinaryTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Threaded Binary Tree/ThreadedBinaryTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Threaded Binary Tree/ThreadedBinaryTree.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Topological Sort/Topological Sort.playground/Contents.swift b/Topological Sort/Topological Sort.playground/Contents.swift index 8f78c388a..3921d37fc 100644 --- a/Topological Sort/Topological Sort.playground/Contents.swift +++ b/Topological Sort/Topological Sort.playground/Contents.swift @@ -1,5 +1,10 @@ //: Playground - noun: a place where people can play +// last checked with Xcode 9.0b4 +#if swift(>=4.0) +print("Hello, Swift 4!") +#endif + import UIKit let graph = Graph() diff --git a/Topological Sort/Topological Sort.playground/timeline.xctimeline b/Topological Sort/Topological Sort.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Topological Sort/Topological Sort.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - 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/Treap/Treap/Treap.xcodeproj/project.pbxproj b/Treap/Treap/Treap.xcodeproj/project.pbxproj index df9a0d6ee..d8393e1c5 100644 --- a/Treap/Treap/Treap.xcodeproj/project.pbxproj +++ b/Treap/Treap/Treap.xcodeproj/project.pbxproj @@ -243,7 +243,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.willowtree.TreapTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -255,7 +255,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.willowtree.TreapTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Treap/Treap/TreapTests/TreapTests.swift b/Treap/Treap/TreapTests/TreapTests.swift index d4fc4cfdb..a5d156ec9 100644 --- a/Treap/Treap/TreapTests/TreapTests.swift +++ b/Treap/Treap/TreapTests/TreapTests.swift @@ -38,6 +38,13 @@ class TreapTests: XCTestCase { super.tearDown() } + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + func testSanity() { var treap = Treap.empty treap = treap.set(key: 5, val: "a").set(key: 7, val: "b") diff --git a/Tree/README.markdown b/Tree/README.markdown index 566864a8f..40129cab5 100644 --- a/Tree/README.markdown +++ b/Tree/README.markdown @@ -1,5 +1,8 @@ # Trees +> This topic has been tutorialized [here](https://www.raywenderlich.com/138190/swift-algorithm-club-swift-tree-data-structure) + + A tree represents hierarchical relationships between objects. This is a tree: ![A tree](Images/Tree.png) diff --git a/Tree/Tree.playground/Contents.swift b/Tree/Tree.playground/Contents.swift index d59b7eedb..1fad157a0 100644 --- a/Tree/Tree.playground/Contents.swift +++ b/Tree/Tree.playground/Contents.swift @@ -1,31 +1,5 @@ //: Playground - noun: a place where people can play -public class TreeNode { - public var value: T - - public weak var parent: TreeNode? - public var children = [TreeNode]() - - public init(value: T) { - self.value = value - } - - public func addChild(_ node: TreeNode) { - children.append(node) - node.parent = self - } -} - -extension TreeNode: CustomStringConvertible { - public var description: String { - var s = "\(value)" - if !children.isEmpty { - s += " {" + children.map { $0.description }.joined(separator: ", ") + "}" - } - return s - } -} - let tree = TreeNode(value: "beverages") let hotNode = TreeNode(value: "hot") diff --git a/Tree/Tree.playground/Sources/Tree.swift b/Tree/Tree.playground/Sources/Tree.swift new file mode 100644 index 000000000..fa49f25e9 --- /dev/null +++ b/Tree/Tree.playground/Sources/Tree.swift @@ -0,0 +1,39 @@ +public class TreeNode { + public var value: T + + public weak var parent: TreeNode? + public var children = [TreeNode]() + + public init(value: T) { + self.value = value + } + + public func addChild(_ node: TreeNode) { + children.append(node) + node.parent = self + } +} + +extension TreeNode: CustomStringConvertible { + public var description: String { + var s = "\(value)" + if !children.isEmpty { + s += " {" + children.map { $0.description }.joined(separator: ", ") + "}" + } + return s + } +} + +extension TreeNode where T: Equatable { + public func search(_ value: T) -> TreeNode? { + if value == self.value { + return self + } + for child in children { + if let found = child.search(value) { + return found + } + } + return nil + } +} diff --git a/Tree/Tree.playground/contents.xcplayground b/Tree/Tree.playground/contents.xcplayground index 06828af92..69d154d1e 100644 --- a/Tree/Tree.playground/contents.xcplayground +++ b/Tree/Tree.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Tree/Tree.playground/timeline.xctimeline b/Tree/Tree.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Tree/Tree.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Tree/Tree.swift b/Tree/Tree.swift index fa49f25e9..08cb61eb5 100644 --- a/Tree/Tree.swift +++ b/Tree/Tree.swift @@ -1,39 +1,40 @@ public class TreeNode { - public var value: T + public var value: T - public weak var parent: TreeNode? - public var children = [TreeNode]() + public weak var parent: TreeNode? + public var children = [TreeNode]() - public init(value: T) { - self.value = value - } + public init(value: T) { + self.value = value + } - public func addChild(_ node: TreeNode) { - children.append(node) - node.parent = self - } + public func addChild(_ node: TreeNode) { + children.append(node) + node.parent = self + } } extension TreeNode: CustomStringConvertible { - public var description: String { - var s = "\(value)" - if !children.isEmpty { - s += " {" + children.map { $0.description }.joined(separator: ", ") + "}" + public var description: String { + var s = "\(value)" + if !children.isEmpty { + s += " {" + children.map { $0.description }.joined(separator: ", ") + "}" + } + return s } - return s - } } extension TreeNode where T: Equatable { - public func search(_ value: T) -> TreeNode? { - if value == self.value { - return self + public func search(_ value: T) -> TreeNode? { + if value == self.value { + return self + } + for child in children { + if let found = child.search(value) { + return found + } + } + return nil } - for child in children { - if let found = child.search(value) { - return found - } - } - return nil - } } + diff --git a/Trie/ReadMe.md b/Trie/ReadMe.md index 356f6887e..8d0c45c8a 100644 --- a/Trie/ReadMe.md +++ b/Trie/ReadMe.md @@ -1,5 +1,7 @@ # Trie +> This topic has been tutorialized [here](https://www.raywenderlich.com/139410/swift-algorithm-club-swift-trie-data-structure) + ## What is a Trie? A `Trie`, (also known as a prefix tree, or radix tree in some other implementations) is a special type of tree used to store associative data structures. A `Trie` for a dictionary might look like this: @@ -25,29 +27,29 @@ Tries are very useful for certain situations. Here are some of the advantages: ```swift func contains(word: String) -> Bool { - guard !word.isEmpty else { return false } + guard !word.isEmpty else { return false } - // 1 - var currentNode = root + // 1 + var currentNode = root - // 2 - var characters = Array(word.lowercased().characters) - var currentIndex = 0 + // 2 + var characters = Array(word.lowercased()) + var currentIndex = 0 - // 3 - while currentIndex < characters.count, - let child = currentNode.children[characters[currentIndex]] { - - currentNode = child - currentIndex += 1 - } - - // 4 - if currentIndex == characters.count && currentNode.isTerminating { - return true - } else { - return false - } + // 3 + while currentIndex < characters.count, + let child = currentNode.children[characters[currentIndex]] { + + currentNode = child + currentIndex += 1 + } + + // 4 + if currentIndex == characters.count && currentNode.isTerminating { + return true + } else { + return false + } } ``` @@ -72,7 +74,7 @@ func insert(word: String) { var currentNode = root // 2 - for character in word.lowercased().characters { + for character in word.lowercased() { // 3 if let childNode = currentNode.children[character] { currentNode = childNode diff --git a/Trie/Trie/Trie.xcodeproj/project.pbxproj b/Trie/Trie/Trie.xcodeproj/project.pbxproj index 90c5734cc..6b7cc9b61 100644 --- a/Trie/Trie/Trie.xcodeproj/project.pbxproj +++ b/Trie/Trie/Trie.xcodeproj/project.pbxproj @@ -87,7 +87,9 @@ EB798E191DFEF79900F0628D /* TrieUITests */, EB798DFB1DFEF79900F0628D /* Products */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; EB798DFB1DFEF79900F0628D /* Products */ = { isa = PBXGroup; @@ -195,20 +197,23 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0810; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Rick Zaccone"; TargetAttributes = { EB798DF91DFEF79900F0628D = { CreatedOnToolsVersion = 8.1; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; EB798E0A1DFEF79900F0628D = { CreatedOnToolsVersion = 8.1; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; TestTargetID = EB798DF91DFEF79900F0628D; }; EB798E151DFEF79900F0628D = { CreatedOnToolsVersion = 8.1; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; TestTargetID = EB798DF91DFEF79900F0628D; }; @@ -326,15 +331,23 @@ 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_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_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_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -376,15 +389,23 @@ 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_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_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_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -418,7 +439,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = edu.bucknell.zaccone.Trie; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -431,7 +452,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = edu.bucknell.zaccone.Trie; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -445,7 +466,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = edu.bucknell.zaccone.TrieTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Trie.app/Contents/MacOS/Trie"; }; name = Debug; @@ -460,7 +481,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = edu.bucknell.zaccone.TrieTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Trie.app/Contents/MacOS/Trie"; }; name = Release; @@ -474,7 +495,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = edu.bucknell.zaccone.TrieUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TEST_TARGET_NAME = Trie; }; name = Debug; @@ -488,7 +509,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = edu.bucknell.zaccone.TrieUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TEST_TARGET_NAME = Trie; }; name = Release; diff --git a/Trie/Trie/Trie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Trie/Trie/Trie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Trie/Trie/Trie.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Trie/Trie/Trie/AppDelegate.swift b/Trie/Trie/Trie/AppDelegate.swift index b2c46695b..48dc24673 100644 --- a/Trie/Trie/Trie/AppDelegate.swift +++ b/Trie/Trie/Trie/AppDelegate.swift @@ -11,12 +11,12 @@ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - func applicationDidFinishLaunching(_ aNotification: Notification) { - // Insert code here to initialize your application - } + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Insert code here to initialize your application + } - func applicationWillTerminate(_ aNotification: Notification) { - // Insert code here to tear down your application - } + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } } diff --git a/Trie/Trie/Trie/Trie.swift b/Trie/Trie/Trie/Trie.swift index 856384fff..3d5031cf3 100644 --- a/Trie/Trie/Trie/Trie.swift +++ b/Trie/Trie/Trie/Trie.swift @@ -43,54 +43,53 @@ class TrieNode { /// A trie data structure containing words. Each node is a single /// character of a word. class Trie: NSObject, NSCoding { - typealias Node = TrieNode - /// The number of words in the trie - public var count: Int { - return wordCount - } - /// Is the trie empty? - public var isEmpty: Bool { - return wordCount == 0 - } - /// All words currently in the trie - public var words: [String] { - return wordsInSubtrie(rootNode: root, partialWord: "") - } - fileprivate let root: Node - fileprivate var wordCount: Int - - /// Creates an empty trie. - override init() { - root = Node() - wordCount = 0 - super.init() - } + typealias Node = TrieNode + /// The number of words in the trie + public var count: Int { + return wordCount + } + /// Is the trie empty? + public var isEmpty: Bool { + return wordCount == 0 + } + /// All words currently in the trie + public var words: [String] { + return wordsInSubtrie(rootNode: root, partialWord: "") + } + fileprivate let root: Node + fileprivate var wordCount: Int + + /// Creates an empty trie. + override init() { + root = Node() + wordCount = 0 + super.init() + } - // MARK: NSCoding + // MARK: NSCoding - /// Initializes the trie with words from an archive - /// - /// - Parameter decoder: Decodes the archive - required convenience init?(coder decoder: NSCoder) { - self.init() - let words = decoder.decodeObject(forKey: "words") as? [String] - for word in words! { - self.insert(word: word) - } + /// Initializes the trie with words from an archive + /// + /// - Parameter decoder: Decodes the archive + required convenience init?(coder decoder: NSCoder) { + self.init() + let words = decoder.decodeObject(forKey: "words") as? [String] + for word in words! { + self.insert(word: word) } + } - /// Encodes the words in the trie by putting them in an array then encoding - /// the array. - /// - /// - Parameter coder: The object that will encode the array - func encode(with coder: NSCoder) { - coder.encode(self.words, forKey: "words") - } + /// Encodes the words in the trie by putting them in an array then encoding + /// the array. + /// + /// - Parameter coder: The object that will encode the array + func encode(with coder: NSCoder) { + coder.encode(self.words, forKey: "words") + } } // MARK: - Adds methods: insert, remove, contains extension Trie { - /// Inserts a word into the trie. If the word is already present, /// there is no change. /// @@ -100,7 +99,7 @@ extension Trie { return } var currentNode = root - for character in word.lowercased().characters { + for character in word.lowercased() { if let childNode = currentNode.children[character] { currentNode = childNode } else { @@ -118,20 +117,23 @@ 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 } var currentNode = root - for character in word.lowercased().characters { + for character in word.lowercased() { guard let childNode = currentNode.children[character] else { return false } currentNode = childNode } - return currentNode.isTerminating + return matchPrefix || currentNode.isTerminating } /// Attempts to walk to the last node of a word. The @@ -142,14 +144,14 @@ extension Trie { /// - Returns: the node where the search ended, nil if the /// search failed. private func findLastNodeOf(word: String) -> Node? { - var currentNode = root - for character in word.lowercased().characters { - guard let childNode = currentNode.children[character] else { - return nil - } - currentNode = childNode + var currentNode = root + for character in word.lowercased() { + guard let childNode = currentNode.children[character] else { + return nil } - return currentNode + currentNode = childNode + } + return currentNode } /// Attempts to walk to the terminating node of a word. The @@ -160,10 +162,9 @@ extension Trie { /// search failed. private func findTerminalNodeOf(word: String) -> Node? { if let lastNode = findLastNodeOf(word: word) { - return lastNode.isTerminating ? lastNode : nil + return lastNode.isTerminating ? lastNode : nil } return nil - } /// Deletes a word from the trie by starting with the last letter @@ -237,17 +238,17 @@ extension Trie { /// - prefix: the letters for word prefix /// - Returns: the words in the subtrie that start with prefix func findWordsWithPrefix(prefix: String) -> [String] { - var words = [String]() - let prefixLowerCased = prefix.lowercased() - if let lastNode = findLastNodeOf(word: prefixLowerCased) { - if lastNode.isTerminating { - words.append(prefixLowerCased) - } - for childNode in lastNode.children.values { - let childWords = wordsInSubtrie(rootNode: childNode, partialWord: prefixLowerCased) - words += childWords - } + var words = [String]() + let prefixLowerCased = prefix.lowercased() + if let lastNode = findLastNodeOf(word: prefixLowerCased) { + if lastNode.isTerminating { + words.append(prefixLowerCased) + } + for childNode in lastNode.children.values { + let childWords = wordsInSubtrie(rootNode: childNode, partialWord: prefixLowerCased) + words += childWords } - return words + } + return words } } diff --git a/Trie/Trie/Trie/ViewController.swift b/Trie/Trie/Trie/ViewController.swift index 928b01f4c..8ce34f6c7 100644 --- a/Trie/Trie/Trie/ViewController.swift +++ b/Trie/Trie/Trie/ViewController.swift @@ -10,16 +10,16 @@ import Cocoa class ViewController: NSViewController { - override func viewDidLoad() { - super.viewDidLoad() + override func viewDidLoad() { + super.viewDidLoad() - // Do any additional setup after loading the view. - } + // Do any additional setup after loading the view. + } - override var representedObject: Any? { - didSet { - // Update the view, if already loaded. - } + override var representedObject: Any? { + didSet { + // Update the view, if already loaded. } + } } diff --git a/Trie/Trie/TrieTests/TrieTests.swift b/Trie/Trie/TrieTests/TrieTests.swift index b69c5666e..59da2cd9b 100644 --- a/Trie/Trie/TrieTests/TrieTests.swift +++ b/Trie/Trie/TrieTests/TrieTests.swift @@ -10,186 +10,209 @@ import XCTest @testable import Trie class TrieTests: XCTestCase { - var wordArray: [String]? - var trie = Trie() - - /// Makes sure that the wordArray and trie are initialized before each test. - override func setUp() { - super.setUp() - createWordArray() - insertWordsIntoTrie() + var wordArray: [String]? + var trie = Trie() + + /// Makes sure that the wordArray and trie are initialized before each test. + override func setUp() { + super.setUp() + createWordArray() + insertWordsIntoTrie() + } + + /// Don't need to do anything here because the wordArray and trie should + /// stay around. + override func tearDown() { + super.tearDown() + } + + /// Reads words from the dictionary file and inserts them into an array. If + /// the word array already has words, do nothing. This allows running all + /// tests without repeatedly filling the array with the same values. + func createWordArray() { + guard wordArray == nil else { + return } - - /// Don't need to do anything here because the wordArray and trie should - /// stay around. - override func tearDown() { - super.tearDown() - } - - /// Reads words from the dictionary file and inserts them into an array. If - /// the word array already has words, do nothing. This allows running all - /// tests without repeatedly filling the array with the same values. - func createWordArray() { - guard wordArray == nil else { - return - } - let resourcePath = Bundle.main.resourcePath! as NSString - let fileName = "dictionary.txt" - let filePath = resourcePath.appendingPathComponent(fileName) - - var data: String? - do { - data = try String(contentsOfFile: filePath, encoding: String.Encoding.utf8) - } catch let error as NSError { - XCTAssertNil(error) - } - XCTAssertNotNil(data) - let dictionarySize = 162825 - wordArray = data!.components(separatedBy: "\n") - XCTAssertEqual(wordArray!.count, dictionarySize) - } - - /// Inserts words into a trie. If the trie is non-empty, don't do anything. - func insertWordsIntoTrie() { - guard let wordArray = wordArray, trie.count == 0 else { return } - for word in wordArray { - trie.insert(word: word) - } - } - - /// Tests that a newly created trie has zero words. - func testCreate() { - let trie = Trie() - XCTAssertEqual(trie.count, 0) - } - - /// Tests the insert method - func testInsert() { - let trie = Trie() - trie.insert(word: "cute") - trie.insert(word: "cutie") - trie.insert(word: "fred") - XCTAssertTrue(trie.contains(word: "cute")) - XCTAssertFalse(trie.contains(word: "cut")) - trie.insert(word: "cut") - XCTAssertTrue(trie.contains(word: "cut")) - XCTAssertEqual(trie.count, 4) - } - - /// Tests the remove method - func testRemove() { - let trie = Trie() - trie.insert(word: "cute") - trie.insert(word: "cut") - XCTAssertEqual(trie.count, 2) - trie.remove(word: "cute") - XCTAssertTrue(trie.contains(word: "cut")) - XCTAssertFalse(trie.contains(word: "cute")) - XCTAssertEqual(trie.count, 1) + let resourcePath = Bundle.main.resourcePath! as NSString + let fileName = "dictionary.txt" + let filePath = resourcePath.appendingPathComponent(fileName) + + var data: String? + do { + data = try String(contentsOfFile: filePath, encoding: String.Encoding.utf8) + } catch let error as NSError { + XCTAssertNil(error) } - - /// Tests the words property - func testWords() { - let trie = Trie() - var words = trie.words - XCTAssertEqual(words.count, 0) - trie.insert(word: "foobar") - words = trie.words - XCTAssertEqual(words[0], "foobar") - XCTAssertEqual(words.count, 1) + XCTAssertNotNil(data) + let dictionarySize = 162825 + wordArray = data!.components(separatedBy: "\n") + XCTAssertEqual(wordArray!.count, dictionarySize) + } + + /// Inserts words into a trie. If the trie is non-empty, don't do anything. + func insertWordsIntoTrie() { + guard let wordArray = wordArray, trie.count == 0 else { return } + for word in wordArray { + trie.insert(word: word) } - - /// Tests the performance of the insert method. - func testInsertPerformance() { - self.measure { - let trie = Trie() - for word in self.wordArray! { - trie.insert(word: word) - } - } - XCTAssertGreaterThan(trie.count, 0) - XCTAssertEqual(trie.count, wordArray?.count) + } + + /// Tests that a newly created trie has zero words. + func testCreate() { + let trie = Trie() + XCTAssertEqual(trie.count, 0) + } + + /// Tests the insert method + func testInsert() { + let trie = Trie() + trie.insert(word: "cute") + trie.insert(word: "cutie") + trie.insert(word: "fred") + XCTAssertTrue(trie.contains(word: "cute")) + XCTAssertFalse(trie.contains(word: "cut")) + trie.insert(word: "cut") + XCTAssertTrue(trie.contains(word: "cut")) + XCTAssertEqual(trie.count, 4) + } + + /// Tests the remove method + func testRemove() { + let trie = Trie() + trie.insert(word: "cute") + trie.insert(word: "cut") + XCTAssertEqual(trie.count, 2) + trie.remove(word: "cute") + XCTAssertTrue(trie.contains(word: "cut")) + XCTAssertFalse(trie.contains(word: "cute")) + XCTAssertEqual(trie.count, 1) + } + + /// Tests the words property + func testWords() { + let trie = Trie() + var words = trie.words + XCTAssertEqual(words.count, 0) + trie.insert(word: "foobar") + words = trie.words + XCTAssertEqual(words[0], "foobar") + XCTAssertEqual(words.count, 1) + } + + /// Tests the performance of the insert method. + func testInsertPerformance() { + self.measure { + let trie = Trie() + for word in self.wordArray! { + trie.insert(word: word) + } } - - /// Tests the performance of the insert method when the words are already - /// present. - func testInsertAgainPerformance() { - self.measure { - for word in self.wordArray! { - self.trie.insert(word: word) - } - } + XCTAssertGreaterThan(trie.count, 0) + XCTAssertEqual(trie.count, wordArray?.count) + } + + /// Tests the performance of the insert method when the words are already + /// present. + func testInsertAgainPerformance() { + self.measure { + for word in self.wordArray! { + self.trie.insert(word: word) + } } - - /// Tests the performance of the contains method. - func testContainsPerformance() { - self.measure { - for word in self.wordArray! { - XCTAssertTrue(self.trie.contains(word: word)) - } - } + } + + /// Tests the performance of the contains method. + func testContainsPerformance() { + self.measure { + for word in self.wordArray! { + XCTAssertTrue(self.trie.contains(word: word)) + } } + } - /// Tests the performance of the remove method. Since setup has already put - /// words into the trie, remove them before measuring performance. - func testRemovePerformance() { - for word in self.wordArray! { - self.trie.remove(word: word) - } - self.measure { - self.insertWordsIntoTrie() - for word in self.wordArray! { - self.trie.remove(word: word) - } - } - XCTAssertEqual(trie.count, 0) + /// Tests the performance of the remove method. Since setup has already put + /// words into the trie, remove them before measuring performance. + func testRemovePerformance() { + for word in self.wordArray! { + self.trie.remove(word: word) } - - /// Tests the performance of the words computed property. Also tests to see - /// if it worked properly. - func testWordsPerformance() { - var words: [String]? - self.measure { - words = self.trie.words - } - XCTAssertEqual(words?.count, trie.count) - for word in words! { - XCTAssertTrue(self.trie.contains(word: word)) - } + self.measure { + self.insertWordsIntoTrie() + for word in self.wordArray! { + self.trie.remove(word: word) + } } - - /// Tests the archiving and unarchiving of the trie. - func testArchiveAndUnarchive() { - let resourcePath = Bundle.main.resourcePath! as NSString - let fileName = "dictionary-archive" - let filePath = resourcePath.appendingPathComponent(fileName) - NSKeyedArchiver.archiveRootObject(trie, toFile: filePath) - let trieCopy = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? Trie - XCTAssertEqual(trieCopy?.count, trie.count) - + XCTAssertEqual(trie.count, 0) + } + + /// Tests the performance of the words computed property. Also tests to see + /// if it worked properly. + func testWordsPerformance() { + var words: [String]? + self.measure { + words = self.trie.words } - - func testFindWordsWithPrefix() { - let trie = Trie() - trie.insert(word: "test") - trie.insert(word: "another") - trie.insert(word: "exam") - let wordsAll = trie.findWordsWithPrefix(prefix: "") - XCTAssertEqual(wordsAll.sorted(), ["another", "exam", "test"]) - let words = trie.findWordsWithPrefix(prefix: "ex") - XCTAssertEqual(words, ["exam"]) - trie.insert(word: "examination") - let words2 = trie.findWordsWithPrefix(prefix: "exam") - XCTAssertEqual(words2, ["exam", "examination"]) - let noWords = trie.findWordsWithPrefix(prefix: "tee") - XCTAssertEqual(noWords, []) - let unicodeWord = "😬😎" - trie.insert(word: unicodeWord) - let wordsUnicode = trie.findWordsWithPrefix(prefix: "😬") - XCTAssertEqual(wordsUnicode, [unicodeWord]) - trie.insert(word: "Team") - let wordsUpperCase = trie.findWordsWithPrefix(prefix: "Te") - XCTAssertEqual(wordsUpperCase.sorted(), ["team", "test"]) - + XCTAssertEqual(words?.count, trie.count) + for word in words! { + XCTAssertTrue(self.trie.contains(word: word)) } + } + + /// Tests the archiving and unarchiving of the trie. + func testArchiveAndUnarchive() { + let resourcePath = Bundle.main.resourcePath! as NSString + let fileName = "dictionary-archive" + let filePath = resourcePath.appendingPathComponent(fileName) + NSKeyedArchiver.archiveRootObject(trie, toFile: filePath) + 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") + trie.insert(word: "another") + trie.insert(word: "exam") + let wordsAll = trie.findWordsWithPrefix(prefix: "") + XCTAssertEqual(wordsAll.sorted(), ["another", "exam", "test"]) + let words = trie.findWordsWithPrefix(prefix: "ex") + XCTAssertEqual(words, ["exam"]) + trie.insert(word: "examination") + let words2 = trie.findWordsWithPrefix(prefix: "exam") + XCTAssertEqual(words2, ["exam", "examination"]) + let noWords = trie.findWordsWithPrefix(prefix: "tee") + XCTAssertEqual(noWords, []) + let unicodeWord = "😬😎" + trie.insert(word: unicodeWord) + let wordsUnicode = trie.findWordsWithPrefix(prefix: "😬") + XCTAssertEqual(wordsUnicode, [unicodeWord]) + trie.insert(word: "Team") + 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/Trie/Trie/TrieUITests/TrieUITests.swift b/Trie/Trie/TrieUITests/TrieUITests.swift index acb3b1756..380bf3092 100644 --- a/Trie/Trie/TrieUITests/TrieUITests.swift +++ b/Trie/Trie/TrieUITests/TrieUITests.swift @@ -10,27 +10,27 @@ import XCTest class TrieUITests: XCTestCase { - override func setUp() { - super.setUp() + override func setUp() { + super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. + // Put setup code here. This method is called before the invocation of each test method in the class. - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. - XCUIApplication().launch() + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } - func testExample() { - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } + func testExample() { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } } diff --git a/Two-Sum Problem/README.markdown b/Two-Sum Problem/README.markdown index a20666efd..bbd033343 100644 --- a/Two-Sum Problem/README.markdown +++ b/Two-Sum Problem/README.markdown @@ -1,66 +1,82 @@ # Two-Sum Problem -You're given an array `a` with numbers. Write an algorithm that checks if there are any two entries in the array that add up to a given number `k`. In other words, is there any `a[i] + a[j] == k`? +Given an array of integers and an integer target, return the indices of two numbers that add up to the target. There are a variety of solutions to this problem (some better than others). The following solutions both run in **O(n)** time. # Solution 1 -This solution uses a dictionary to store differences between each element in the array and the sum `k` that we're looking for. The dictionary also stores the indices of each element. +This solution looks at one number at a time, storing each number in the dictionary. It uses the number as the key and the number's index in the array as the value. -With this approach, each key in the dictionary corresponds to a new target value. If one of the successive numbers from the array is equal to one of the dictionary's keys, then we know there exist two numbers that sum to `k`. +For each number n, we know the complementing number to sum up to the target is `target - n`. By looking up the complement in the dictionary, we'd know whether we've seen the complement before and what its index is. ```swift -func twoSumProblem(_ a: [Int], k: Int) -> ((Int, Int))? { - var dict = [Int: Int]() - - for i in 0 ..< a.count { - if let newK = dict[a[i]] { - return (newK, i) - } else { - dict[k - a[i]] = i +func twoSum(_ nums: [Int], target: Int) -> (Int, Int)? { + var dict = [Int: Int]() + + // For every number n, + for (currentIndex, n) in nums.enumerated() { + // Find the complement to n that would sum up to the target. + let complement = target - n + + // Check if the complement is in the dictionary. + if let complementIndex = dict[complement] { + return (complementIndex, currentIndex) + } + + // Store n and its index into the dictionary. + dict[n] = currentIndex } - } - - return nil // if empty array or no entries sum to target k + + return nil } ``` -The `twoSumProblem()` function takes two parameters: the array `a` with the numbers, and `k`, the sum we're looking for. It returns the first set of indices `(i, j)` where `a[i] + a[j] == k`, or `nil` if no two numbers add up to `k`. +The `twoSum` function takes two parameters: the `numbers` array and the target sum. It returns the two indicies of the pair of elements that sums up to the target, or `nil` if they can't be found. -Let's take a look at an example and run through the algorithm to see how it works. Given is the array: +Let's run through the algorithm to see how it works. Given the array: ```swift -[ 7, 2, 23, 8, -1, 0, 11, 6 ] +[3, 2, 9, 8] ``` -Let's find out if there exist two entries whose sum is equal to 10. +Let's find out if there exist two entries whose sum is 10. Initially, our dictionary is empty. We begin looping through each element: -- **i = 0**: Is `7` in the dictionary? No. We add the difference between the target `k` and the current number to the dictionary. The difference is `10 - 7 = 3`, so the dictionary key is `3`. The value for that key is the current index, `0`. The dictionary now looks like this: +- **currentIndex = 0** | n = nums[0] = 3 | complement = 10 - 3 = 7 + +Is the complement `7` in the dictionary? No, so we add `3` and its index `0` to the dictionary. ```swift -[ 3: 0 ] +[3: 0] ``` -- **i = 1:** Is `2` in the dictionary? No. Let's do as above and add the difference (`10 - 2 = 8`) and the array index (`1`). The dictionary is: +- **currentIndex = 1** | n = 2 | complement = 10 - 2 = 8 + +Is the complement `8` in the dictionary? No, so we add `2` and its index `1` to the dictionary. ```swift -[ 3: 0, 8: 1 ] +[3: 0, 2: 1] ``` -- **i = 2:** Is `23` in the dictionary? No. Again, we add it to the dictionary. The difference is `10 - 23 = -13` and the index is `2`: +- **currentIndex = 2** | n = 9 | complement = 10 - 9 = 1 + +Is the complement `1` in the dictionary? No, so we add `9` and its index `2` to the dictionary.: ```swift -[ 3: 0, 8: 1, -13: 2 ] +[3: 0, 2: 1, 9: 2] ``` -- **i = 3:** Is `8` in the dictionary? Yes! That means that we have found a pair of entries that sum to our target. Namely the current number `8` and `array[dict[8]]` because `dict[8] = 1`, `array[1] = 2`, and `8 + 2 = 10`. Therefore, we return the corresponding indices of these numbers. For `8` that is the current loop index, `3`. For `2` that is `dict[8]` or `1`. The tuple we return is `(1, 3)`. +- **currentIndex = 3** | n = 8 | complement = 10 - 8 = 2 + +Is the complement `2` in the dictionary? Yes! That means that we have found a pair of entries that sum to the target! -The given array actually has multiple solutions: `(1, 3)` and `(4, 6)`. However, only the first solution is returned. +Therefore, the `complementIndex = dict[2] = 1` and the `currentIndex = 3`. The tuple we return is `(1, 3)`. -The running time of this algorithm is **O(n)** because it potentially may need to look at all array elements. It also requires **O(n)** additional storage space for the dictionary. +If the given array has multiple solutions, only the first solution is returned. + +The running time of this algorithm is **O(n)** because it may look at every element in the array. It also requires **O(n)** additional storage space for the dictionary. # Solution 2 @@ -148,4 +164,8 @@ It's possible, of course, that there are no values for `a[i] + a[j]` that sum to I'm quite enamored by this little algorithm. It shows that with some basic preprocessing on the input data -- sorting it from low to high -- you can turn a tricky problem into a very simple and beautiful algorithm. -*Written for Swift Algorithm Club by Matthijs Hollemans and Daniel Speiser* +## Additional Reading + +* [3Sum / 4Sum](https://github.com/raywenderlich/swift-algorithm-club/tree/master/3Sum%20and%204Sum) + +*Written for Swift Algorithm Club by Matthijs Hollemans and Daniel Speiser updated to swift 4.2 by Farrukh Askari* diff --git a/Two-Sum Problem/Solution 1/2Sum.playground/Contents.swift b/Two-Sum Problem/Solution 1/2Sum.playground/Contents.swift index 00ce05136..00a39b04a 100644 --- a/Two-Sum Problem/Solution 1/2Sum.playground/Contents.swift +++ b/Two-Sum Problem/Solution 1/2Sum.playground/Contents.swift @@ -1,25 +1,20 @@ -//: Playground - noun: a place where people can play +//: Two Sum +// Last checked with: Version 10.0 (10A255) -func twoSumProblem(_ a: [Int], k: Int) -> ((Int, Int))? { - var map = [Int: Int]() - - for i in 0 ..< a.count { - if let newK = map[a[i]] { - return (newK, i) - } else { - map[k - a[i]] = i +func twoSum(_ nums: [Int], target: Int) -> (Int, Int)? { + var dict = [Int: Int]() + + for (currentIndex, n) in nums.enumerated() { + let complement = target - n + + if let complementIndex = dict[complement] { + return (complementIndex, currentIndex) + } + + dict[n] = currentIndex } - } - return nil -} - -let a = [7, 100, 2, 21, 12, 10, 22, 14, 3, 4, 8, 4, 9] -if let (i, j) = twoSumProblem(a, k: 33) { - i // 3 - a[i] // 21 - j // 4 - a[j] // 12 - a[i] + a[j] // 33 + + return nil } -twoSumProblem(a, k: 37) // nil +twoSum([3, 2, 9, 8], target: 10) // expected output: indices 1 and 3 diff --git a/Two-Sum Problem/Solution 1/2Sum.playground/contents.xcplayground b/Two-Sum Problem/Solution 1/2Sum.playground/contents.xcplayground index 06828af92..3de2b51ba 100644 --- a/Two-Sum Problem/Solution 1/2Sum.playground/contents.xcplayground +++ b/Two-Sum Problem/Solution 1/2Sum.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/Two-Sum Problem/Solution 1/2Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Two-Sum Problem/Solution 1/2Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Two-Sum Problem/Solution 1/2Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Two-Sum Problem/Solution 2/2Sum.playground/Contents.swift b/Two-Sum Problem/Solution 2/2Sum.playground/Contents.swift index 9a555f0eb..469c15b5f 100644 --- a/Two-Sum Problem/Solution 2/2Sum.playground/Contents.swift +++ b/Two-Sum Problem/Solution 2/2Sum.playground/Contents.swift @@ -1,4 +1,5 @@ //: Playground - noun: a place where people can play +// Last checked with: Version 10.0 (10A255) func twoSumProblem(_ a: [Int], k: Int) -> ((Int, Int))? { var i = 0 diff --git a/Two-Sum Problem/Solution 2/2Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Two-Sum Problem/Solution 2/2Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Two-Sum Problem/Solution 2/2Sum.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Two-Sum Problem/Solution 2/2Sum.playground/timeline.xctimeline b/Two-Sum Problem/Solution 2/2Sum.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Two-Sum Problem/Solution 2/2Sum.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/Union-Find/UnionFind.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Union-Find/UnionFind.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Union-Find/UnionFind.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Z-Algorithm/README.markdown b/Z-Algorithm/README.markdown index 8b84c28bf..a6423be03 100644 --- a/Z-Algorithm/README.markdown +++ b/Z-Algorithm/README.markdown @@ -28,8 +28,7 @@ This a simple description of the idea that is behind this algorithm. There are a Here is the code of the function that computes the Z-array: ```swift func ZetaAlgorithm(ptrn: String) -> [Int]? { - - let pattern = Array(ptrn.characters) + let pattern = Array(ptrn) let patternLength: Int = pattern.count guard patternLength > 0 else { @@ -131,7 +130,7 @@ The Z-Algorithm discussed above leads to the simplest linear-time string matchin extension String { func indexesOf(pattern: String) -> [Int]? { - let patternLength: Int = pattern.characters.count + let patternLength: Int = pattern.count /* Let's calculate the Z-Algorithm on the concatenation of pattern and text */ let zeta = ZetaAlgorithm(ptrn: pattern + "💲" + self) diff --git a/Z-Algorithm/ZAlgorithm.swift b/Z-Algorithm/ZAlgorithm.swift index 969a3599b..65acaabd1 100644 --- a/Z-Algorithm/ZAlgorithm.swift +++ b/Z-Algorithm/ZAlgorithm.swift @@ -9,8 +9,7 @@ import Foundation func ZetaAlgorithm(ptrn: String) -> [Int]? { - - let pattern = Array(ptrn.characters) + let pattern = Array(ptrn) let patternLength = pattern.count guard patternLength > 0 else { diff --git a/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift b/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift index b1d49eec7..56aa92170 100644 --- a/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift +++ b/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift @@ -1,8 +1,7 @@ //: Playground - noun: a place where people can play func ZetaAlgorithm(ptrn: String) -> [Int]? { - - let pattern = Array(ptrn.characters) + let pattern = Array(ptrn) let patternLength = pattern.count guard patternLength > 0 else { @@ -60,7 +59,7 @@ func ZetaAlgorithm(ptrn: String) -> [Int]? { extension String { func indexesOf(pattern: String) -> [Int]? { - let patternLength = pattern.characters.count + let patternLength = pattern.count let zeta = ZetaAlgorithm(ptrn: pattern + "💲" + self) guard zeta != nil else { diff --git a/Z-Algorithm/ZetaAlgorithm.swift b/Z-Algorithm/ZetaAlgorithm.swift index e60c1617f..f2c453364 100644 --- a/Z-Algorithm/ZetaAlgorithm.swift +++ b/Z-Algorithm/ZetaAlgorithm.swift @@ -11,7 +11,7 @@ import Foundation extension String { func indexesOf(pattern: String) -> [Int]? { - let patternLength = pattern.characters.count + let patternLength = pattern.count let zeta = ZetaAlgorithm(ptrn: pattern + "💲" + self) guard zeta != nil else { diff --git a/gfm-render.sh b/gfm-render.sh new file mode 100755 index 000000000..4e18c8560 --- /dev/null +++ b/gfm-render.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -e + +# $1 - readme file name +function render_markdown_to_html { + # escape escaping characters on Darwin only + content=$( + cat "$1" \ + | sed 's/\\/\\\\/g' \ + | sed 's/"/\\"/g' \ + | sed $'s/\t/\\\\t/g' \ + | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/\\\n/g' \ + ) + + # network call to GitHub API + json="{\"text\":\"$content\",\"mode\":\"gfm\",\"context\":\"$USERNAME/swift-algorithm-club\"}" + echo -e "$(curl -s --data "$json" -u $USERNAME:$TOKEN https://api.github.com/markdown)" +} + +# download github systax highlight stylesheet +echo "> Downloading github-light.css..." +curl -s -O https://raw.githubusercontent.com/primer/github-syntax-light/master/lib/github-light.css + +# slightly modify the main stylesheet +echo "> Modifying github-light.css..." +cat >> github-light.css << EOF +#container { + margin: 0 auto; + width: 75%; + min-width: 768px; + max-width: 896px; + position: relative; +} + +body { + font-size: 18px; +} + +code { + padding: 0.2em; + margin: 0; + font-size: 85%; + background-color: #f6f8fa; + line-height: 1.45; + border-radius: 3px +} + +pre code { + padding: 0px; + background-color: transparent; +} + +.highlight { + margin: 0px; + padding: 0px 16px; + font-size: 85%; + line-height: 1.45; + overflow: auto; + background-color: #f6f8fa; + border-radius: 3px; +} + +@media (max-width: 768px) { + #container { + position: absolute; + margin: 0; + width: 100%; + height: 100%; + min-width: 100%; + } +} +EOF + +# other markdown articles +for title in "What are Algorithms" "Big-O Notation" "Algorithm Design" "Why Algorithms"; do + echo "> Generating $title.html..." + + cat > "$title.html" << EOF + + + $title + + + +
$(render_markdown_to_html "$title.markdown")
+ + +EOF +done + +# if index.html does not exist, create one; +# otherwise, empty its content. +echo "> Generating index.html..." +cat > index.html << EOF + + + Swift Algorithm Club + + + +
$(render_markdown_to_html README.markdown | sed 's/.markdown/.html/g')
+ + +EOF + +# iterate immediate directories +find . -maxdepth 1 -type d | while read folder; do + readme='' + + # get the right extension for the README file if there is one + if [[ -f $folder/README.md ]]; then readme="$folder/README.md"; fi + if [[ -f $folder/README.markdown ]]; then readme="$folder/README.markdown"; fi + + # skip if there is no README or it it the README of the repository + if [[ (-z $readme) || $readme == "./README.markdown" ]]; then continue; fi + + # render README to HTML + name=$(basename "$folder") + echo "> Generating $name/index.html..." + + cat > "$folder/index.html" << EOF + + + $name + + + +
$(render_markdown_to_html "$readme")
+ + +EOF +done + +# push to gh-pages +if [[ $CI = true ]]; then + git checkout -b gh-pages + git add . + git commit -m "$Generated by TravisCI on $(date +%D)" + git push -f https://$TOKEN@github.com/$USERNAME/swift-algorithm-club.git gh-pages +fi diff --git a/swift-algorithm-club.xcworkspace/contents.xcworkspacedata b/swift-algorithm-club.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7ca193aca..000000000 --- a/swift-algorithm-club.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,2712 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -