diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..29f4ca7e1 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing Guidelines + +Want to help out with the Swift Algorithm Club? Great! While we don't have strict templates on the format of each contribution, we do have a few guidelines that should be kept in mind: + +**Readability** + +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** + +A good contribution abides to the [Swift API Guidelines](https://swift.org/documentation/api-design-guidelines/). We review the pull requests with this in mind. + +**Swift Language Guidelines** + +We follow the following Swift [style guide](https://github.com/raywenderlich/swift-style-guide). + +## Contribution Categories + +### Refinement + +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 are most welcome. + +### New Contributions + +Before writing about something new, you should do 2 things: + +1. Check the main page for existing implementations +2. Check the [pull requests](https://github.com/raywenderlich/swift-algorithm-club/pulls) for "claimed" topics. More info on that below. + +If what you have in mind is a new addition, please follow this process when submitting your contribution: + +1. Create a pull request to "claim" an algorithm or data structure. This is to avoid having multiple people working on the same thing. +2. Use this [style guide](https://github.com/raywenderlich/swift-style-guide) for writing code (more or less). +3. Write an explanation of how the algorithm works. Include **plenty of examples** for readers to follow along. Pictures are good. Take a look at [the explanation of quicksort](../Quicksort/) to get an idea. +4. Include your name in the explanation, something like *Written by Your Name* at the end of the document. +5. Add a playground and/or unit tests. + +For the unit tests: + +- Add the unit test project to `.travis.yml` so they will be run on [Travis-CI](https://travis-ci.org/raywenderlich/swift-algorithm-club). Add a line to `.travis.yml` like this: + +``` +- xctool test -project ./Algorithm/Tests/Tests.xcodeproj -scheme Tests +``` + +- Configure the Test project's scheme to run on Travis-CI: + - Open **Product -> Scheme -> Manage Schemes...** + - Uncheck **Autocreate schemes** + - Check **Shared** + +![Screenshot of scheme settings](../Images/scheme-settings-for-travis.png) + +## Want to chat? + +This isn't just a repo with a bunch of code... If you want to learn more about how an algorithm works or want to discuss better ways of solving problems, then open a [Github issue](https://github.com/raywenderlich/swift-algorithm-club/issues) and we'll talk! diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..5c72ed5a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +### Brief Intro + + + +### More Details + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..c52564721 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ + + +### Checklist + +- [ ] I've looked at the [contribution guidelines](https://github.com/raywenderlich/swift-algorithm-club/blob/master/.github/CONTRIBUTING.md). +- [ ] This pull request is complete and ready for review. + +### Description + + + diff --git a/.swiftlint.yml b/.swiftlint.yml index 1434bf637..6cb714110 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -17,8 +17,8 @@ disabled_rules: custom_rules: smiley_face: name: "Smiley Face" - regex: "(\:\))" - match_kinds: + regex: '( :\))' + match_kinds: - comment - string message: "A closing parenthesis smiley :) creates a half-hearted smile, and thus is not preferred. Use :]" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2b2bafd14..000000000 --- a/.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -language: objective-c -osx_image: xcode8 -# 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 ./Run-Length\ Encoding/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/Red-Black Tree/Red-Black Tree 2.playground/contents.xcplayground b/3Sum and 4Sum/4Sum.playground/contents.xcplayground similarity index 100% rename from Red-Black Tree/Red-Black Tree 2.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.playground/Sources/AVLTree.swift b/AVL Tree/AVLTree.playground/Sources/AVLTree.swift index b4b97f0d6..9f8695a83 100644 --- a/AVL Tree/AVLTree.playground/Sources/AVLTree.swift +++ b/AVL Tree/AVLTree.playground/Sources/AVLTree.swift @@ -99,17 +99,11 @@ open class AVLTree { extension TreeNode { public func minimum() -> TreeNode? { - if let leftChild = self.leftChild { - return leftChild.minimum() - } - return self + return leftChild?.minimum() ?? self } public func maximum() -> TreeNode? { - if let rightChild = self.rightChild { - return rightChild.maximum() - } - return self + return rightChild?.maximum() ?? self } } @@ -120,11 +114,7 @@ extension AVLTree { } public func search(input: Key) -> Payload? { - if let result = search(key: input, node: root) { - return result.payload - } else { - return nil - } + return search(key: input, node: root)?.payload } fileprivate func search(key: Key, node: Node?) -> Node? { @@ -351,11 +341,11 @@ extension AVLTree { } } else { // Handle stem cases - if let replacement = node.leftChild?.maximum() , replacement !== node { + if let replacement = node.leftChild?.maximum(), replacement !== node { node.key = replacement.key node.payload = replacement.payload delete(node: replacement) - } else if let replacement = node.rightChild?.minimum() , replacement !== node { + } else if let replacement = node.rightChild?.minimum(), replacement !== node { node.key = replacement.key node.payload = replacement.payload delete(node: replacement) @@ -364,7 +354,6 @@ extension AVLTree { } } - // MARK: - Debugging extension TreeNode: CustomDebugStringConvertible { @@ -385,11 +374,7 @@ extension TreeNode: CustomDebugStringConvertible { extension AVLTree: CustomDebugStringConvertible { public var debugDescription: String { - if let root = root { - return root.debugDescription - } else { - return "[]" - } + return root?.debugDescription ?? "[]" } } @@ -409,10 +394,6 @@ extension TreeNode: CustomStringConvertible { extension AVLTree: CustomStringConvertible { public var description: String { - if let root = root { - return root.description - } else { - return "[]" - } + return root?.description ?? "[]" } } diff --git a/AVL Tree/AVLTree.swift b/AVL Tree/AVLTree.swift index cfcf114c0..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() { } } @@ -99,17 +99,11 @@ open class AVLTree { extension TreeNode { public func minimum() -> TreeNode? { - if let leftChild = self.leftChild { - return leftChild.minimum() - } - return self + return leftChild?.minimum() ?? self } - + public func maximum() -> TreeNode? { - if let rightChild = self.rightChild { - return rightChild.maximum() - } - return self + return rightChild?.maximum() ?? self } } @@ -118,15 +112,11 @@ extension AVLTree { get { return search(input: key) } set { insert(key: key, payload: newValue) } } - + public func search(input: Key) -> Payload? { - if let result = search(key: input, node: root) { - return result.payload - } else { - return nil - } + return search(key: input, node: root)?.payload } - + fileprivate func search(key: Key, node: Node?) -> Node? { if let node = node { if key == node.key { @@ -152,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 { @@ -162,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 { @@ -185,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 @@ -212,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 @@ -222,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 @@ -235,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 @@ -245,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 @@ -256,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 @@ -269,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) } } @@ -309,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 @@ -328,7 +342,7 @@ extension AVLTree { size -= 1 } } - + private func delete(node: Node) { if node.isLeaf { // Just remove and balance up @@ -337,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 @@ -351,11 +365,11 @@ extension AVLTree { } } else { // Handle stem cases - if let replacement = node.leftChild?.maximum() , replacement !== node { + if let replacement = node.leftChild?.maximum(), replacement !== node { node.key = replacement.key node.payload = replacement.payload delete(node: replacement) - } else if let replacement = node.rightChild?.minimum() , replacement !== node { + } else if let replacement = node.rightChild?.minimum(), replacement !== node { node.key = replacement.key node.payload = replacement.payload delete(node: replacement) @@ -364,6 +378,45 @@ 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 @@ -385,11 +438,7 @@ extension TreeNode: CustomDebugStringConvertible { extension AVLTree: CustomDebugStringConvertible { public var debugDescription: String { - if let root = root { - return root.debugDescription - } else { - return "[]" - } + return root?.debugDescription ?? "[]" } } @@ -409,10 +458,6 @@ extension TreeNode: CustomStringConvertible { extension AVLTree: CustomStringConvertible { public var description: String { - if let root = root { - return root.description - } else { - return "[]" - } + return root?.description ?? "[]" } } diff --git a/AVL Tree/README.markdown b/AVL Tree/README.markdown index 355204587..4e974ba71 100644 --- a/AVL Tree/README.markdown +++ b/AVL Tree/README.markdown @@ -1,6 +1,6 @@ # AVL Tree -An AVL tree is a self-balancing form of a [binary search tree](../Binary Search Tree/), in which the height of subtrees differ at most by only 1. +An AVL tree is a self-balancing form of a [binary search tree](../Binary%20Search%20Tree/), in which the height of subtrees differ at most by only 1. A binary tree is *balanced* when its left and right subtrees contain roughly the same number of nodes. That is what makes searching the tree really fast. But if a binary search tree is unbalanced, searching can become really slow. @@ -8,7 +8,7 @@ This is an example of an unbalanced tree: ![Unbalanced tree](Images/Unbalanced.png) -All the children are in the left branch and none are in the right. This is essentially the same as a [linked list](../Linked List/). As a result, searching takes **O(n)** time instead of the much faster **O(log n)** that you'd expect from a binary search tree. +All the children are in the left branch and none are in the right. This is essentially the same as a [linked list](../Linked%20List/). As a result, searching takes **O(n)** time instead of the much faster **O(log n)** that you'd expect from a binary search tree. A balanced version of that tree would look like this: @@ -48,12 +48,12 @@ Each tree node keeps track of its current balance factor in a variable. After in ![Rotation0](Images/RotationStep0.jpg) For the rotation we're using the terminology: -* *Root* - the parent not of the subtrees that will be rotated; +* *Root* - the parent node of the subtrees that will be rotated; * *Pivot* - the node that will become parent (basically will be on the *Root*'s position) after rotation; * *RotationSubtree* - subtree of the *Pivot* upon the side of rotation * *OppositeSubtree* - subtree of the *Pivot* opposite the side of rotation -Let take an example of balancing the unbalanced tree using *Right* (clockwise direction) rotation: +Let take an example of balancing the unbalanced tree using *Right* (clockwise direction) rotation: ![Rotation1](Images/RotationStep1.jpg) ![Rotation2](Images/RotationStep2.jpg) ![Rotation3](Images/RotationStep3.jpg) @@ -76,9 +76,9 @@ Insertion never needs more than 2 rotations. Removal might require up to __log(n ## The code -Most of the code in [AVLTree.swift](AVLTree.swift) is just regular [binary search tree](../Binary Search Tree/) stuff. You'll find this in any implementation of a binary search tree. For example, searching the tree is exactly the same. The only things that an AVL tree does slightly differently are inserting and deleting the nodes. +Most of the code in [AVLTree.swift](AVLTree.swift) is just regular [binary search tree](../Binary%20Search%20Tree/) stuff. You'll find this in any implementation of a binary search tree. For example, searching the tree is exactly the same. The only things that an AVL tree does slightly differently are inserting and deleting the nodes. -> **Note:** If you're a bit fuzzy on the regular operations of a binary search tree, I suggest you [catch up on those first](../Binary Search Tree/). It will make the rest of the AVL tree easier to understand. +> **Note:** If you're a bit fuzzy on the regular operations of a binary search tree, I suggest you [catch up on those first](../Binary%20Search%20Tree/). It will make the rest of the AVL tree easier to understand. The interesting bits are in the `balance()` method which is called after inserting or deleting a node. @@ -86,6 +86,6 @@ The interesting bits are in the `balance()` method which is called after inserti [AVL tree on Wikipedia](https://en.wikipedia.org/wiki/AVL_tree) -AVL tree was the first self-balancing binary tree. These days, the [red-black tree](../Red-Black Tree/) seems to be more popular. +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 88b314e41..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() @@ -27,7 +32,6 @@ class TreeNodeTests: XCTestCase { super.tearDown() } - func testSingleNodeCreationNOPayload() { let treeNode = TreeNode(key: "Building") XCTAssertNil(treeNode.payload, "Payload for this case should be nil") diff --git a/Algorithm Design.markdown b/Algorithm Design.markdown index 23f8ad70e..5d89cb05a 100644 --- a/Algorithm Design.markdown +++ b/Algorithm Design.markdown @@ -18,6 +18,13 @@ And if you only work with small datasets, then a brute force approach may actual ### Divide and conquer -A big problem is often just a whole bunch of much smaller problems. +>"When you change the way you look at things, the things you look at change."
+>Max Planck, Quantum theorist and Nobel Prize Winner -[More to come here] +Divide and conquer is a way of dealing with a large problem by breaking it down into bits and pieces and working your way up towards the solution. + +Instead of seeing the whole problem as a single, huge and complex task you divide the problem in relatively smaller problems that are easier to understand and deal with. + +You solve smaller problems and aggregate the solution until you are left with the solution only. At each step the problem at hand shrinks and the solution gets mature until you have the final correct solution. + +Solving the smaller task and applying the same solution repetitively ( or often times recursively) to other chunks give you the result in less time. 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/APSP/FloydWarshall.swift b/All-Pairs Shortest Paths/APSP/APSP/FloydWarshall.swift index b1a870f2a..7bc78f326 100644 --- a/All-Pairs Shortest Paths/APSP/APSP/FloydWarshall.swift +++ b/All-Pairs Shortest Paths/APSP/APSP/FloydWarshall.swift @@ -175,7 +175,7 @@ public struct FloydWarshallResult: APSPResult where T: Hashable { public func path(fromVertex from: Vertex, toVertex to: Vertex, inGraph graph: AbstractGraph) -> [T]? { if let path = recursePathFrom(fromVertex: from, toVertex: to, path: [ to ], inGraph: graph) { - let pathValues = path.map() { vertex in + let pathValues = path.map { vertex in vertex.data } return pathValues diff --git a/All-Pairs Shortest Paths/APSP/APSP/Helpers.swift b/All-Pairs Shortest Paths/APSP/APSP/Helpers.swift index 3812b3f58..f590a6e50 100644 --- a/All-Pairs Shortest Paths/APSP/APSP/Helpers.swift +++ b/All-Pairs Shortest Paths/APSP/APSP/Helpers.swift @@ -7,7 +7,6 @@ import Foundation - /** Print a matrix, optionally specifying only the cells to display with the triplet (i, j, k) -> matrix[i][j], matrix[i][k], matrix[k][j] */ 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/Array2D.playground/Contents.swift b/Array2D/Array2D.playground/Contents.swift index 7064de59c..d31d77540 100644 --- a/Array2D/Array2D.playground/Contents.swift +++ b/Array2D/Array2D.playground/Contents.swift @@ -1,8 +1,8 @@ /* - Two-dimensional array with a fixed number of rows and columns. - This is mostly handy for games that are played on a grid, such as chess. - Performance is always O(1). -*/ + Two-dimensional array with a fixed number of rows and columns. + This is mostly handy for games that are played on a grid, such as chess. + Performance is always O(1). + */ public struct Array2D { public let columns: Int public let rows: Int @@ -11,21 +11,23 @@ public struct Array2D { public init(columns: Int, rows: Int, initialValue: T) { self.columns = columns self.rows = rows - array = .init(repeatElement(initialValue, count: rows*columns)) + array = .init(repeating: initialValue, count: rows*columns) } public subscript(column: Int, row: Int) -> T { get { + precondition(column < columns, "Column \(column) Index is out of range. Array(columns: \(columns), rows:\(rows))") + precondition(row < rows, "Row \(row) Index is out of range. Array(columns: \(columns), rows:\(rows))") return array[row*columns + column] } set { + precondition(column < columns, "Column \(column) Index is out of range. Array(columns: \(columns), rows:\(rows))") + precondition(row < rows, "Row \(row) Index is out of range. Array(columns: \(columns), rows:\(rows))") array[row*columns + column] = newValue } } } - - // initialization var matrix = Array2D(columns: 3, rows: 5, initialValue: 0) diff --git a/Array2D/Array2D.swift b/Array2D/Array2D.swift index 6d6c01902..caff8373c 100644 --- a/Array2D/Array2D.swift +++ b/Array2D/Array2D.swift @@ -16,12 +16,13 @@ public struct Array2D { public subscript(column: Int, row: Int) -> T { get { + precondition(column < columns, "Column \(column) Index is out of range. Array(columns: \(columns), rows:\(rows))") + precondition(row < rows, "Row \(row) Index is out of range. Array(columns: \(columns), rows:\(rows))") return array[row*columns + column] } set { - precondition(row < rows, "Row \(row) Index is out of range. Array(columns: \(columns), rows:\(rows))") precondition(column < columns, "Column \(column) Index is out of range. Array(columns: \(columns), rows:\(rows))") - + precondition(row < rows, "Row \(row) Index is out of range. Array(columns: \(columns), rows:\(rows))") array[row*columns + column] = newValue } } diff --git a/Array2D/README.markdown b/Array2D/README.markdown index 51e77435e..cf00638e1 100644 --- a/Array2D/README.markdown +++ b/Array2D/README.markdown @@ -1,14 +1,14 @@ # Array2D -In C and Objective-C you can write the following line, +In C and Objective-C, you can write the following line, int cookies[9][7]; -to make a 9x7 grid of cookies. This would create a two-dimensional array of 63 elements. To find the cookie at column 3, row 6, you'd write: +to make a 9x7 grid of cookies. This creates a two-dimensional array of 63 elements. To find the cookie at column 3 and row 6, you can write: myCookie = cookies[3][6]; -Unfortunately, you can't write the above in Swift. To create a multi-dimensional array in Swift you'd have to do something like this: +This statement is not acceptable in Swift. To create a multi-dimensional array in Swift, you can write: ```swift var cookies = [[Int]]() @@ -21,33 +21,33 @@ for _ in 1...9 { } ``` -And then to find a cookie: +Then, to find a cookie, you can write: ```swift let myCookie = cookies[3][6] ``` -Actually, you could create the array in a single line of code, like so: +You can also create the array in a single line of code: ```swift var cookies = [[Int]](repeating: [Int](repeating: 0, count: 7), count: 9) ``` -but that's just ugly. To be fair, you can hide the ugliness in a helper function: +This looks complicated, but you can simplify it with a helper function: ```swift -func dim(count: Int, _ value: T) -> [T] { +func dim(_ count: Int, _ value: T) -> [T] { return [T](repeating: value, count: count) } ``` -And then creating the array looks like this: +Then, you can create the array: ```swift var cookies = dim(9, dim(7, 0)) ``` -Swift infers that the datatype of the array should be `Int` because you specified `0` as the default value of the array elements. To use a string instead, you'd write: +Swift infers that the datatype of the array must be `Int` because you specified `0` as the default value of the array elements. To use a string instead, you can write: ```swift var cookies = dim(9, dim(7, "yum")) @@ -59,27 +59,31 @@ The `dim()` function makes it easy to go into even more dimensions: var threeDimensions = dim(2, dim(3, dim(4, 0))) ``` -The downside of using multi-dimensional arrays in this fashion -- actually, multiple nested arrays -- is that it's easy to lose track of what dimension represents what. +The downside of using multi-dimensional arrays or multiple nested arrays in this way is to lose track of what dimension represents what. -So instead let's create our own type that acts like a 2-D array and that is more convenient to use. Here it is, short and sweet: +Instead, you can create your own type that acts like a 2-D array which is more convenient to use: ```swift public struct Array2D { public let columns: Int public let rows: Int - private var array: [T] - + fileprivate var array: [T] + public init(columns: Int, rows: Int, initialValue: T) { self.columns = columns self.rows = rows array = .init(repeating: initialValue, count: rows*columns) } - + public subscript(column: Int, row: Int) -> T { get { + precondition(column < columns, "Column \(column) Index is out of range. Array(columns: \(columns), rows:\(rows))") + precondition(row < rows, "Row \(row) Index is out of range. Array(columns: \(columns), rows:\(rows))") return array[row*columns + column] } set { + precondition(column < columns, "Column \(column) Index is out of range. Array(columns: \(columns), rows:\(rows))") + precondition(row < rows, "Row \(row) Index is out of range. Array(columns: \(columns), rows:\(rows))") array[row*columns + column] = newValue } } @@ -88,26 +92,24 @@ public struct Array2D { `Array2D` is a generic type, so it can hold any kind of object, not just numbers. -To create an instance of `Array2D` you'd write: +To create an instance of `Array2D`, you can write: ```swift var cookies = Array2D(columns: 9, rows: 7, initialValue: 0) ``` -Thanks to the `subscript` function, you can do the following to retrieve an object from the array: +By using the `subscript` function, you can retrieve an object from the array: ```swift let myCookie = cookies[column, row] ``` -Or change it: +Or, you can change it: ```swift cookies[column, row] = newCookie ``` -Internally, `Array2D` uses a single one-dimensional array to store the data. The index of an object in that array is given by `(row x numberOfColumns) + column`. But as a user of `Array2D` you don't have to worry about that; you only have to think in terms of "column" and "row", and let `Array2D` figure out the details for you. That's the advantage of wrapping primitive types into a wrapper class or struct. - -And that's all there is to it. +Internally, `Array2D` uses a single one-dimensional array to store the data. The index of an object in that array is given by `(row x numberOfColumns) + column`, but as a user of `Array2D`, you only need to think in terms of "column" and "row", and the details will be done by `Array2D`. This is the advantage of wrapping primitive types into a wrapper class or struct. *Written for Swift Algorithm Club by Matthijs Hollemans* 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 8238fe5a5..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) @@ -14,9 +16,8 @@ bTree[3] bTree.remove(2) -bTree.traverseKeysInOrder { - key in - print(key) +bTree.traverseKeysInOrder { key in + print(key) } bTree.numberOfKeys diff --git a/B-Tree/BTree.playground/Sources/BTree.swift b/B-Tree/BTree.playground/Sources/BTree.swift index 52cda4e43..2ed0b2593 100644 --- a/B-Tree/BTree.playground/Sources/BTree.swift +++ b/B-Tree/BTree.playground/Sources/BTree.swift @@ -33,23 +33,23 @@ class BTreeNode { * The tree that owns the node. */ unowned var owner: BTree - + fileprivate var keys = [Key]() fileprivate var values = [Value]() var children: [BTreeNode]? - + var isLeaf: Bool { return children == nil } - + var numberOfKeys: Int { return keys.count } - + init(owner: BTree) { self.owner = owner } - + convenience init(owner: BTree, keys: [Key], values: [Value], children: [BTreeNode]? = nil) { self.init(owner: owner) @@ -62,7 +62,7 @@ class BTreeNode { // MARK: BTreeNode extesnion: Searching extension BTreeNode { - + /** * Returns the value for a given `key`, returns nil if the `key` is not found. * @@ -71,11 +71,11 @@ extension BTreeNode { */ func value(for key: Key) -> Value? { var index = keys.startIndex - + while (index + 1) < keys.endIndex && keys[index] < key { index = (index + 1) } - + if key == keys[index] { return values[index] } else if key < keys[index] { @@ -86,10 +86,10 @@ extension BTreeNode { } } -// MARK: BTreeNode extension: Travelsals +// MARK: BTreeNode extension: Traversals extension BTreeNode { - + /** * Traverses the keys in order, executes `process` for every key. * @@ -101,7 +101,7 @@ extension BTreeNode { children?[i].traverseKeysInOrder(process) process(keys[i]) } - + children?.last?.traverseKeysInOrder(process) } } @@ -109,7 +109,7 @@ extension BTreeNode { // MARK: BTreeNode extension: Insertion extension BTreeNode { - + /** * Inserts `value` for `key` to the node, or to one if its descendants. * @@ -119,16 +119,16 @@ extension BTreeNode { */ func insert(_ value: Value, for key: Key) { var index = keys.startIndex - + while index < keys.endIndex && keys[index] < key { index = (index + 1) } - + if index < keys.endIndex && keys[index] == key { values[index] = value return } - + if isLeaf { keys.insert(key, at: index) values.insert(value, at: index) @@ -140,7 +140,7 @@ extension BTreeNode { } } } - + /** * Splits `child` at `index`. * The key-value pair at `index` gets moved up to the parent node, @@ -156,7 +156,7 @@ extension BTreeNode { values.insert(child.values[middleIndex], at: index) child.keys.remove(at: middleIndex) child.values.remove(at: middleIndex) - + let rightSibling = BTreeNode( owner: owner, keys: Array(child.keys[child.keys.indices.suffix(from: middleIndex)]), @@ -164,9 +164,9 @@ extension BTreeNode { ) child.keys.removeSubrange(child.keys.indices.suffix(from: middleIndex)) child.values.removeSubrange(child.values.indices.suffix(from: middleIndex)) - + children!.insert(rightSibling, at: (index + 1)) - + if child.children != nil { rightSibling.children = Array( child.children![child.children!.indices.suffix(from: (middleIndex + 1))] @@ -198,7 +198,7 @@ extension BTreeNode { return children!.last!.inorderPredecessor } } - + /** * Removes `key` and the value associated with it from the node * or one of its descendants. @@ -208,11 +208,11 @@ extension BTreeNode { */ func remove(_ key: Key) { var index = keys.startIndex - + while (index + 1) < keys.endIndex && keys[index] < key { index = (index + 1) } - + if keys[index] == key { if isLeaf { keys.remove(at: index) @@ -229,7 +229,7 @@ extension BTreeNode { } } else if key < keys[index] { // We should go to left child... - + if let leftChild = children?[index] { leftChild.remove(key) if leftChild.numberOfKeys < owner.order { @@ -240,7 +240,7 @@ extension BTreeNode { } } else { // We should go to right child... - + if let rightChild = children?[(index + 1)] { rightChild.remove(key) if rightChild.numberOfKeys < owner.order { @@ -251,7 +251,7 @@ extension BTreeNode { } } } - + /** * Fixes `childWithTooFewKeys` by either moving a key to it from * one of its neighbouring nodes, or by merging. @@ -264,7 +264,7 @@ extension BTreeNode { * - index: the index of the child to be fixed in the current node */ private func fix(childWithTooFewKeys child: BTreeNode, atIndex index: Int) { - + if (index - 1) >= 0 && children![(index - 1)].numberOfKeys > owner.order { move(keyAtIndex: (index - 1), to: child, from: children![(index - 1)], at: .left) } else if (index + 1) < children!.count && children![(index + 1)].numberOfKeys > owner.order { @@ -275,7 +275,7 @@ extension BTreeNode { merge(child: child, atIndex: index, to: .right) } } - + /** * Moves the key at the specified `index` from `node` to * the `targetNode` at `position` @@ -301,7 +301,7 @@ extension BTreeNode { at: targetNode.children!.startIndex) node.children!.removeLast() } - + case .right: targetNode.keys.insert(keys[index], at: targetNode.keys.endIndex) targetNode.values.insert(values[index], at: targetNode.values.endIndex) @@ -316,7 +316,7 @@ extension BTreeNode { } } } - + /** * Merges `child` at `position` to the node at the `position`. * @@ -329,33 +329,33 @@ extension BTreeNode { switch position { case .left: // We can merge to the left sibling - + children![(index - 1)].keys = children![(index - 1)].keys + [keys[(index - 1)]] + child.keys - + children![(index - 1)].values = children![(index - 1)].values + [values[(index - 1)]] + child.values - + keys.remove(at: (index - 1)) values.remove(at: (index - 1)) - + if !child.isLeaf { children![(index - 1)].children = children![(index - 1)].children! + child.children! } - + case .right: // We should merge to the right sibling - + children![(index + 1)].keys = child.keys + [keys[index]] + children![(index + 1)].keys - + children![(index + 1)].values = child.values + [values[index]] + children![(index + 1)].values - + keys.remove(at: index) values.remove(at: index) - + if !child.isLeaf { children![(index + 1)].children = child.children! + children![(index + 1)].children! @@ -374,18 +374,18 @@ extension BTreeNode { */ var inorderArrayFromKeys: [Key] { var array = [Key] () - + for i in 0.. { * except the root node which is allowed to contain less keys than the value of order. */ public let order: Int - + /** * The root node of the tree */ var rootNode: BTreeNode! - + fileprivate(set) public var numberOfKeys = 0 - + /** * Designated initializer for the tree * @@ -443,7 +443,7 @@ public class BTree { } } -// MARK: BTree extension: Travelsals +// MARK: BTree extension: Traversals extension BTree { /** @@ -484,7 +484,7 @@ extension BTree { guard rootNode.numberOfKeys > 0 else { return nil } - + return rootNode.value(for: key) } } @@ -501,12 +501,12 @@ extension BTree { */ public func insert(_ value: Value, for key: Key) { rootNode.insert(value, for: key) - + if rootNode.numberOfKeys > order * 2 { splitRoot() } } - + /** * Splits the root node of the tree. * @@ -515,7 +515,7 @@ extension BTree { */ private func splitRoot() { let middleIndexOfOldRoot = rootNode.numberOfKeys / 2 - + let newRoot = BTreeNode( owner: self, keys: [rootNode.keys[middleIndexOfOldRoot]], @@ -524,7 +524,7 @@ extension BTree { ) rootNode.keys.remove(at: middleIndexOfOldRoot) rootNode.values.remove(at: middleIndexOfOldRoot) - + let newRightChild = BTreeNode( owner: self, keys: Array(rootNode.keys[rootNode.keys.indices.suffix(from: middleIndexOfOldRoot)]), @@ -532,7 +532,7 @@ extension BTree { ) rootNode.keys.removeSubrange(rootNode.keys.indices.suffix(from: middleIndexOfOldRoot)) rootNode.values.removeSubrange(rootNode.values.indices.suffix(from: middleIndexOfOldRoot)) - + if rootNode.children != nil { newRightChild.children = Array( rootNode.children![rootNode.children!.indices.suffix(from: (middleIndexOfOldRoot + 1))] @@ -541,7 +541,7 @@ extension BTree { rootNode.children!.indices.suffix(from: (middleIndexOfOldRoot + 1)) ) } - + newRoot.children!.append(newRightChild) rootNode = newRoot } @@ -560,9 +560,9 @@ extension BTree { guard rootNode.numberOfKeys > 0 else { return } - + rootNode.remove(key) - + if rootNode.numberOfKeys == 0 && !rootNode.isLeaf { rootNode = rootNode.children!.first! } 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/BTree.swift b/B-Tree/BTree.swift index 52cda4e43..2693eb308 100644 --- a/B-Tree/BTree.swift +++ b/B-Tree/BTree.swift @@ -33,23 +33,23 @@ class BTreeNode { * The tree that owns the node. */ unowned var owner: BTree - + fileprivate var keys = [Key]() fileprivate var values = [Value]() var children: [BTreeNode]? - + var isLeaf: Bool { return children == nil } - + var numberOfKeys: Int { return keys.count } - + init(owner: BTree) { self.owner = owner } - + convenience init(owner: BTree, keys: [Key], values: [Value], children: [BTreeNode]? = nil) { self.init(owner: owner) @@ -62,7 +62,7 @@ class BTreeNode { // MARK: BTreeNode extesnion: Searching extension BTreeNode { - + /** * Returns the value for a given `key`, returns nil if the `key` is not found. * @@ -71,11 +71,11 @@ extension BTreeNode { */ func value(for key: Key) -> Value? { var index = keys.startIndex - + while (index + 1) < keys.endIndex && keys[index] < key { index = (index + 1) } - + if key == keys[index] { return values[index] } else if key < keys[index] { @@ -89,7 +89,7 @@ extension BTreeNode { // MARK: BTreeNode extension: Travelsals extension BTreeNode { - + /** * Traverses the keys in order, executes `process` for every key. * @@ -101,7 +101,7 @@ extension BTreeNode { children?[i].traverseKeysInOrder(process) process(keys[i]) } - + children?.last?.traverseKeysInOrder(process) } } @@ -109,7 +109,7 @@ extension BTreeNode { // MARK: BTreeNode extension: Insertion extension BTreeNode { - + /** * Inserts `value` for `key` to the node, or to one if its descendants. * @@ -119,16 +119,16 @@ extension BTreeNode { */ func insert(_ value: Value, for key: Key) { var index = keys.startIndex - + while index < keys.endIndex && keys[index] < key { index = (index + 1) } - + if index < keys.endIndex && keys[index] == key { values[index] = value return } - + if isLeaf { keys.insert(key, at: index) values.insert(value, at: index) @@ -140,7 +140,7 @@ extension BTreeNode { } } } - + /** * Splits `child` at `index`. * The key-value pair at `index` gets moved up to the parent node, @@ -156,7 +156,7 @@ extension BTreeNode { values.insert(child.values[middleIndex], at: index) child.keys.remove(at: middleIndex) child.values.remove(at: middleIndex) - + let rightSibling = BTreeNode( owner: owner, keys: Array(child.keys[child.keys.indices.suffix(from: middleIndex)]), @@ -164,9 +164,9 @@ extension BTreeNode { ) child.keys.removeSubrange(child.keys.indices.suffix(from: middleIndex)) child.values.removeSubrange(child.values.indices.suffix(from: middleIndex)) - + children!.insert(rightSibling, at: (index + 1)) - + if child.children != nil { rightSibling.children = Array( child.children![child.children!.indices.suffix(from: (middleIndex + 1))] @@ -198,7 +198,7 @@ extension BTreeNode { return children!.last!.inorderPredecessor } } - + /** * Removes `key` and the value associated with it from the node * or one of its descendants. @@ -208,11 +208,11 @@ extension BTreeNode { */ func remove(_ key: Key) { var index = keys.startIndex - + while (index + 1) < keys.endIndex && keys[index] < key { index = (index + 1) } - + if keys[index] == key { if isLeaf { keys.remove(at: index) @@ -229,7 +229,7 @@ extension BTreeNode { } } else if key < keys[index] { // We should go to left child... - + if let leftChild = children?[index] { leftChild.remove(key) if leftChild.numberOfKeys < owner.order { @@ -240,7 +240,7 @@ extension BTreeNode { } } else { // We should go to right child... - + if let rightChild = children?[(index + 1)] { rightChild.remove(key) if rightChild.numberOfKeys < owner.order { @@ -251,7 +251,7 @@ extension BTreeNode { } } } - + /** * Fixes `childWithTooFewKeys` by either moving a key to it from * one of its neighbouring nodes, or by merging. @@ -264,7 +264,7 @@ extension BTreeNode { * - index: the index of the child to be fixed in the current node */ private func fix(childWithTooFewKeys child: BTreeNode, atIndex index: Int) { - + if (index - 1) >= 0 && children![(index - 1)].numberOfKeys > owner.order { move(keyAtIndex: (index - 1), to: child, from: children![(index - 1)], at: .left) } else if (index + 1) < children!.count && children![(index + 1)].numberOfKeys > owner.order { @@ -275,7 +275,7 @@ extension BTreeNode { merge(child: child, atIndex: index, to: .right) } } - + /** * Moves the key at the specified `index` from `node` to * the `targetNode` at `position` @@ -301,7 +301,7 @@ extension BTreeNode { at: targetNode.children!.startIndex) node.children!.removeLast() } - + case .right: targetNode.keys.insert(keys[index], at: targetNode.keys.endIndex) targetNode.values.insert(values[index], at: targetNode.values.endIndex) @@ -316,7 +316,7 @@ extension BTreeNode { } } } - + /** * Merges `child` at `position` to the node at the `position`. * @@ -329,33 +329,33 @@ extension BTreeNode { switch position { case .left: // We can merge to the left sibling - + children![(index - 1)].keys = children![(index - 1)].keys + [keys[(index - 1)]] + child.keys - + children![(index - 1)].values = children![(index - 1)].values + [values[(index - 1)]] + child.values - + keys.remove(at: (index - 1)) values.remove(at: (index - 1)) - + if !child.isLeaf { children![(index - 1)].children = children![(index - 1)].children! + child.children! } - + case .right: // We should merge to the right sibling - + children![(index + 1)].keys = child.keys + [keys[index]] + children![(index + 1)].keys - + children![(index + 1)].values = child.values + [values[index]] + children![(index + 1)].values - + keys.remove(at: index) values.remove(at: index) - + if !child.isLeaf { children![(index + 1)].children = child.children! + children![(index + 1)].children! @@ -374,18 +374,18 @@ extension BTreeNode { */ var inorderArrayFromKeys: [Key] { var array = [Key] () - + for i in 0.. { * except the root node which is allowed to contain less keys than the value of order. */ public let order: Int - + /** * The root node of the tree */ var rootNode: BTreeNode! - + fileprivate(set) public var numberOfKeys = 0 - + /** * Designated initializer for the tree * @@ -484,7 +484,7 @@ extension BTree { guard rootNode.numberOfKeys > 0 else { return nil } - + return rootNode.value(for: key) } } @@ -501,12 +501,12 @@ extension BTree { */ public func insert(_ value: Value, for key: Key) { rootNode.insert(value, for: key) - + if rootNode.numberOfKeys > order * 2 { splitRoot() } } - + /** * Splits the root node of the tree. * @@ -515,7 +515,7 @@ extension BTree { */ private func splitRoot() { let middleIndexOfOldRoot = rootNode.numberOfKeys / 2 - + let newRoot = BTreeNode( owner: self, keys: [rootNode.keys[middleIndexOfOldRoot]], @@ -524,7 +524,7 @@ extension BTree { ) rootNode.keys.remove(at: middleIndexOfOldRoot) rootNode.values.remove(at: middleIndexOfOldRoot) - + let newRightChild = BTreeNode( owner: self, keys: Array(rootNode.keys[rootNode.keys.indices.suffix(from: middleIndexOfOldRoot)]), @@ -532,7 +532,7 @@ extension BTree { ) rootNode.keys.removeSubrange(rootNode.keys.indices.suffix(from: middleIndexOfOldRoot)) rootNode.values.removeSubrange(rootNode.values.indices.suffix(from: middleIndexOfOldRoot)) - + if rootNode.children != nil { newRightChild.children = Array( rootNode.children![rootNode.children!.indices.suffix(from: (middleIndexOfOldRoot + 1))] @@ -541,7 +541,7 @@ extension BTree { rootNode.children!.indices.suffix(from: (middleIndexOfOldRoot + 1)) ) } - + newRoot.children!.append(newRightChild) rootNode = newRoot } @@ -560,9 +560,9 @@ extension BTree { guard rootNode.numberOfKeys > 0 else { return } - + rootNode.remove(key) - + if rootNode.numberOfKeys == 0 && !rootNode.isLeaf { rootNode = rootNode.children!.first! } 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) + 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] + } - 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) - } + 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 c8db17fa3..69d7ce55d 100644 --- a/B-Tree/Tests/Tests/BTreeTests.swift +++ b/B-Tree/Tests/Tests/BTreeTests.swift @@ -10,201 +10,208 @@ 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)! } - + // MARK: - Tests on empty tree - + func testOrder() { XCTAssertEqual(bTree.order, 3) } - + func testRootNode() { XCTAssertNotNil(bTree.rootNode) } - + func testNumberOfNodesOnEmptyTree() { XCTAssertEqual(bTree.numberOfKeys, 0) } - + func testInorderTraversalOfEmptyTree() { - bTree.traverseKeysInOrder { i in + bTree.traverseKeysInOrder { _ in XCTFail("Inorder travelsal fail.") } } - + func testSubscriptOnEmptyTree() { XCTAssertEqual(bTree[1], nil) } - + func testSearchEmptyTree() { XCTAssertEqual(bTree.value(for: 1), nil) } - + func testInsertToEmptyTree() { bTree.insert(1, for: 1) - + XCTAssertEqual(bTree[1]!, 1) } - + func testRemoveFromEmptyTree() { bTree.remove(1) XCTAssertEqual(bTree.description, "[]") } - + func testInorderArrayFromEmptyTree() { XCTAssertEqual(bTree.inorderArrayFromKeys, [Int]()) } - + func testDescriptionOfEmptyTree() { XCTAssertEqual(bTree.description, "[]") } - + // MARK: - Travelsal - + func testInorderTravelsal() { for i in 1...20 { bTree.insert(i, for: i) } - + var j = 1 - + bTree.traverseKeysInOrder { i in XCTAssertEqual(i, j) j += 1 } } - + // MARK: - Searching - + func testSearchForMaximum() { for i in 1...20 { bTree.insert(i, for: i) } - + XCTAssertEqual(bTree.value(for: 20)!, 20) } - + func testSearchForMinimum() { for i in 1...20 { bTree.insert(i, for: i) } - + XCTAssertEqual(bTree.value(for: 1)!, 1) } - + // MARK: - Insertion - + func testInsertion() { bTree.insertKeysUpTo(20) - + XCTAssertEqual(bTree.numberOfKeys, 20) - + for i in 1...20 { XCTAssertNotNil(bTree[i]) } - + do { try bTree.checkBalance() } catch { XCTFail("BTree is not balanced") } } - + // MARK: - Removal - + func testRemoveMaximum() { for i in 1...20 { bTree.insert(i, for: i) } - + bTree.remove(20) - + XCTAssertNil(bTree[20]) - + do { try bTree.checkBalance() } catch { XCTFail("BTree is not balanced") } } - + func testRemoveMinimum() { bTree.insertKeysUpTo(20) - + bTree.remove(1) - + XCTAssertNil(bTree[1]) - + do { try bTree.checkBalance() } catch { XCTFail("BTree is not balanced") } } - + func testRemoveSome() { bTree.insertKeysUpTo(20) - + bTree.remove(6) bTree.remove(9) - + XCTAssertNil(bTree[6]) XCTAssertNil(bTree[9]) - + do { try bTree.checkBalance() } catch { XCTFail("BTree is not balanced") } } - + func testRemoveSomeFrom2ndOrder() { bTree = BTree(order: 2)! bTree.insertKeysUpTo(20) - + bTree.remove(6) bTree.remove(9) - + XCTAssertNil(bTree[6]) XCTAssertNil(bTree[9]) - + do { try bTree.checkBalance() } catch { XCTFail("BTree is not balanced") } } - + func testRemoveAll() { bTree.insertKeysUpTo(20) - + XCTAssertEqual(bTree.numberOfKeys, 20) - + for i in (1...20).reversed() { bTree.remove(i) } - + do { try bTree.checkBalance() } catch { XCTFail("BTree is not balanced") } - + XCTAssertEqual(bTree.numberOfKeys, 0) } - + // MARK: - InorderArray - + func testInorderArray() { bTree.insertKeysUpTo(20) - + let returnedArray = bTree.inorderArrayFromKeys let targetArray = Array(1...20) - + XCTAssertEqual(returnedArray, targetArray) } } @@ -221,7 +228,7 @@ extension BTreeNode { } else if !root && numberOfKeys < owner.order { throw BTreeError.tooFewNodes } - + if !isLeaf { for child in children! { try child.checkBalance(isRoot: false) @@ -234,14 +241,14 @@ extension BTree where Key: SignedInteger, Value: SignedInteger { func insertKeysUpTo(_ to: Int) { var k: Key = 1 var v: Value = 1 - + for _ in 1...to { insert(v, for: k) k = k + 1 v = v + 1 } } - + func checkBalance() throws { try rootNode.checkBalance(isRoot: true) } diff --git a/Big-O Notation.markdown b/Big-O Notation.markdown index 8cb988168..c3f020df2 100644 --- a/Big-O Notation.markdown +++ b/Big-O Notation.markdown @@ -15,10 +15,136 @@ 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. -Note that Big-O notation is an estimate and is only really useful for large values of **n**. For example, the worst-case running time for the [insertion sort](Insertion Sort/) algorithm is **O(n^2)**. In theory that is worse than the running time for [merge sort](Merge Sort/), which is **O(n log n)**. But for small amounts of data, insertion sort is actually faster, especially if the array is partially sorted already! +Note that Big-O notation is an estimate and is only really useful for large values of **n**. For example, the worst-case running time for the [insertion sort](Insertion%20Sort/) algorithm is **O(n^2)**. In theory that is worse than the running time for [merge sort](Merge%20Sort/), which is **O(n log n)**. But for small amounts of data, insertion sort is actually faster, especially if the array is partially sorted already! If you find this confusing, don't let this Big-O stuff bother you too much. It's mostly useful when comparing two algorithms to figure out which one is better. But in the end you still want to test in practice which one really is the best. And if the amount of data is relatively small, then even a slow algorithm will be fast enough for practical use. diff --git a/Binary Search Tree/README.markdown b/Binary Search Tree/README.markdown index bd2a00c9e..57d1f4bff 100644 --- a/Binary Search Tree/README.markdown +++ b/Binary Search Tree/README.markdown @@ -1,8 +1,11 @@ # Binary Search Tree (BST) -A binary search tree is a special kind of [binary tree](../Binary Tree/) (a tree in which each node has at most two children) that performs insertions and deletions such that the tree is always sorted. +> This topic has been tutorialized [here](https://www.raywenderlich.com/139821/swift-algorithm-club-swift-binary-search-tree-data-structure) -If you don't know what a tree is or what it is for, then [read this first](../Tree/). + +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/). ## "Always sorted" property @@ -12,56 +15,56 @@ Here is an example of a valid binary search tree: Notice how each left child is smaller than its parent node, and each right child is greater than its parent node. This is the key feature of a binary search tree. -For example, `2` is smaller than `7` so it goes on the left; `5` is greater than `2` so it goes on the right. +For example, `2` is smaller than `7`, so it goes on the left; `5` is greater than `2`, so it goes on the right. ## Inserting new nodes When performing an insertion, we first compare the new value to the root node. If the new value is smaller, we take the *left* branch; if greater, we take the *right* branch. We work our way down the tree this way until we find an empty spot where we can insert the new value. -Say we want to insert the new value `9`: +Suppose we want to insert the new value `9`: - We start at the root of the tree (the node with the value `7`) and compare it to the new value `9`. - `9 > 7`, so we go down the right branch and repeat the same procedure but this time on node `10`. - Because `9 < 10`, we go down the left branch. -- We've now arrived at a point where there are no more values to compare with. A new node for `9` is inserted at that location. +- We now arrived at a point where there are no more values to compare with. A new node for `9` is inserted at that location. -The tree now looks like this: +Here is the tree after inserting the new value `9`: ![After adding 9](Images/Tree2.png) -There is always only one possible place where the new element can be inserted in the tree. Finding this place is usually pretty quick. It takes **O(h)** time, where **h** is the height of the tree. +There is only one possible place where the new element can be inserted in the tree. Finding this place is usually quick. It takes **O(h)** time, where **h** is the height of the tree. > **Note:** The *height* of a node is the number of steps it takes to go from that node to its lowest leaf. The height of the entire tree is the distance from the root to the lowest leaf. Many of the operations on a binary search tree are expressed in terms of the tree's height. -By following this simple rule -- smaller values on the left, larger values on the right -- we keep the tree sorted in a way such that whenever we query it, we can quickly check if a value is in the tree. +By following this simple rule -- smaller values on the left, larger values on the right -- we keep the tree sorted, so whenever we query it, we can check if a value is in the tree. ## Searching the tree -To find a value in the tree, we essentially perform the same steps as with insertion: +To find a value in the tree, we perform the same steps as with insertion: - If the value is less than the current node, then take the left branch. - If the value is greater than the current node, take the right branch. -- And if the value is equal to the current node, we've found it! +- If the value is equal to the current node, we've found it! -Like most tree operations, this is performed recursively until either we find what we're looking for, or run out of nodes to look at. +Like most tree operations, this is performed recursively until either we find what we are looking for or run out of nodes to look at. -If we were looking for the value `5` in the example, it would go as follows: +Here is an example for searching the value `5`: ![Searching the tree](Images/Searching.png) -Thanks to the structure of the tree, searching is really fast. It runs in **O(h)** time. If you have a well-balanced tree with a million nodes, it only takes about 20 steps to find anything in this tree. (The idea is very similar to [binary search](../Binary Search) in an array.) +Searching is fast using the structure of the tree. It runs in **O(h)** time. If you have a well-balanced tree with a million nodes, it only takes about 20 steps to find anything in this tree. (The idea is very similar to [binary search](../Binary%20Search) in an array.) ## Traversing the tree -Sometimes you don't want to look at just a single node, but at all of them. +Sometimes you need to look at all nodes rather than only one. There are three ways to traverse a binary tree: -1. *In-order* (or *depth-first*): first look at the left child of a node, then at the node itself, and finally at its right child. -2. *Pre-order*: first look at a node, then its left and right children. +1. *In-order* (or *depth-first*): first look at the left child of a node then at the node itself and finally at its right child. +2. *Pre-order*: first look at a node then its left and right children. 3. *Post-order*: first look at the left and right children and process the node itself last. -Once again, this happens recursively. +Traversing the tree also happens recursively. If you traverse a binary search tree in-order, it looks at all the nodes as if they were sorted from low to high. For the example tree, it would print `1, 2, 5, 7, 9, 10`: @@ -69,7 +72,7 @@ If you traverse a binary search tree in-order, it looks at all the nodes as if t ## Deleting nodes -Removing nodes is also easy. After removing a node, we replace the node with either its biggest child on the left or its smallest child on the right. That way the tree is still sorted after the removal. In following example, 10 is removed and replaced with either 9 (Figure 2), or 11 (Figure 3). +Removing nodes is easy. After removing a node, we replace the node with either its biggest child on the left or its smallest child on the right. That way the tree is still sorted after the removal. In the following example, 10 is removed and replaced with either 9 (Figure 2) or 11 (Figure 3). ![Deleting a node with two children](Images/DeleteTwoChildren.png) @@ -80,9 +83,9 @@ Note the replacement needs to happen when the node has at least one child. If it ## The code (solution 1) -So much for the theory. Let's see how we can implement a binary search tree in Swift. There are different approaches you can take. First, I'll show you how to make a class-based version but we'll also look at how to make one using enums. +So much for the theory. Let's see how we can implement a binary search tree in Swift. There are different approaches you can take. First, I will show you how to make a class-based version, but we will also look at how to make one using enums. -Here's a first stab at a `BinarySearchTree` class: +Here is an example for a `BinarySearchTree` class: ```swift public class BinarySearchTree { @@ -133,21 +136,21 @@ public class BinarySearchTree { } ``` -This class describes just a single node, not the entire tree. It's a generic type, so the node can store any kind of data. It also has references to its `left` and `right` child nodes and a `parent` node. +This class describes just a single node not the entire tree. It is a generic type, so the node can store any kind of data. It also has references to its `left` and `right` child nodes and a `parent` node. -Here's how you'd use it: +Here is how you can use it: ```swift let tree = BinarySearchTree(value: 7) ``` -The `count` property determines how many nodes are in the subtree described by this node. This doesn't just count the node's immediate children but also their children and their children's children, and so on. If this particular object is the root node, then it counts how many nodes are in the entire tree. Initially, `count = 0`. +The `count` property determines how many nodes are in the subtree described by this node. This does not just count the node's immediate children but also their children and their children's children, and so on. If this particular object is the root node, then it counts how many nodes are in the entire tree. Initially, `count = 0`. -> **Note:** Because `left`, `right`, and `parent` are optionals, we can make good use of Swift's optional chaining (`?`) and nil-coalescing operators (`??`). You could also write this sort of thing with `if let` but that is less concise. +> **Note:** Because `left`, `right`, and `parent` are optional, we can make good use of Swift's optional chaining (`?`) and nil-coalescing operators (`??`). You could also write this sort of thing with `if let`, but that is less concise. ### Inserting nodes -A tree node by itself is pretty useless, so here is how you would add new nodes to the tree: +A tree node by itself is useless, so here is how you would add new nodes to the tree: ```swift public func insert(value: T) { @@ -173,9 +176,9 @@ Like so many other tree operations, insertion is easiest to implement with recur If there is no more left or right child to look at, we create a `BinarySearchTree` object for the new node and connect it to the tree by setting its `parent` property. -> **Note:** Because the whole point of a binary search tree is to have smaller nodes on the left and larger ones on the right, you should always insert elements at the root, to make to sure this remains a valid binary tree! +> **Note:** Because the whole point of a binary search tree is to have smaller nodes on the left and larger ones on the right, you should always insert elements at the root to make sure this remains a valid binary tree! -To build the complete tree from the example you'd do: +To build the complete tree from the example you can do: ```swift let tree = BinarySearchTree(value: 7) @@ -186,7 +189,7 @@ tree.insert(9) tree.insert(1) ``` -> **Note:** For reasons that will become clear later, you should insert the numbers in a somewhat random order. If you insert them in sorted order, the tree won't have the right shape. +> **Note:** For reasons that will become clear later, you should insert the numbers in a random order. If you insert them in a sorted order, the tree will not have the right shape. For convenience, let's add an init method that calls `insert()` for all the elements in an array: @@ -195,7 +198,7 @@ For convenience, let's add an init method that calls `insert()` for all the elem precondition(array.count > 0) self.init(value: array.first!) for v in array.dropFirst() { - insert(v, parent: self) + insert(value: v) } } ``` @@ -210,7 +213,7 @@ The first value in the array becomes the root of the tree. ### Debug output -When working with somewhat complicated data structures such as this, it's useful to have human-readable debug output. +When working with complicated data structures, it is useful to have human-readable debug output. ```swift extension BinarySearchTree: CustomStringConvertible { @@ -236,11 +239,11 @@ The root node is in the middle. With some imagination, you should see that this ![The tree](Images/Tree2.png) -By the way, you may be wondering what happens when you insert duplicate items? We always insert those in the right branch. Try it out! +You may be wondering what happens when you insert duplicate items? We always insert those in the right branch. Try it out! ### Searching -What do we do now that we have some values in our tree? Search for them, of course! Being able to find items quickly is the entire purpose of a binary search tree. :-) +What do we do now that we have some values in our tree? Search for them, of course! To find items quickly is the main purpose of a binary search tree. :-) Here is the implementation of `search()`: @@ -258,16 +261,16 @@ Here is the implementation of `search()`: I hope the logic is clear: this starts at the current node (usually the root) and compares the values. If the search value is less than the node's value, we continue searching in the left branch; if the search value is greater, we dive into the right branch. -Of course, if there are no more nodes to look at -- when `left` or `right` is nil -- then we return `nil` to indicate the search value is not in the tree. +If there are no more nodes to look at -- when `left` or `right` is nil -- then we return `nil` to indicate the search value is not in the tree. -> **Note:** In Swift that's very conveniently done with optional chaining; when you write `left?.search(value)` it automatically returns nil if `left` is nil. There's no need to explicitly check for this with an `if` statement. +> **Note:** In Swift, that is conveniently done with optional chaining; when you write `left?.search(value)` it automatically returns nil if `left` is nil. There is no need to explicitly check for this with an `if` statement. -Searching is a recursive process but you can also implement it with a simple loop instead: +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 { @@ -280,9 +283,9 @@ Searching is a recursive process but you can also implement it with a simple loo } ``` -Verify for yourself that you understand that these two implementations are equivalent. Personally, I prefer to use iterative code over recursive code but your opinion may differ. ;-) +Verify that you understand these two implementations are equivalent. Personally, I prefer to use iterative code over recursive code, but your opinion may differ. ;-) -Here's how to test searching: +Here is how to test searching: ```swift tree.search(5) @@ -291,9 +294,9 @@ tree.search(7) tree.search(6) // nil ``` -The first three lines all return the corresponding `BinaryTreeNode` object. The last line returns `nil` because there is no node with value `6`. +The first three lines return the corresponding `BinaryTreeNode` object. The last line returns `nil` because there is no node with value `6`. -> **Note:** If there are duplicate items in the tree, `search()` always returns the "highest" node. That makes sense, because we start searching from the root downwards. +> **Note:** If there are duplicate items in the tree, `search()` returns the "highest" node. That makes sense, because we start searching from the root downwards. ### Traversal @@ -319,9 +322,9 @@ Remember there are 3 different ways to look at all nodes in the tree? Here they } ``` -They all do pretty much the same thing but in different orders. Notice once again that all the work is done recursively. Thanks to Swift's optional chaining, the calls to `traverseInOrder()` etc are ignored when there is no left or right child. +They all work the same but in different orders. Notice that all the work is done recursively. The Swift's optional chaining makes it clear that the calls to `traverseInOrder()` etc are ignored when there is no left or right child. -To print out all the values from the tree sorted from low to high you can write: +To print out all the values of the tree sorted from low to high you can write: ```swift tree.traverseInOrder { value in print(value) } @@ -336,7 +339,7 @@ This prints the following: 9 10 -You can also add things like `map()` and `filter()` to the tree. For example, here's an implementation of map: +You can also add things like `map()` and `filter()` to the tree. For example, here is an implementation of map: ```swift @@ -365,14 +368,14 @@ This turns the contents of the tree back into a sorted array. Try it out in the tree.toArray() // [1, 2, 5, 7, 9, 10] ``` -As an exercise for yourself, see if you can implement filter and reduce. +As an exercise, see if you can implement filter and reduce. ### Deleting nodes -We can make the code much more readable by defining some helper functions. +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 @@ -384,14 +387,14 @@ We can make the code much more readable by defining some helper functions. } ``` -Making changes to the tree involves changing a bunch of `parent` and `left` and `right` pointers. This function helps with that. It takes the parent of the current node -- that is `self` -- and connects it to another node. Usually that other node will be one of the children of `self`. +Making changes to the tree involves changing a bunch of `parent` and `left` and `right` pointers. This function helps with this implementation. It takes the parent of the current node -- that is `self` -- and connects it to another node which will be one of the children of `self`. 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 @@ -407,7 +410,7 @@ We also need a function that returns the minimum and maximum of a node: ``` -The rest of the code is pretty self-explanatory: +The rest of the code is self-explanatory: ```swift @discardableResult public func remove() -> BinarySearchTree? { @@ -457,7 +460,7 @@ Recall that the height of a node is the distance to its lowest leaf. We can calc We look at the heights of the left and right branches and take the highest one. Again, this is a recursive procedure. Since this looks at all children of this node, performance is **O(n)**. -> **Note:** Swift's null-coalescing operator is used as shorthand to deal with `left` or `right` pointers that are nil. You could write this with `if let` but this is a lot more concise. +> **Note:** Swift's null-coalescing operator is used as shorthand to deal with `left` or `right` pointers that are nil. You could write this with `if let`, but this is more concise. Try it out: @@ -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 } @@ -479,7 +482,7 @@ You can also calculate the *depth* of a node, which is the distance to the root. } ``` -It steps upwards through the tree, following the `parent` pointers until we reach the root node (whose `parent` is nil). This takes **O(h)** time. Example: +It steps upwards through the tree, following the `parent` pointers until we reach the root node (whose `parent` is nil). This takes **O(h)** time. Here is an example: ```swift if let node9 = tree.search(9) { @@ -489,11 +492,11 @@ if let node9 = tree.search(9) { ### Predecessor and successor -The binary search tree is always "sorted" but that doesn't mean that consecutive numbers are actually next to each other in the tree. +The binary search tree is always "sorted" but that does not mean that consecutive numbers are actually next to each other in the tree. ![Example](Images/Tree2.png) -Note that you can't find the number that comes before `7` by just looking at its left child node. The left child is `2`, not `5`. Likewise for the number that comes after `7`. +Note that you cannot find the number that comes before `7` by just looking at its left child node. The left child is `2`, not `5`. Likewise for the number that comes after `7`. The `predecessor()` function returns the node whose value precedes the current value in sorted order: @@ -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 } @@ -512,11 +515,11 @@ The `predecessor()` function returns the node whose value precedes the current v } ``` -It's easy if we have a left subtree. In that case, the immediate predecessor is the maximum value in that subtree. You can verify in the above picture that `5` is indeed the maximum value in `7`'s left branch. +It is easy if we have a left subtree. In that case, the immediate predecessor is the maximum value in that subtree. You can verify in the above picture that `5` is indeed the maximum value in `7`'s left branch. -However, if there is no left subtree then we have to look at our parent nodes until we find a smaller value. So if we want to know what the predecessor is of node `9`, we keep going up until we find the first parent with a smaller value, which is `7`. +If there is no left subtree, then we have to look at our parent nodes until we find a smaller value. If we want to know what the predecessor is of node `9`, we keep going up until we find the first parent with a smaller value, which is `7`. -The code for `successor()` works the exact same way but mirrored: +The code for `successor()` works the same way but mirrored: ```swift public func successor() -> BinarySearchTree? { @@ -524,7 +527,7 @@ The code for `successor()` works the exact 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 } @@ -535,11 +538,11 @@ The code for `successor()` works the exact same way but mirrored: Both these methods run in **O(h)** time. -> **Note:** There is a cool variation called a ["threaded" binary tree](../Threaded Binary Tree) where "unused" left and right pointers are repurposed to make direct links between predecessor and successor nodes. Very clever! +> **Note:** There is a variation called a ["threaded" binary tree](../Threaded%20Binary%20Tree) where "unused" left and right pointers are repurposed to make direct links between predecessor and successor nodes. Very clever! ### Is the search tree valid? -If you were intent on sabotage you could turn the binary search tree into an invalid tree by calling `insert()` on a node that is not the root, like so: +If you were intent on sabotage you could turn the binary search tree into an invalid tree by calling `insert()` on a node that is not the root. Here is an example: ```swift if let node1 = tree.search(1) { @@ -547,14 +550,14 @@ if let node1 = tree.search(1) { } ``` -The value of the root node is `7`, so a node with value `100` is supposed to be in the tree's right branch. However, you're not inserting at the root but at a leaf node in the tree's left branch. So the new `100` node is in the wrong place in the tree! +The value of the root node is `7`, so a node with value `100`must be in the tree's right branch. However, you are not inserting at the root but at a leaf node in the tree's left branch. So the new `100` node is in the wrong place in the tree! 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 @@ -562,7 +565,7 @@ You can check whether a tree is a valid binary search tree with the following me } ``` -This verifies that the left branch does indeed contain values that are less than the current node's value, and that the right branch only contains values that are larger. +This verifies the left branch contains values that are less than the current node's value, and that the right branch only contains values that are larger. Call it as follows: @@ -577,11 +580,11 @@ if let node1 = tree.search(1) { ## The code (solution 2) -We've implemented the binary tree node as a class but you can also use an enum. +We have implemented the binary tree node as a class, but you can also use an enum. -The difference is reference semantics versus value semantics. Making a change to the class-based tree will update that same instance in memory. But the enum-based tree is immutable -- any insertions or deletions will give you an entirely new copy of the tree. Which one is best totally depends on what you want to use it for. +The difference is reference semantics versus value semantics. Making a change to the class-based tree will update that same instance in memory, but the enum-based tree is immutable -- any insertions or deletions will give you an entirely new copy of the tree. Which one is best, totally depends on what you want to use it for. -Here's how you'd make a binary search tree using an enum: +Here is how you can make a binary search tree using an enum: ```swift public enum BinarySearchTree { @@ -597,9 +600,9 @@ The enum has three cases: - `Leaf` for a leaf node that has no children. - `Node` for a node that has one or two children. This is marked `indirect` so that it can hold `BinarySearchTree` values. Without `indirect` you can't make recursive enums. -> **Note:** The nodes in this binary tree don't have a reference to their parent node. It's not a major impediment but it will make certain operations slightly more cumbersome to implement. +> **Note:** The nodes in this binary tree do not have a reference to their parent node. It is not a major impediment, but it will make certain operations more cumbersome to implement. -As usual, we'll implement most functionality recursively. We'll treat each case of the enum slightly differently. For example, this is how you could calculate the number of nodes in the tree and the height of the tree: +This implementation is recursive, and each case of the enum will be treated differently. For example, this is how you can calculate the number of nodes in the tree and the height of the tree: ```swift public var count: Int { @@ -612,8 +615,8 @@ As usual, we'll implement most functionality recursively. We'll treat each case 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) } } @@ -655,7 +658,7 @@ tree = tree.insert(9) tree = tree.insert(1) ``` -Notice that each time you insert something, you get back a completely new tree object. That's why you need to assign the result back to the `tree` variable. +Notice that for each insertion, you get back a new tree object, so you need to assign the result back to the `tree` variable. Here is the all-important search function: @@ -678,7 +681,7 @@ Here is the all-important search function: } ``` -As you can see, most of these functions have the same structure. +Most of these functions have the same structure. Try it out in a playground: @@ -688,7 +691,7 @@ tree.search(1) tree.search(11) // nil ``` -To print the tree for debug purposes you can use this method: +To print the tree for debug purposes, you can use this method: ```swift extension BinarySearchTree: CustomDebugStringConvertible { @@ -703,21 +706,21 @@ extension BinarySearchTree: CustomDebugStringConvertible { } ``` -When you do `print(tree)` it will look something like this: +When you do `print(tree)`, it will look like this: ((1 <- 2 -> 5) <- 7 -> (9 <- 10 -> .)) -The root node is in the middle; a dot means there is no child at that position. +The root node is in the middle, and a dot means there is no child at that position. ## When the tree becomes unbalanced... -A binary search tree is *balanced* when its left and right subtrees contain roughly the same number of nodes. In that case, the height of the tree is *log(n)*, where *n* is the number of nodes. That's the ideal situation. +A binary search tree is *balanced* when its left and right subtrees contain the same number of nodes. In that case, the height of the tree is *log(n)*, where *n* is the number of nodes. That is the ideal situation. -However, if one branch is significantly longer than the other, searching becomes very slow. We end up checking way more values than we'd ideally have to. In the worst case, the height of the tree can become *n*. Such a tree acts more like a [linked list](../Linked List/) than a binary search tree, with performance degrading to **O(n)**. Not good! +If one branch is significantly longer than the other, searching becomes very slow. We end up checking more values than we need. In the worst case, the height of the tree can become *n*. Such a tree acts like a [linked list](../Linked%20List/) than a binary search tree, with performance degrading to **O(n)**. Not good! -One way to make the binary search tree balanced is to insert the nodes in a totally random order. On average that should balance out the tree quite nicely. But it doesn't guarantee success, nor is it always practical. +One way to make the binary search tree balanced is to insert the nodes in a totally random order. On average that should balance out the tree well, but it not guaranteed, nor is it always practical. -The other solution is to use a *self-balancing* binary tree. This type of data structure adjusts the tree to keep it balanced after you insert or delete nodes. See [AVL tree](../AVL Tree) and [red-black tree](../Red-Black Tree) for examples. +The other solution is to use a *self-balancing* binary tree. This type of data structure adjusts the tree to keep it balanced after you insert or delete nodes. To see examples, check [AVL tree](../AVL%20Tree) and [red-black tree](../Red-Black%20Tree). ## See also 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 c65d0b5fd..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 Search/). 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.swift b/Binary Tree/BinaryTree.playground/Contents.swift index b4d65ec1f..b0bff577a 100644 --- a/Binary Tree/BinaryTree.playground/Contents.swift +++ b/Binary Tree/BinaryTree.playground/Contents.swift @@ -18,15 +18,13 @@ extension BinaryTree: CustomStringConvertible { public var description: String { switch self { case let .node(left, value, right): - return "value: \(value), left = [" + left.description + "], right = [" + right.description + "]" + return "value: \(value), left = [\(left.description)], right = [\(right.description)]" case .empty: return "" } } } - - // leaf nodes let node5 = BinaryTree.node(.empty, "5", .empty) let nodeA = BinaryTree.node(.empty, "a", .empty) @@ -50,8 +48,6 @@ let tree = BinaryTree.node(timesLeft, "+", timesRight) print(tree) tree.count // 12 - - extension BinaryTree { public func traverseInOrder(process: (T) -> Void) { if case let .node(left, value, right) = self { 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 ad5fcfd13..6146880b4 100644 --- a/Binary Tree/BinaryTree.swift +++ b/Binary Tree/BinaryTree.swift @@ -21,7 +21,7 @@ extension BinaryTree: CustomStringConvertible { public var description: String { switch self { case let .node(left, value, right): - return "value: \(value), left = [" + left.description + "], right = [" + right.description + "]" + return "value: \(value), left = [\(left.description)], right = [\(right.description)]" case .empty: return "" } @@ -29,26 +29,26 @@ extension BinaryTree: CustomStringConvertible { } extension BinaryTree { - public func traverseInOrder(@noescape process: T -> Void) { + public func traverseInOrder(process: (T) -> Void) { if case let .node(left, value, right) = self { - left.traverseInOrder(process) + left.traverseInOrder(process: process) process(value) - right.traverseInOrder(process) + right.traverseInOrder(process: process) } } - public func traversePreOrder(@noescape process: T -> Void) { + public func traversePreOrder(process: (T) -> Void) { if case let .node(left, value, right) = self { process(value) - left.traversePreOrder(process) - right.traversePreOrder(process) + left.traversePreOrder(process: process) + right.traversePreOrder(process: process) } } - public func traversePostOrder(@noescape process: T -> Void) { + public func traversePostOrder(process: (T) -> Void) { if case let .node(left, value, right) = self { - left.traversePostOrder(process) - right.traversePostOrder(process) + left.traversePostOrder(process: process) + right.traversePostOrder(process: process) process(value) } } diff --git a/Binary Tree/README.markdown b/Binary Tree/README.markdown index 52089b8e7..8ce398804 100644 --- a/Binary Tree/README.markdown +++ b/Binary Tree/README.markdown @@ -8,7 +8,7 @@ The child nodes are usually called the *left* child and the *right* child. If a Often nodes will have a link back to their parent but this is not strictly necessary. -Binary trees are often used as [binary search trees](../Binary Search Tree/). In that case, the nodes must be in a specific order (smaller values on the left, larger values on the right). But this is not a requirement for all binary trees. +Binary trees are often used as [binary search trees](../Binary%20Search%20Tree/). In that case, the nodes must be in a specific order (smaller values on the left, larger values on the right). But this is not a requirement for all binary trees. For example, here is a binary tree that represents a sequence of arithmetical operations, `(5 * (a - 10)) + (-4 * (3 / b))`: @@ -58,8 +58,7 @@ extension BinaryTree: CustomStringConvertible { public var description: String { switch self { case let .node(left, value, right): - return "value: \(value), left = [" + left.description + "], right = [" - + right.description + "]" + return "value: \(value), left = [\(left.description)], right = [\(right.description)]" case .empty: return "" } diff --git a/Bit Set/BitSet.playground/Contents.swift b/Bit Set/BitSet.playground/Contents.swift index 77d371420..70d5ca5e8 100644 --- a/Bit Set/BitSet.playground/Contents.swift +++ b/Bit Set/BitSet.playground/Contents.swift @@ -29,9 +29,6 @@ print(bits) print("") - - - // Bitwise operations var a = BitSet(size: 4) @@ -69,9 +66,6 @@ print(~c) // 0101110000000000000000000000000000000000000000000000000000000000 (~b).cardinality // 5 (~c).cardinality // 4 - - - var z = BitSet(size: 66) z.all0() // true z.all1() // false @@ -93,8 +87,25 @@ z.all1() // true z[65] = false 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 d5e7d2dc6..8ed808a06 100644 --- a/Bloom Filter/BloomFilter.playground/Contents.swift +++ b/Bloom Filter/BloomFilter.playground/Contents.swift @@ -10,7 +10,7 @@ public class BloomFilter { } private func computeHashes(_ value: T) -> [Int] { - return hashFunctions.map() { hashFunc in abs(hashFunc(value) % array.count) } + return hashFunctions.map { hashFunc in abs(hashFunc(value) % array.count) } } public func insert(_ element: T) { @@ -29,7 +29,7 @@ public class BloomFilter { let hashValues = computeHashes(value) // Map hashes to indices in the Bloom Filter - let results = hashValues.map() { hashValue in array[hashValue] } + let results = hashValues.map { hashValue in array[hashValue] } // All values must be 'true' for the query to return true @@ -47,13 +47,11 @@ public class BloomFilter { } } - - /* Two hash functions, adapted from http://www.cse.yorku.ca/~oz/hash.html */ 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) @@ -61,14 +59,12 @@ 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) } - - /* A simple test */ let bloom = BloomFilter(size: 17, hashFunctions: [djb2, sdbm]) @@ -83,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/BloomFilter.swift b/Bloom Filter/BloomFilter.swift index 03b5333f1..56c3be328 100644 --- a/Bloom Filter/BloomFilter.swift +++ b/Bloom Filter/BloomFilter.swift @@ -8,7 +8,7 @@ public class BloomFilter { } private func computeHashes(_ value: T) -> [Int] { - return hashFunctions.map() { hashFunc in abs(hashFunc(value) % array.count) } + return hashFunctions.map { hashFunc in abs(hashFunc(value) % array.count) } } public func insert(_ element: T) { @@ -27,7 +27,7 @@ public class BloomFilter { let hashValues = computeHashes(value) // Map hashes to indices in the Bloom Filter - let results = hashValues.map() { hashValue in array[hashValue] } + let results = hashValues.map { hashValue in array[hashValue] } // All values must be 'true' for the query to return true diff --git a/Bloom Filter/README.markdown b/Bloom Filter/README.markdown index a85d71be4..5f2ede069 100644 --- a/Bloom Filter/README.markdown +++ b/Bloom Filter/README.markdown @@ -18,7 +18,7 @@ An advantage of the Bloom Filter over a hash table is that the former maintains ## Inserting objects into the set -A Bloom Filter is essentially a fixed-length [bit vector](../Bit Set/), an array of bits. When we insert objects, we set some of these bits to `1`, and when we query for objects we check if certain bits are `0` or `1`. Both operations use hash functions. +A Bloom Filter is essentially a fixed-length [bit vector](../Bit%20Set/), an array of bits. When we insert objects, we set some of these bits to `1`, and when we query for objects we check if certain bits are `0` or `1`. Both operations use hash functions. To insert an element in the filter, the element is hashed with several different hash functions. Each hash function returns a value that we map to an index in the array. We then set the bits at these indices to `1` or true. @@ -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 82bf4fb8f..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,16 +16,20 @@ 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) } - 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 @@ Bool { return m1.priority < m2.priority } - - let queue = BoundedPriorityQueue(maxElements: 5) queue.count @@ -48,8 +46,6 @@ print(queue) // At this point, the queue is: // - - // Try to insert an item with a really low priority. This should not get added. queue.enqueue(Message(name: "very", priority: -1)) queue.count // 5 @@ -70,8 +66,6 @@ queue.count queue.peek() print(queue) - - // Test dequeuing queue.dequeue() queue.count diff --git a/Bounded Priority Queue/BoundedPriorityQueue.playground/Sources/BoundedPriorityQueue.swift b/Bounded Priority Queue/BoundedPriorityQueue.playground/Sources/BoundedPriorityQueue.swift index fc399811d..b94863afc 100644 --- a/Bounded Priority Queue/BoundedPriorityQueue.playground/Sources/BoundedPriorityQueue.swift +++ b/Bounded Priority Queue/BoundedPriorityQueue.playground/Sources/BoundedPriorityQueue.swift @@ -9,7 +9,7 @@ public class LinkedListNode { } public class BoundedPriorityQueue { - private typealias Node = LinkedListNode + fileprivate typealias Node = LinkedListNode private(set) public var count = 0 fileprivate var head: Node? diff --git a/Bounded Priority Queue/BoundedPriorityQueue.playground/contents.xcplayground b/Bounded Priority Queue/BoundedPriorityQueue.playground/contents.xcplayground index 06828af92..69d154d1e 100644 --- a/Bounded Priority Queue/BoundedPriorityQueue.playground/contents.xcplayground +++ b/Bounded Priority Queue/BoundedPriorityQueue.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ 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/BoundedPriorityQueue.swift b/Bounded Priority Queue/BoundedPriorityQueue.swift index fbd8d5450..d882a7698 100644 --- a/Bounded Priority Queue/BoundedPriorityQueue.swift +++ b/Bounded Priority Queue/BoundedPriorityQueue.swift @@ -1,4 +1,4 @@ -public class LinkedListNode { +open class LinkedListNode { var value: T var next: LinkedListNode? var previous: LinkedListNode? @@ -8,27 +8,27 @@ public class LinkedListNode { } } -public class BoundedPriorityQueue { - private typealias Node = LinkedListNode +open class BoundedPriorityQueue { + fileprivate typealias Node = LinkedListNode - private(set) public var count = 0 - private var head: Node? - private var tail: Node? - private var maxElements: Int + fileprivate(set) open var count = 0 + fileprivate var head: Node? + fileprivate var tail: Node? + fileprivate var maxElements: Int public init(maxElements: Int) { self.maxElements = maxElements } - public var isEmpty: Bool { + open var isEmpty: Bool { return count == 0 } - public func peek() -> T? { + open func peek() -> T? { return head?.value } - public func enqueue(value: T) { + open func enqueue(_ value: T) { if let node = insert(value, after: findInsertionPoint(value)) { // If the newly inserted node is the last one in the list, then update // the tail pointer. @@ -44,7 +44,7 @@ public class BoundedPriorityQueue { } } - private func insert(value: T, after: Node?) -> Node? { + fileprivate func insert(_ value: T, after: Node?) -> Node? { if let previous = after { // If the queue is full and we have to insert at the end of the list, @@ -78,18 +78,18 @@ public class BoundedPriorityQueue { /* Find the node after which to insert the new value. If this returns nil, the new value should be inserted at the head of the list. */ - private func findInsertionPoint(value: T) -> Node? { + fileprivate func findInsertionPoint(_ value: T) -> Node? { var node = head var prev: Node? = nil - while let current = node where value < current.value { + while let current = node, value < current.value { prev = node node = current.next } return prev } - private func removeLeastImportantElement() { + fileprivate func removeLeastImportantElement() { if let last = tail { tail = last.previous tail?.next = nil @@ -101,7 +101,7 @@ public class BoundedPriorityQueue { // this is much slower on large lists. } - public func dequeue() -> T? { + open func dequeue() -> T? { if let first = head { count -= 1 if count == 0 { diff --git a/Bounded Priority Queue/README.markdown b/Bounded Priority Queue/README.markdown index 8d47adefb..8cbaa85b2 100644 --- a/Bounded Priority Queue/README.markdown +++ b/Bounded Priority Queue/README.markdown @@ -1,6 +1,6 @@ # Bounded Priority queue -A bounded priority queue is similar to a regular [priority queue](../Priority Queue/), except that there is a fixed upper bound on the number of elements that can be stored. When a new element is added to the queue while the queue is at capacity, the element with the highest priority value is ejected from the queue. +A bounded priority queue is similar to a regular [priority queue](../Priority%20Queue/), except that there is a fixed upper bound on the number of elements that can be stored. When a new element is added to the queue while the queue is at capacity, the element with the highest priority value is ejected from the queue. ## Example @@ -26,7 +26,7 @@ Suppose that we wish to insert the element `G` with priority 0.1 into this BPQ. ## Implementation -While a [heap](../Heap/) may be a really simple implementation for a priority queue, a sorted [linked list](../Linked List/) allows for **O(k)** insertion and **O(1)** deletion, where **k** is the bounding number of elements. +While a [heap](../Heap/) may be a really simple implementation for a priority queue, a sorted [linked list](../Linked%20List/) allows for **O(k)** insertion and **O(1)** deletion, where **k** is the bounding number of elements. Here's how you could implement it in Swift: diff --git a/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj b/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj index 6fd5c0b46..11f907b47 100644 --- a/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj +++ b/Bounded Priority Queue/Tests/Tests.xcodeproj/project.pbxproj @@ -82,10 +82,11 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0820; TargetAttributes = { B80004B21C83E342001FE2D7 = { CreatedOnToolsVersion = 7.2.1; + LastSwiftMigration = 1000; }; }; }; @@ -132,12 +133,49 @@ B80004AD1C83E324001FE2D7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; }; name = Debug; }; B80004AE1C83E324001FE2D7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -188,6 +226,7 @@ SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -230,6 +269,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OBJC_BRIDGING_HEADER = ""; + 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/Bounded Priority Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Bounded Priority Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 0416248f1..8bb763edd 100644 --- a/Bounded Priority Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Bounded Priority Queue/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ 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 93% rename from Boyer-Moore/README.markdown rename to Boyer-Moore-Horspool/README.markdown index e821b812e..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. @@ -24,7 +27,7 @@ animals.indexOf(pattern: "🐮") > **Note:** The index of the cow is 6, not 3 as you might expect, because the string uses more storage per character for emoji. The actual value of the `String.Index` is not so important, just that it points at the right character in the string. -The [brute-force approach](../Brute-Force String Search/) works OK, but it's not very efficient, especially on large chunks of text. As it turns out, you don't need to look at _every_ character from the source string -- you can often skip ahead multiple characters. +The [brute-force approach](../Brute-Force%20String%20Search/) works OK, but it's not very efficient, especially on large chunks of text. As it turns out, you don't need to look at _every_ character from the source string -- you can often skip ahead multiple characters. The skip-ahead algorithm is called [Boyer-Moore](https://en.wikipedia.org/wiki/Boyer–Moore_string_search_algorithm) and it has been around for a long time. It is considered the benchmark for all string search algorithms. @@ -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 0fb98e1c8..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) @@ -19,8 +20,6 @@ func breadthFirstSearch(_ graph: Graph, source: Node) -> [String] { return nodesExplored } - - let graph = Graph() let nodeA = graph.addNode("a") @@ -42,6 +41,5 @@ graph.addEdge(nodeE, neighbor: nodeH) graph.addEdge(nodeE, neighbor: nodeF) graph.addEdge(nodeF, neighbor: nodeG) - let nodesExplored = breadthFirstSearch(graph, source: nodeA) print(nodesExplored) 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 084f4611a..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 Path/) between a source node and each of the other nodes (only for unweighted graphs). -* Calculating the [minimum spanning tree](../Minimum Spanning Tree (Unweighted)/) on an unweighted graph. +* 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/Graph.swift b/Breadth-First Search/Tests/Graph.swift index 5e0837160..a8c34b3f6 100644 --- a/Breadth-First Search/Tests/Graph.swift +++ b/Breadth-First Search/Tests/Graph.swift @@ -86,7 +86,7 @@ public class Graph: CustomStringConvertible, Equatable { let duplicated = Graph() for node in nodes { - let _ = duplicated.addNode(node.label) + _ = duplicated.addNode(node.label) } for node in nodes { 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? { - + for i in self.characters.indices { var j = i var found = true - for p in pattern.characters.indices{ + for p in pattern.characters.indices { if j == self.characters.endIndex || self[j] != pattern[p] { found = false break @@ -22,8 +27,6 @@ extension String { } } - - // A few simple tests let s = "Hello, World" diff --git a/Brute-Force String Search/BruteForceStringSearch.swift b/Brute-Force String Search/BruteForceStringSearch.swift index 03a2dff13..016292304 100644 --- a/Brute-Force String Search/BruteForceStringSearch.swift +++ b/Brute-Force String Search/BruteForceStringSearch.swift @@ -6,7 +6,7 @@ extension String { for i in self.characters.indices { var j = i var found = true - for p in pattern.characters.indices{ + for p in pattern.characters.indices { if j == self.characters.endIndex || self[j] != pattern[p] { found = false break 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 e510e51b4..64408eca4 100644 --- a/Bucket Sort/BucketSort.playground/Sources/BucketSort.swift +++ b/Bucket Sort/BucketSort.playground/Sources/BucketSort.swift @@ -20,34 +20,61 @@ // // -import Foundation - ////////////////////////////////////// // MARK: Main algorithm ////////////////////////////////////// +/** + 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 + */ -public func bucketSort(elements: [T], distributor: Distributor, sorter: Sorter, buckets: inout [Bucket]) -> [T] { - for elem in elements { - distributor.distribute(element: elem, buckets: &buckets) - } - - 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 } ////////////////////////////////////// // MARK: Distributor ////////////////////////////////////// - public protocol Distributor { - func distribute(element: T, buckets: inout [Bucket]) + func distribute(_ element: T, buckets: inout [Bucket]) } /* @@ -66,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) + } } ////////////////////////////////////// @@ -83,7 +110,7 @@ public struct RangeDistributor: Distributor { ////////////////////////////////////// public protocol IntConvertible { - func toInt() -> Int + func toInt() -> Int } public protocol Sortable: IntConvertible, Comparable { @@ -94,50 +121,51 @@ 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 - } } ////////////////////////////////////// // MARK: Bucket ////////////////////////////////////// -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) +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) + } + } + + 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 ef72c67dc..4c2abd372 100644 --- a/Bucket Sort/BucketSort.swift +++ b/Bucket Sort/BucketSort.swift @@ -20,37 +20,10 @@ // // -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 ////////////////////////////////////// - /** Performs bucket sort algorithm on the given input elements. [Bucket Sort Algorithm Reference](https://en.wikipedia.org/wiki/Bucket_sort) @@ -63,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)) @@ -85,20 +58,23 @@ 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 } ////////////////////////////////////// // MARK: Distributor ////////////////////////////////////// - public protocol Distributor { - func distribute(_ element: T, buckets: inout [Bucket]) + func distribute(_ element: T, buckets: inout [Bucket]) } /* @@ -120,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 @@ -173,7 +149,7 @@ public struct InsertionSorter: Sorter { // MARK: Bucket ////////////////////////////////////// -public struct Bucket { +public struct Bucket { var elements: [T] let capacity: Int 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.swift b/Bucket Sort/Tests/Tests.swift index b50789ed0..1a06c92b5 100644 --- a/Bucket Sort/Tests/Tests.swift +++ b/Bucket Sort/Tests/Tests.swift @@ -86,7 +86,6 @@ class TestTests: XCTestCase { } } - ////////////////////////////////////// // MARK: Extensions ////////////////////////////////////// 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/Red-Black Tree/Red-Black Tree 2.playground/playground.xcworkspace/contents.xcworkspacedata b/Closest Pair/ClosestPair.playground/playground.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Red-Black Tree/Red-Black Tree 2.playground/playground.xcworkspace/contents.xcworkspacedata rename to Closest Pair/ClosestPair.playground/playground.xcworkspace/contents.xcworkspacedata 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 d6add44f5..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] @@ -16,5 +16,3 @@ while i < 1000 { i += 1 } combSort(bigArray) - - diff --git a/Comb Sort/Comb Sort.playground/Sources/Comb Sort.swift b/Comb Sort/Comb Sort.playground/Sources/Comb Sort.swift index b6e85fa2b..559fe47e8 100644 --- a/Comb Sort/Comb Sort.playground/Sources/Comb Sort.swift +++ b/Comb Sort/Comb Sort.playground/Sources/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/Comb Sort.swift b/Comb Sort/Comb Sort.swift index b6e85fa2b..bd37fed42 100644 --- a/Comb Sort/Comb Sort.swift +++ b/Comb Sort/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/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 1bcc67fd0..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] - - override func setUp() { - super.setUp() - sequence = [2, 32, 9, -1, 89, 101, 55, -10, -12, 67] - } + var sequence: [Int]! + let expectedSequence: [Int] = [-12, -10, -1, 2, 9, 32, 55, 67, 89, 101] - override func tearDown() { - super.tearDown() - } + override func setUp() { + super.setUp() + sequence = [2, 32, 9, -1, 89, 101, 55, -10, -12, 67] + } + + 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 @@ -14,8 +19,6 @@ func factorial(_ n: Int) -> Int { factorial(5) factorial(20) - - /* Calculates P(n, k), the number of permutations of n distinct symbols in groups of size k. @@ -34,8 +37,6 @@ permutations(5, 3) permutations(50, 6) permutations(9, 4) - - /* Prints out all the permutations of the given array. Original algorithm by Niklaus Wirth. @@ -48,9 +49,9 @@ func permuteWirth(_ a: [T], _ n: Int) { var a = a permuteWirth(a, n - 1) for i in 0.. Int { quickBinomialCoefficient(8, choose: 2) quickBinomialCoefficient(30, choose: 15) - - /* Supporting code because Swift doesn't have a built-in 2D array. */ struct Array2D { let columns: Int diff --git a/Combinatorics/Combinatorics.playground/timeline.xctimeline b/Combinatorics/Combinatorics.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Combinatorics/Combinatorics.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,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.. + + + + diff --git a/Convex Hull/Convex Hull.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Convex Hull/Convex Hull.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Convex Hull/Convex Hull.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + 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/AppDelegate.swift b/Convex Hull/Convex Hull/AppDelegate.swift new file mode 100644 index 000000000..9bc61ff96 --- /dev/null +++ b/Convex Hull/Convex Hull/AppDelegate.swift @@ -0,0 +1,54 @@ +// +// AppDelegate.swift +// Convex Hull +// +// Created by Jaap Wijnen on 19/02/2017. +// Copyright © 2017 Workmoose. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + + let screenBounds = UIScreen.main.bounds + + window = UIWindow(frame: screenBounds) + + let viewController = UIViewController() + viewController.view = View(frame: (window?.frame)!) + viewController.view.backgroundColor = .white + + window?.rootViewController = viewController + window?.makeKeyAndVisible() + + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + +} diff --git a/Convex Hull/Convex Hull/Assets.xcassets/AppIcon.appiconset/Contents.json b/Convex Hull/Convex Hull/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d8db8d65f --- /dev/null +++ b/Convex Hull/Convex Hull/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Convex Hull/Convex Hull/Base.lproj/LaunchScreen.storyboard b/Convex Hull/Convex Hull/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..fdf3f97d1 --- /dev/null +++ b/Convex Hull/Convex Hull/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Convex Hull/Convex Hull/Info.plist b/Convex Hull/Convex Hull/Info.plist new file mode 100644 index 000000000..390c5347e --- /dev/null +++ b/Convex Hull/Convex Hull/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Convex Hull/Convex Hull/View.swift b/Convex Hull/Convex Hull/View.swift new file mode 100644 index 000000000..e75c73ed0 --- /dev/null +++ b/Convex Hull/Convex Hull/View.swift @@ -0,0 +1,178 @@ +// +// View.swift +// Convex Hull +// +// Created by Jaap Wijnen on 19/02/2017. +// Copyright © 2017 Workmoose. All rights reserved. +// + +import UIKit + +class View: UIView { + + 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 quickHull(points: [CGPoint]) { + var pts = points + + // 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() + + // 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 p2 to p1 + var s2 = [CGPoint]() + + // 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. + + 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) + } + + 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 + var maxDist: CGFloat = -1 + var maxPoint: CGPoint = pts.first! + + 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 + + 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 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) + + 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 + + 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) + } + + 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)) + } + + // 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() + + // 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) + + let firstPoint = convexHull.first! + context!.move(to: firstPoint) + + for p in convexHull.dropFirst() { + context!.addLine(to: p) + } + context!.addLine(to: firstPoint) + + 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) + } + } +} diff --git a/Convex Hull/README.md b/Convex Hull/README.md new file mode 100644 index 000000000..3036a4138 --- /dev/null +++ b/Convex Hull/README.md @@ -0,0 +1,51 @@ +# Convex Hull + +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. + +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 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)` + +``` +findHull(S1, A, B) +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`) +4. Add `maxPoint` to the hull right after `p1`. +5. Draw a line (`line1`) from `p1` to `maxPoint`. +6. Draw a line (`line2`) from `maxPoint` to `p2`. (These lines now form a triangle) +7. All points within this triangle are of course not part of the hull and thus can be ignored. We check which points in `points` are to the right of `line1` these are grouped in an array `s1`. +8. All points that are to the right of `line2` are grouped in an array `s2`. Note that there are no points that are both to the right of `line1` and `line2` as then `maxPoint` wouldn't be the point furthest away from our initial line between `p1` and `p2`. +9. We call `findHull(_, _, _)` again on our new groups of points to find more hull points. +``` +findHull(s1, p1, maxPoint) +findHull(s2, maxPoint, p2) +``` + +This eventually leaves us with an array of points describing the convex hull. + +## See also + +[Convex Hull on Wikipedia](https://en.wikipedia.org/wiki/Convex_hull_algorithms) + +*Written for the Swift Algorithm Club by Jaap Wijnen.* 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 cbb43c868..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,15 +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) @@ -62,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 90a9ec2f3..8e935ea58 100644 --- a/Count Occurrences/README.markdown +++ b/Count Occurrences/README.markdown @@ -2,9 +2,9 @@ Goal: Count how often a certain value appears in an array. -The obvious way to do this is with a [linear search](../Linear Search/) from the beginning of the array until the end, keeping count of how often you come across the value. That is an **O(n)** algorithm. +The obvious way to do this is with a [linear search](../Linear%20Search/) from the beginning of the array until the end, keeping count of how often you come across the value. That is an **O(n)** algorithm. -However, if the array is sorted you can do it much faster, in **O(log n)** time, by using a modification of [binary search](../Binary Search/). +However, if the array is sorted you can do it much faster, in **O(log n)** time, by using a modification of [binary search](../Binary%20Search/). Let's say we have the following array: @@ -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 { @@ -36,10 +36,10 @@ 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 { @@ -50,19 +50,19 @@ 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 Search/) 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 1d84f8132..99178f85d 100644 --- a/Counting Sort/CountingSort.playground/Contents.swift +++ b/Counting Sort/CountingSort.playground/Contents.swift @@ -29,13 +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 d37bb2eff..68dcf5c0d 100644 --- a/Counting Sort/CountingSort.swift +++ b/Counting Sort/CountingSort.swift @@ -6,20 +6,14 @@ // Copyright © 2016 Ali Hafizji. All rights reserved. // -enum CountingSortError: ErrorType { - case arrayEmpty -} - -func countingSort(array: [Int]) throws -> [Int] { - guard array.count > 0 else { - throw CountingSortError.arrayEmpty - } +func countingSort(_ array: [Int])-> [Int] { + guard array.count > 0 else {return []} // Step 1 // Create an array to store the count of each element - let maxElement = array.maxElement() ?? 0 + let maxElement = array.max() ?? 0 - var countArray = [Int](count: Int(maxElement + 1), repeatedValue: 0) + var countArray = [Int](repeating: 0, count: Int(maxElement + 1)) for element in array { countArray[element] += 1 } @@ -35,8 +29,11 @@ func countingSort(array: [Int]) throws -> [Int] { // Step 3 // Place the element in the final array as per the number of elements before it - var sortedArray = [Int](count: array.count, repeatedValue: 0) - for element in array { + // 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 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 6801bb587..aaa6c1b6b 100644 --- a/Counting Sort/Tests/Tests.xcodeproj/project.pbxproj +++ b/Counting Sort/Tests/Tests.xcodeproj/project.pbxproj @@ -83,11 +83,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0820; }; }; }; @@ -96,6 +97,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -145,8 +147,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -189,8 +193,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -209,6 +215,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -220,6 +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 = 5.0; }; name = Debug; }; @@ -231,6 +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 = 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/Counting Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Counting Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 8ef8d8581..dfcf6de42 100644 --- a/Counting Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Counting Sort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ [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() let nodeA = graph.addNode("a") 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 9e1a5112d..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. @@ -40,7 +42,7 @@ func depthFirstSearch(_ graph: Graph, source: Node) -> [String] { } ``` -Where a [breadth-first search](../Breadth-First Search/) visits all immediate neighbors first, a depth-first search tries to go as deep down the tree or graph as it can. +Where a [breadth-first search](../Breadth-First%20Search/) visits all immediate neighbors first, a depth-first search tries to go as deep down the tree or graph as it can. Put this code in a playground and test it like so: @@ -71,13 +73,13 @@ print(nodesExplored) ``` This will output: `["a", "b", "d", "e", "h", "f", "g", "c"]` - + ## What is DFS good for? Depth-first search can be used to solve many problems, for example: * Finding connected components of a sparse graph -* [Topological sorting](../Topological Sort/) of nodes in a graph +* [Topological sorting](../Topological%20Sort/) of nodes in a graph * Finding bridges of a graph (see: [Bridges](https://en.wikipedia.org/wiki/Bridge_(graph_theory)#Bridge-finding_algorithm)) * And lots of others! 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/Graph.swift b/Depth-First Search/Tests/Graph.swift index ed71cab7c..a52b3c419 100644 --- a/Depth-First Search/Tests/Graph.swift +++ b/Depth-First Search/Tests/Graph.swift @@ -1,7 +1,7 @@ // MARK: - Edge -public class Edge: Equatable { - public var neighbor: Node +open class Edge: Equatable { + open var neighbor: Node public init(neighbor: Node) { self.neighbor = neighbor @@ -14,12 +14,12 @@ public func == (lhs: Edge, rhs: Edge) -> Bool { // MARK: - Node -public class Node: CustomStringConvertible, Equatable { - public var neighbors: [Edge] +open class Node: CustomStringConvertible, Equatable { + open var neighbors: [Edge] - public private(set) var label: String - public var distance: Int? - public var visited: Bool + open fileprivate(set) var label: String + open var distance: Int? + open var visited: Bool public init(label: String) { self.label = label @@ -27,19 +27,19 @@ public class Node: CustomStringConvertible, Equatable { visited = false } - public var description: String { + open var description: String { if let distance = distance { return "Node(label: \(label), distance: \(distance))" } return "Node(label: \(label), distance: infinity)" } - public var hasDistance: Bool { + open var hasDistance: Bool { return distance != nil } - public func remove(edge: Edge) { - neighbors.removeAtIndex(neighbors.indexOf { $0 === edge }!) + open func remove(_ edge: Edge) { + neighbors.remove(at: neighbors.index { $0 === edge }!) } } @@ -49,25 +49,25 @@ public func == (lhs: Node, rhs: Node) -> Bool { // MARK: - Graph -public class Graph: CustomStringConvertible, Equatable { - public private(set) var nodes: [Node] +open class Graph: CustomStringConvertible, Equatable { + open fileprivate(set) var nodes: [Node] public init() { self.nodes = [] } - public func addNode(label: String) -> Node { + open func addNode(_ label: String) -> Node { let node = Node(label: label) nodes.append(node) return node } - public func addEdge(source: Node, neighbor: Node) { + open func addEdge(_ source: Node, neighbor: Node) { let edge = Edge(neighbor: neighbor) source.neighbors.append(edge) } - public var description: String { + open var description: String { var description = "" for node in nodes { @@ -78,15 +78,15 @@ public class Graph: CustomStringConvertible, Equatable { return description } - public func findNodeWithLabel(label: String) -> Node { + open func findNodeWithLabel(_ label: String) -> Node { return nodes.filter { $0.label == label }.first! } - public func duplicate() -> Graph { + open func duplicate() -> Graph { let duplicated = Graph() for node in nodes { - duplicated.addNode(node.label) + _ = duplicated.addNode(node.label) } for node in nodes { diff --git a/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj b/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj index 9e2872f88..2b433bd96 100644 --- a/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj +++ b/Depth-First Search/Tests/Tests.xcodeproj/project.pbxproj @@ -86,11 +86,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0820; }; }; }; @@ -149,8 +150,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -177,6 +180,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -193,8 +197,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -213,6 +219,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -226,6 +234,7 @@ PRODUCT_BUNDLE_IDENTIFIER = swift.algorithm.club.Tests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -238,6 +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 = 4.2; }; name = Release; }; diff --git a/Depth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Depth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index e5bd10d97..4462aede0 100644 --- a/Depth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Depth-First Search/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ { private var array: [T?] private var head: Int private var capacity: Int + private let originalCapacity: Int public init(_ capacity: Int = 10) { self.capacity = max(capacity, 1) + originalCapacity = self.capacity array = [T?](repeating: nil, count: capacity) head = capacity } @@ -30,7 +32,7 @@ public struct Deque { if head == 0 { capacity *= 2 let emptySpace = [T?](repeating: nil, count: capacity) - array.insertContentsOf(emptySpace, at: 0) + array.insert(contentsOf: emptySpace, at: 0) head = capacity } @@ -44,7 +46,7 @@ public struct Deque { array[head] = nil head += 1 - if capacity > 10 && head >= capacity*2 { + if capacity >= originalCapacity && head >= capacity*2 { let amountToRemove = capacity + capacity/2 array.removeFirst(amountToRemove) head -= amountToRemove 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 5858f3e6d..f3172e16e 100644 --- a/Deque/README.markdown +++ b/Deque/README.markdown @@ -9,23 +9,23 @@ Here is a very basic implementation of a deque in Swift: ```swift public struct Deque { private var array = [T]() - + public var isEmpty: Bool { return array.isEmpty } - + public var count: Int { return array.count } - + public mutating func enqueue(_ element: T) { array.append(element) } - + public mutating func enqueueFront(_ element: T) { - array.insert(element, atIndex: 0) + array.insert(element, at: 0) } - + public mutating func dequeue() -> T? { if isEmpty { return nil @@ -33,7 +33,7 @@ public struct Deque { return array.removeFirst() } } - + public mutating func dequeueBack() -> T? { if isEmpty { return nil @@ -41,11 +41,11 @@ public struct Deque { return array.removeLast() } } - + public func peekFront() -> T? { return array.first } - + public func peekBack() -> T? { return array.last } @@ -73,7 +73,7 @@ deque.dequeue() // 5 This particular implementation of `Deque` is simple but not very efficient. Several operations are **O(n)**, notably `enqueueFront()` and `dequeue()`. I've included it only to show the principle of what a deque does. ## A more efficient version - + The reason that `dequeue()` and `enqueueFront()` are **O(n)** is that they work on the front of the array. If you remove an element at the front of an array, what happens is that all the remaining elements need to be shifted in memory. Let's say the deque's array contains the following items: @@ -92,7 +92,7 @@ Likewise, inserting an element at the front of the array is expensive because it First, the elements `2`, `3`, and `4` are moved up by one position in the computer's memory, and then the new element `5` is inserted at the position where `2` used to be. -Why is this not an issue at for `enqueue()` and `dequeueBack()`? Well, these operations are performed at the end of the array. The way resizable arrays are implemented in Swift is by reserving a certain amount of free space at the back. +Why is this not an issue at for `enqueue()` and `dequeueBack()`? Well, these operations are performed at the end of the array. The way resizable arrays are implemented in Swift is by reserving a certain amount of free space at the back. Our initial array `[ 1, 2, 3, 4]` actually looks like this in memory: @@ -119,25 +119,27 @@ public struct Deque { private var array: [T?] private var head: Int private var capacity: Int - + private let originalCapacity:Int + public init(_ capacity: Int = 10) { self.capacity = max(capacity, 1) + originalCapacity = self.capacity array = [T?](repeating: nil, count: capacity) head = capacity } - + public var isEmpty: Bool { return count == 0 } - + public var count: Int { return array.count - head } - + public mutating func enqueue(_ element: T) { array.append(element) } - + public mutating func enqueueFront(_ element: T) { // this is explained below } @@ -153,7 +155,7 @@ public struct Deque { return array.removeLast() } } - + public func peekFront() -> T? { if isEmpty { return nil @@ -161,7 +163,7 @@ public struct Deque { return array[head] } } - + public func peekBack() -> T? { if isEmpty { return nil @@ -174,7 +176,7 @@ public struct Deque { It still largely looks the same -- `enqueue()` and `dequeueBack()` haven't changed -- but there are also a few important differences. The array now stores objects of type `T?` instead of just `T` because we need some way to mark array elements as being empty. -The `init` method allocates a new array that contains a certain number of `nil` values. This is the free room we have to work with at the beginning of the array. By default this creates 10 empty spots. +The `init` method allocates a new array that contains a certain number of `nil` values. This is the free room we have to work with at the beginning of the array. By default this creates 10 empty spots. The `head` variable is the index in the array of the front-most object. Since the queue is currently empty, `head` points at an index beyond the end of the array. @@ -217,7 +219,7 @@ Notice how the array has resized itself. There was no room to add the `1`, so Sw | head -> **Note:** You won't see those empty spots at the back of the array when you `print(deque.array)`. This is because Swift hides them from you. Only the ones at the front of the array show up. +> **Note:** You won't see those empty spots at the back of the array when you `print(deque.array)`. This is because Swift hides them from you. Only the ones at the front of the array show up. The `dequeue()` method does the opposite of `enqueueFront()`, it reads the value at `head`, sets the array element back to `nil`, and then moves `head` one position to the right: @@ -248,7 +250,7 @@ There is one tiny problem... If you enqueue a lot of objects at the front, you'r } ``` -If `head` equals 0, there is no room left at the front. When that happens, we add a whole bunch of new `nil` elements to the array. This is an **O(n)** operation but since this cost gets divided over all the `enqueueFront()`s, each individual call to `enqueueFront()` is still **O(1)** on average. +If `head` equals 0, there is no room left at the front. When that happens, we add a whole bunch of new `nil` elements to the array. This is an **O(n)** operation but since this cost gets divided over all the `enqueueFront()`s, each individual call to `enqueueFront()` is still **O(1)** on average. > **Note:** We also multiply the capacity by 2 each time this happens, so if your queue grows bigger and bigger, the resizing happens less often. This is also what Swift arrays automatically do at the back. @@ -267,7 +269,7 @@ Those empty spots at the front only get used when you call `enqueueFront()`. But array[head] = nil head += 1 - if capacity > 10 && head >= capacity*2 { + if capacity >= originalCapacity && head >= capacity*2 { let amountToRemove = capacity + capacity/2 array.removeFirst(amountToRemove) head -= amountToRemove @@ -279,6 +281,8 @@ Those empty spots at the front only get used when you call `enqueueFront()`. But Recall that `capacity` is the original number of empty places at the front of the queue. If the `head` has advanced more to the right than twice the capacity, then it's time to trim off a bunch of these empty spots. We reduce it to about 25%. +> **Note:** The deque will keep at least its original capacity by comparing `capacity` to `originalCapacity`. + For example, this: [ x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, 1, 2, 3 ] @@ -288,9 +292,9 @@ For example, this: becomes after trimming: [ x, x, x, x, x, 1, 2, 3 ] - | - head - capacity + | + head + capacity This way we can strike a balance between fast enqueuing and dequeuing at the front and keeping the memory requirements reasonable. @@ -298,7 +302,7 @@ This way we can strike a balance between fast enqueuing and dequeuing at the fro ## See also -Other ways to implement deque are by using a [doubly linked list](../Linked List/), a [circular buffer](../Ring Buffer/), or two [stacks](../Stack/) facing opposite directions. +Other ways to implement deque are by using a [doubly linked list](../Linked%20List/), a [circular buffer](../Ring%20Buffer/), or two [stacks](../Stack/) facing opposite directions. [A fully-featured deque implementation in Swift](https://github.com/lorentey/Deque) diff --git a/Dijkstra Algorithm/Dijkstra.playground/Contents.swift b/Dijkstra Algorithm/Dijkstra.playground/Contents.swift new file mode 100644 index 000000000..7c7f1a4b0 --- /dev/null +++ b/Dijkstra Algorithm/Dijkstra.playground/Contents.swift @@ -0,0 +1,80 @@ +//: Playground - noun: a place where people can play +import Foundation + +var vertices: Set = Set() + +func createNotConnectedVertices() { + //change this value to increase or decrease amount of vertices in the graph + let numberOfVerticesInGraph = 15 + for i in 0.. Vertex { + var newSet = vertices + newSet.remove(vertex) + let offset = Int(arc4random_uniform(UInt32(newSet.count))) + let index = newSet.index(newSet.startIndex, offsetBy: offset) + return newSet[index] +} + +func randomVertex() -> Vertex { + let offset = Int(arc4random_uniform(UInt32(vertices.count))) + let index = vertices.index(vertices.startIndex, offsetBy: offset) + return vertices[index] +} + +//initialize random graph +createNotConnectedVertices() +setupConnections() + +//initialize Dijkstra algorithm with graph vertices +let dijkstra = Dijkstra(vertices: vertices) + +//decide which vertex will be the starting one +let startVertex = randomVertex() + +let startTime = Date() + +//ask algorithm to find shortest paths from start vertex to all others +dijkstra.findShortestPaths(from: startVertex) + +let endTime = Date() + +print("calculation time is = \((endTime.timeIntervalSince(startTime))) sec") + +//printing results +let destinationVertex = randomVertex(except: startVertex) +print(destinationVertex.pathLengthFromStart) +var pathVerticesFromStartString: [String] = [] +for vertex in destinationVertex.pathVerticesFromStart { + pathVerticesFromStartString.append(vertex.identifier) +} + +print(pathVerticesFromStartString.joined(separator: "->")) + + diff --git a/Dijkstra Algorithm/Dijkstra.playground/Sources/Dijkstra.swift b/Dijkstra Algorithm/Dijkstra.playground/Sources/Dijkstra.swift new file mode 100644 index 000000000..8cda797dd --- /dev/null +++ b/Dijkstra Algorithm/Dijkstra.playground/Sources/Dijkstra.swift @@ -0,0 +1,41 @@ +import Foundation + +public class Dijkstra { + private var totalVertices: Set + + public init(vertices: Set) { + totalVertices = vertices + } + + private func clearCache() { + totalVertices.forEach { $0.clearCache() } + } + + public func findShortestPaths(from startVertex: Vertex) { + clearCache() + var currentVertices = self.totalVertices + startVertex.pathLengthFromStart = 0 + startVertex.pathVerticesFromStart.append(startVertex) + var currentVertex: Vertex? = startVertex + while let vertex = currentVertex { + currentVertices.remove(vertex) + let filteredNeighbors = vertex.neighbors.filter { currentVertices.contains($0.0) } + for neighbor in filteredNeighbors { + let neighborVertex = neighbor.0 + let weight = neighbor.1 + + let theoreticNewWeight = vertex.pathLengthFromStart + weight + if theoreticNewWeight < neighborVertex.pathLengthFromStart { + neighborVertex.pathLengthFromStart = theoreticNewWeight + neighborVertex.pathVerticesFromStart = vertex.pathVerticesFromStart + neighborVertex.pathVerticesFromStart.append(neighborVertex) + } + } + if currentVertices.isEmpty { + currentVertex = nil + break + } + currentVertex = currentVertices.min { $0.pathLengthFromStart < $1.pathLengthFromStart } + } + } +} diff --git a/Dijkstra Algorithm/Dijkstra.playground/Sources/Vertex.swift b/Dijkstra Algorithm/Dijkstra.playground/Sources/Vertex.swift new file mode 100644 index 000000000..a3587e04e --- /dev/null +++ b/Dijkstra Algorithm/Dijkstra.playground/Sources/Vertex.swift @@ -0,0 +1,30 @@ +import Foundation + +open class Vertex { + + open var identifier: String + open var neighbors: [(Vertex, Double)] = [] + open var pathLengthFromStart = Double.infinity + open var pathVerticesFromStart: [Vertex] = [] + + public init(identifier: String) { + self.identifier = identifier + } + + open func clearCache() { + pathLengthFromStart = Double.infinity + pathVerticesFromStart = [] + } +} + +extension Vertex: Hashable { + open var hashValue: Int { + return identifier.hashValue + } +} + +extension Vertex: Equatable { + public static func ==(lhs: Vertex, rhs: Vertex) -> Bool { + return lhs.hashValue == rhs.hashValue + } +} diff --git a/Dijkstra Algorithm/Dijkstra.playground/contents.xcplayground b/Dijkstra Algorithm/Dijkstra.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Dijkstra Algorithm/Dijkstra.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Dijkstra Algorithm/Dijkstra.playground/playground.xcworkspace/contents.xcworkspacedata b/Dijkstra Algorithm/Dijkstra.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Dijkstra Algorithm/Dijkstra.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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/Images/DirectedGraph.png b/Dijkstra Algorithm/Images/DirectedGraph.png new file mode 100644 index 000000000..67db49958 Binary files /dev/null and b/Dijkstra Algorithm/Images/DirectedGraph.png differ diff --git a/Dijkstra Algorithm/Images/Vertices.png b/Dijkstra Algorithm/Images/Vertices.png new file mode 100644 index 000000000..09b2ad2c0 Binary files /dev/null and b/Dijkstra Algorithm/Images/Vertices.png differ diff --git a/Dijkstra Algorithm/Images/WeightedDirectedGraph.png b/Dijkstra Algorithm/Images/WeightedDirectedGraph.png new file mode 100644 index 000000000..542c698e1 Binary files /dev/null and b/Dijkstra Algorithm/Images/WeightedDirectedGraph.png differ diff --git a/Dijkstra Algorithm/Images/WeightedDirectedGraphFinal.png b/Dijkstra Algorithm/Images/WeightedDirectedGraphFinal.png new file mode 100644 index 000000000..b2bb3e8e2 Binary files /dev/null and b/Dijkstra Algorithm/Images/WeightedDirectedGraphFinal.png differ diff --git a/Dijkstra Algorithm/Images/WeightedUndirectedGraph.png b/Dijkstra Algorithm/Images/WeightedUndirectedGraph.png new file mode 100644 index 000000000..2f26b74d4 Binary files /dev/null and b/Dijkstra Algorithm/Images/WeightedUndirectedGraph.png differ diff --git a/Dijkstra Algorithm/Images/image1.png b/Dijkstra Algorithm/Images/image1.png new file mode 100644 index 000000000..aabe0969e Binary files /dev/null and b/Dijkstra Algorithm/Images/image1.png differ diff --git a/Dijkstra Algorithm/Images/image2.png b/Dijkstra Algorithm/Images/image2.png new file mode 100644 index 000000000..d4c4d94da Binary files /dev/null and b/Dijkstra Algorithm/Images/image2.png differ diff --git a/Dijkstra Algorithm/Images/image3.png b/Dijkstra Algorithm/Images/image3.png new file mode 100644 index 000000000..493048ab7 Binary files /dev/null and b/Dijkstra Algorithm/Images/image3.png differ diff --git a/Dijkstra Algorithm/Images/image4.png b/Dijkstra Algorithm/Images/image4.png new file mode 100644 index 000000000..9c47aaaaf Binary files /dev/null and b/Dijkstra Algorithm/Images/image4.png differ diff --git a/Dijkstra Algorithm/Images/image5.png b/Dijkstra Algorithm/Images/image5.png new file mode 100644 index 000000000..249465444 Binary files /dev/null and b/Dijkstra Algorithm/Images/image5.png differ diff --git a/Dijkstra Algorithm/Images/image6.png b/Dijkstra Algorithm/Images/image6.png new file mode 100644 index 000000000..a2c7c7689 Binary files /dev/null and b/Dijkstra Algorithm/Images/image6.png differ diff --git a/Dijkstra Algorithm/Images/image7.png b/Dijkstra Algorithm/Images/image7.png new file mode 100644 index 000000000..dd91cab0b Binary files /dev/null and b/Dijkstra Algorithm/Images/image7.png differ diff --git a/Dijkstra Algorithm/README.md b/Dijkstra Algorithm/README.md new file mode 100644 index 000000000..4bc719598 --- /dev/null +++ b/Dijkstra Algorithm/README.md @@ -0,0 +1,317 @@ +# Weighted graph general concepts + +Every weighted graph should contain: +1. Vertices/Nodes (I will use "vertex" in this readme). + + + +2. Edges connecting vertices. Let's add some edges to our graph. For simplicity let's create directed graph for now. Directed means that edge has a direction, i.e. vertex, where it starts and vertex, where it ends. But remember a VERY IMPORTANT thing: + * All undirected graphs can be viewed as a directed graph. + * A directed graph is undirected if and only if every edge is paired with an edge going in the opposite direction. + + + +3. Weights for every edge. + + + +Final result. +Directed weighted graph: + + + +Undirected weighted graph: + + + +And once again: An undirected graph it is a directed graph with every edge paired with an edge going in the opposite direction. This statement is clear on the image above. + +Great! Now we are familiar with general concepts about graphs. + +# The Dijkstra's algorithm +This [algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) was invented in 1956 by Edsger W. Dijkstra. + +It can be used when you have one source vertex and want to find the shortest paths to ALL other vertices in the graph. + +The best example is a road network. If you want to find the shortest path from your house to your job or if you want to find the closest store to your house then it is time for the Dijkstra's algorithm. + +The algorithm repeats following cycle until all vertices are marked as visited. +Cycle: +1. From the non-visited vertices the algorithm picks a vertex with the shortest path length from the start (if there are more than one vertex with the same shortest path value then algorithm picks any of them) +2. The algorithm marks picked vertex as visited. +3. The algorithm checks all of its neighbours. If the current vertex path length from the start plus an edge weight to a neighbour less than the neighbour current path length from the start than it assigns new path length from the start to the neighbour. +When all vertices are marked as visited, the algorithm's job is done. Now, you can see the shortest path from the start for every vertex by pressing the one you are interested in. + +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. + +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 + +When the algorithm starts to work initial graph looks like this: + + + +The table below represents graph state: + +| | A | B | C | D | E | +|:------------------------- |:---:|:---:|:---:|:---:|:---:| +| Visited | F | F | F | F | F | +| Path Length From Start | inf | inf | inf | inf | inf | +| Path Vertices From Start | [ ] | [ ] | [ ] | [ ] | [ ] | + +>inf is equal infinity which basically means that algorithm doesn't know how far away is this vertex from start one. + +>F states for False + +>T states for True + +To initialize our graph we have to set source vertex path length from source vertex to 0 and append itself to path vertices from start. + +| | A | B | C | D | E | +|:------------------------- |:---:|:---:|:---:|:---:|:---:| +| Visited | F | F | F | F | F | +| Path Length From Start | 0 | inf | inf | inf | inf | +| Path Vertices From Start | [A] | [ ] | [ ] | [ ] | [ ] | + +Great, now our graph is initialised and we can pass it to the Dijkstra's algorithm, let's start! + +Let's follow the algorithm's cycle and pick the first vertex which neighbours we want to check. +All our vertices are not visited but there is only one has the smallest path length from start. It is A. This vertex is the first one which neighbors we will check. +First of all, set this vertex as visited. + +A.visited = true + + + +After this step graph has this state: + +| | A | B | C | D | E | +|:------------------------- |:---:|:---:|:---:|:---:|:---:| +| Visited | T | F | F | F | F | +| Path Length From Start | 0 | inf | inf | inf | inf | +| Path Vertices From Start | [A] | [ ] | [ ] | [ ] | [ ] | + +### Step 1 + +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: +```swift +if (A.pathLengthFromStart + AB.weight) < B.pathLengthFromStart { + B.pathLengthFromStart = A.pathLengthFromStart + AB.weight + B.pathVerticesFromStart = A.pathVerticesFromStart + B.pathVerticesFromStart.append(B) +} +``` +And now our graph looks like this one: + + + +And its state is here: + +| | A | B | C | D | E | +|:------------------------- |:----------:|:----------:|:----------:|:----------:|:----------:| +| Visited | T | F | F | F | F | +| Path Length From Start | 0 | 3 | inf | 1 | inf | +| Path Vertices From Start | [A] | [A, B] | [ ] | [A, D] | [ ] | + +### Step 2 + +From now we repeat all actions again and fill our table with new info! + + + +| | A | B | C | D | E | +|:------------------------- |:----------:|:----------:|:----------:|:----------:|:----------:| +| Visited | T | F | F | T | F | +| Path Length From Start | 0 | 3 | inf | 1 | 2 | +| Path Vertices From Start | [A] | [A, B] | [ ] | [A, D] | [A, D, E] | + +### Step 3 + + + +| | A | B | C | D | E | +|:------------------------- |:----------:|:----------:|:----------:|:----------:|:----------:| +| Visited | T | F | F | T | T | +| Path Length From Start | 0 | 3 | 11 | 1 | 2 | +| Path Vertices From Start | [A] | [A, B] |[A, D, E, C]| [A, D] | [A, D, E ] | + +### Step 4 + + + +| | A | B | C | D | E | +|:------------------------- |:----------:|:----------:|:----------:|:----------:|:----------:| +| Visited | T | T | F | T | T | +| Path Length From Start | 0 | 3 | 8 | 1 | 2 | +| Path Vertices From Start | [A] | [A, B] | [A, B, C]| [A, D] | [A, D, E ] | + +### Step 5 + + + +| | A | B | C | D | E | +|:------------------------- |:----------:|:----------:|:----------:|:----------:|:----------:| +| Visited | T | T | T | T | T | +| Path Length From Start | 0 | 3 | 8 | 1 | 2 | +| Path Vertices From Start | [A] | [A, B] | [A, B, C]| [A, D] | [A, D, E ] | + + +## Code implementation +First of all, let’s create class that will describe any Vertex in the graph. +It is pretty simple +```swift +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 + //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 + + //This array contains vertices which we need to go through to reach this vertex from starting one + //As with path length from start, we will change this array during the algorithm execution. + open var pathVerticesFromStart: [Vertex] = [] + + public init(identifier: String) { + self.identifier = identifier + } + + //This function let us use the same array of vertices again and again to calculate paths with different starting vertex. + //When we will need to set new starting vertex and recalculate paths then we will simply clear graph vertices' cashes. + open func clearCache() { + pathLengthFromStart = Double.infinity + pathVerticesFromStart = [] + } +} +``` + +As every vertex should be unique it is useful to make them Hashable and according Equatable. We use an identifier for this purposes. +```swift +extension Vertex: Hashable { + open var hashValue: Int { + return identifier.hashValue + } +} + +extension Vertex: Equatable { + public static func ==(lhs: Vertex, rhs: Vertex) -> Bool { + return lhs.hashValue == rhs.hashValue + } +} +``` + +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. + //Assuming that our vertices are unique we can use Set instead of array. This approach will bring some benefits later. + private var totalVertices: Set + + public init(vertices: Set) { + totalVertices = vertices + } + + //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, + //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. + //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. + 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 } + } + } +} +``` + +That's all! Now you can check this algorithm in the playground. On the main page there is a code for creating a random graph. + +Also there is a **VisualizedDijkstra.playground**. Use it to figure out the algorithm's flow in real (slowed :)) time. + +It is up to you how to implement some specific parts of the algorithm, you can use Array instead of Set, add _visited_ property to Vertex or you can create some local totalVertices Array/Set inside _func findShortestPaths(from startVertex: Vertex)_ to keep totalVertices Array/Set unchanged. This is a general explanation with one possible implementation :) + +# About this repository + +This repository contains two playgrounds: +* To understand how does this algorithm works, I created **VisualizedDijkstra.playground.** It works in auto and interactive modes. Moreover, there are play/pause/stop buttons. +* If you need only realisation of the algorithm without visualisation then run **Dijkstra.playground.** It contains necessary classes and couple functions to create random graph for algorithm testing. + +# Demo video + +Click the link: [YouTube](https://youtu.be/PPESI7et0cQ) + +# Credits + +WWDC 2017 Scholarship Project (Rejected) created by [Taras Nikulin](https://github.com/crabman448) diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Contents.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Contents.swift new file mode 100644 index 000000000..de6b49834 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Contents.swift @@ -0,0 +1,58 @@ +/*: + ## Dijkstra's algorithm visualization + This playground is about the [Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm). + Plyground works in 2 modes: + * Auto visualization + * Interactive visualization + + */ +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. + */ +GraphColors.sharedInstance.visitedColor = #colorLiteral(red: 0, green: 0.5898008943, blue: 1, alpha: 1) +GraphColors.sharedInstance.checkingColor = #colorLiteral(red: 0.9411764741, green: 0.4980392158, blue: 0.3529411852, alpha: 1) +GraphColors.sharedInstance.defaultEdgeColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1) +GraphColors.sharedInstance.defaultVertexColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1) + +GraphColors.sharedInstance.mainWindowBackgroundColor = #colorLiteral(red: 0.921431005, green: 0.9214526415, blue: 0.9214410186, alpha: 1) +GraphColors.sharedInstance.topViewBackgroundColor = #colorLiteral(red: 1, green: 0.4932718873, blue: 0.4739984274, alpha: 1) +GraphColors.sharedInstance.buttonsBackgroundColor = #colorLiteral(red: 0, green: 0.3285208941, blue: 0.5748849511, alpha: 1) +GraphColors.sharedInstance.graphBackgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) +/*: + Now, we need to create some graph. You can create graph with any vertices amount but I aware you from setting up too many, otherwise it will be hard to place all of them nicely on the screen. Also, you can change the animations' duration: slow down or speed up. + */ +let graph = Graph(verticesCount: 6) +graph.interactiveNeighborCheckAnimationDuration = 1.2 +graph.visualizationNeighborCheckAnimationDuration = 1.2 +/*: + Now, let's configure the graph's visual representation by passing the virtual graph to our window. For better perception open live view in full screen. + */ +let screenBounds = UIScreen.main.bounds +let frame = CGRect(x: 0, y: 0, width: screenBounds.width * 0.8, height: screenBounds.height * 0.8) +let window = Window(frame: frame) +window.configure(graph: graph) +PlaygroundPage.current.liveView = window +/*: + **Great!** + + Now we have graph on the screen. It is beautiful, isn't it? ;) Before the visualization starts, I recommend you to move vertices around the screen using you finger to be sure that all vertices and edges are properly visible. + + And a final step! Before you will see the visualization **(by pressing "Visualization" button),** please, read explanation of how the Dijkstra's algorithm works. + + Algorithm's flow: + First of all, this program randomly decides which vertex will be the start one, then the program assigns a zero value to the start vertex path length from the start. + + Then the algorithm repeats following cycle until all vertices are marked as visited. + Cycle: + 1) From the non-visited vertices the algorithm picks a vertex with the shortest path length from the start (if there are more than one vertex with the same shortest path value, then algorithm picks any of them) + 2) The algorithm marks picked vertex as visited. + 3) The algorithm check all of its neighbors. If the current vertex path length from the start plus an edge weight to a neighbor less than the neighbor current path length from the start, than it assigns new path length from the start to the neihgbor. + When all vertices are marked as visited, the algorithm's job is done. Now, you can see the shortest path from the start for every vertex by pressing the one you are interested in. + + Now, try yourself at the Dijkstra's algorithm. Press **"Interactive" button.** The program will mark the start vertex as visited and calculate new paths for its neighbors. You have to pick next vertex for the algorithm to check. If you are wrong, you will see a message on the screen. + + Good luck and have fun! ;) + */ diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Pause.png b/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Pause.png new file mode 100644 index 000000000..12a5d4922 Binary files /dev/null and b/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Pause.png differ diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Start.png b/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Start.png new file mode 100644 index 000000000..3ab955629 Binary files /dev/null and b/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Start.png differ diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Stop.png b/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Stop.png new file mode 100644 index 000000000..b74585cbe Binary files /dev/null and b/Dijkstra Algorithm/VisualizedDijkstra.playground/Resources/Stop.png differ diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/EdgeRepresentation.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/EdgeRepresentation.swift new file mode 100644 index 000000000..d827d3924 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/EdgeRepresentation.swift @@ -0,0 +1,109 @@ +import UIKit + +public class EdgeRepresentation { + private var graphColors: GraphColors = GraphColors.sharedInstance + + public private(set)var label: UILabel! + public private(set)var layer: MyShapeLayer! + + public init(from vertex1: Vertex, to vertex2: Vertex, weight: Double) { + guard let vertex1View = vertex1.view, let vertex2View = vertex2.view else { + assertionFailure("passed vertices without configured views") + return + } + let x1 = vertex1View.frame.origin.x + let y1 = vertex1View.frame.origin.y + let width1 = vertex1View.frame.width + let height1 = vertex1View.frame.height + + let x2 = vertex2View.frame.origin.x + let y2 = vertex2View.frame.origin.y + let width2 = vertex2View.frame.width + let height2 = vertex2View.frame.height + + var startPoint: CGPoint + var endPoint: CGPoint + + if y1 == y2 { + if x1 < x2 { + startPoint = CGPoint(x: x1 + width1, y: y1 + height1 / 2) + endPoint = CGPoint(x: x2, y: y2 + height2 / 2) + } else { + startPoint = CGPoint(x: x1, y: y1 + height1 / 2) + endPoint = CGPoint(x: x2 + width2, y: y2 + height2 / 2) + } + } else { + startPoint = CGPoint(x: x1 + width1 / 2, y: y1 + height1) + endPoint = CGPoint(x: x2 + width2 / 2, y: y2) + } + + let arcDiameter: CGFloat = 20 + var circleOrigin: CGPoint! + + if endPoint.x == startPoint.x { + startPoint.y -= 1 + endPoint.y += 1 + let x = startPoint.x - arcDiameter / 2 + let y = startPoint.y + ((endPoint.y - startPoint.y) / 2 * 1.25 - arcDiameter / 2) + circleOrigin = CGPoint(x: x, y: y) + } else if endPoint.y == startPoint.y { + let x = startPoint.x + ((endPoint.x - startPoint.x) / 2 * 1.25 - arcDiameter / 2) + let y = startPoint.y + ((endPoint.y - startPoint.y) / 2 * 1.25 - arcDiameter / 2) + circleOrigin = CGPoint(x: x, y: y) + } else { + startPoint.x -= 1 + endPoint.x += 1 + startPoint.y -= 1 + endPoint.y += 1 + let x = startPoint.x + ((endPoint.x - startPoint.x) / 2 * 1.25 - arcDiameter / 2) + let y = startPoint.y + ((endPoint.y - startPoint.y) / 2 * 1.25 - arcDiameter / 2) + circleOrigin = CGPoint(x: x, y: y) + } + + + let path = UIBezierPath() + path.move(to: startPoint) + path.addLine(to: endPoint) + + let label = UILabel(frame: CGRect(origin: circleOrigin, size: CGSize(width: arcDiameter, height: arcDiameter))) + label.textAlignment = .center + label.backgroundColor = graphColors.defaultEdgeColor + label.clipsToBounds = true + label.adjustsFontSizeToFitWidth = true + label.layer.cornerRadius = arcDiameter / 2 + label.text = "" + + let shapeLayer = MyShapeLayer() + shapeLayer.path = path.cgPath + shapeLayer.strokeColor = graphColors.defaultEdgeColor.cgColor + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.lineWidth = 2.0 + shapeLayer.startPoint = startPoint + shapeLayer.endPoint = endPoint + shapeLayer.actions = ["position" : NSNull(), "bounds" : NSNull(), "path" : NSNull()] + + self.layer = shapeLayer + self.label = label + self.label.text = "\(weight)" + } + + public func setCheckingColor() { + layer.strokeColor = graphColors.checkingColor.cgColor + label.backgroundColor = graphColors.checkingColor + } + + public func setDefaultColor() { + layer.strokeColor = graphColors.defaultEdgeColor.cgColor + label.backgroundColor = graphColors.defaultEdgeColor + } + + public func setText(text: String) { + label.text = text + } +} + +extension EdgeRepresentation: Equatable { + public static func ==(lhs: EdgeRepresentation, rhs: EdgeRepresentation) -> Bool { + return lhs.label.hashValue == rhs.label.hashValue && lhs.layer.hashValue == rhs.layer.hashValue + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/ErrorView.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/ErrorView.swift new file mode 100644 index 000000000..a63655dd9 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/ErrorView.swift @@ -0,0 +1,31 @@ +import UIKit + +public class ErrorView: UIView { + private var label: UILabel! + + public override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + backgroundColor = UIColor(red: 242/255, green: 156/255, blue: 84/255, alpha: 1) + layer.cornerRadius = 10 + + let labelFrame = CGRect(x: 10, y: 10, width: frame.width - 20, height: frame.height - 20) + label = UILabel(frame: labelFrame) + label.numberOfLines = 0 + label.adjustsFontSizeToFitWidth = true + label.textAlignment = .center + label.textColor = UIColor.white + addSubview(label) + } + + public func setText(text: String) { + label.text = text + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/MyShapeLayer.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/MyShapeLayer.swift new file mode 100644 index 000000000..33bf4a515 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/MyShapeLayer.swift @@ -0,0 +1,6 @@ +import UIKit + +public class MyShapeLayer: CAShapeLayer { + public var startPoint: CGPoint? + public var endPoint: CGPoint? +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/RoundedButton.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/RoundedButton.swift new file mode 100644 index 000000000..819fcfc5c --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/RoundedButton.swift @@ -0,0 +1,20 @@ +import UIKit + +public class RoundedButton: UIButton { + private var graphColors: GraphColors = GraphColors.sharedInstance + + public override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + backgroundColor = graphColors.buttonsBackgroundColor + titleLabel?.adjustsFontSizeToFitWidth = true + layer.cornerRadius = 7 + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/VertexView.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/VertexView.swift new file mode 100644 index 000000000..e785e120b --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/CustomUI/VertexView.swift @@ -0,0 +1,63 @@ +import UIKit + +public class VertexView: UIButton { + + public var vertex: Vertex? + private var idLabel: UILabel! + private var pathLengthLabel: UILabel! + private var graphColors: GraphColors = GraphColors.sharedInstance + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override init(frame: CGRect) { + precondition(frame.height == frame.width) + precondition(frame.height >= 30) + super.init(frame: frame) + backgroundColor = graphColors.defaultVertexColor + layer.cornerRadius = frame.width / 2 + clipsToBounds = true + setupIdLabel() + setupPathLengthFromStartLabel() + } + + private func setupIdLabel() { + let x: CGFloat = frame.width * 0.2 + let y: CGFloat = 0 + let width: CGFloat = frame.width * 0.6 + let height: CGFloat = frame.height / 2 + let idLabelFrame = CGRect(x: x, y: y, width: width, height: height) + + idLabel = UILabel(frame: idLabelFrame) + idLabel.textAlignment = .center + idLabel.adjustsFontSizeToFitWidth = true + addSubview(idLabel) + } + + private func setupPathLengthFromStartLabel() { + let x: CGFloat = frame.width * 0.2 + let y: CGFloat = frame.height / 2 + let width: CGFloat = frame.width * 0.6 + let height: CGFloat = frame.height / 2 + let pathLengthLabelFrame = CGRect(x: x, y: y, width: width, height: height) + + pathLengthLabel = UILabel(frame: pathLengthLabelFrame) + pathLengthLabel.textAlignment = .center + pathLengthLabel.adjustsFontSizeToFitWidth = true + addSubview(pathLengthLabel) + } + + public func setIdLabel(text: String) { + idLabel.text = text + } + + public func setPathLengthLabel(text: String) { + pathLengthLabel.text = text + } + + public func setLabelsTextColor(color: UIColor) { + idLabel.textColor = color + pathLengthLabel.textColor = color + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Graph.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Graph.swift new file mode 100644 index 000000000..447b3dbe1 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Graph.swift @@ -0,0 +1,310 @@ +import Foundation +import UIKit + +public enum GraphState { + case initial + case autoVisualization + case interactiveVisualization + case parsing + case completed +} + +public class Graph { + private var verticesCount: UInt + private var _vertices: Set = Set() + public weak var delegate: GraphDelegate? + public var nextVertices: [Vertex] = [] + public var state: GraphState = .initial + public var pauseVisualization = false + public var stopVisualization = false + public var startVertex: Vertex? + public var interactiveNeighborCheckAnimationDuration: Double = 1.8 { + didSet { + _interactiveOneSleepDuration = UInt32(interactiveNeighborCheckAnimationDuration * 1000000.0 / 3.0) + } + } + private var _interactiveOneSleepDuration: UInt32 = 600000 + + public var visualizationNeighborCheckAnimationDuration: Double = 2.25 { + didSet { + _visualizationOneSleepDuration = UInt32(visualizationNeighborCheckAnimationDuration * 1000000.0 / 3.0) + } + } + private var _visualizationOneSleepDuration: UInt32 = 750000 + + public var vertices: Set { + return _vertices + } + + public init(verticesCount: UInt) { + self.verticesCount = verticesCount + } + + public func removeGraph() { + _vertices.removeAll() + startVertex = nil + } + + public func createNewGraph() { + guard _vertices.isEmpty, startVertex == nil else { + assertionFailure("Clear graph before creating new one") + return + } + createNotConnectedVertices() + setupConnections() + let offset = Int(arc4random_uniform(UInt32(_vertices.count))) + let index = _vertices.index(_vertices.startIndex, offsetBy: offset) + startVertex = _vertices[index] + setVertexLevels() + } + + private func clearCache() { + _vertices.forEach { $0.clearCache() } + } + + public func reset() { + for vertex in _vertices { + vertex.clearCache() + } + } + + private func createNotConnectedVertices() { + for i in 0.. Vertex { + var newSet = _vertices + newSet.remove(vertex) + let offset = Int(arc4random_uniform(UInt32(newSet.count))) + let index = newSet.index(newSet.startIndex, offsetBy: offset) + return newSet[index] + } + + private func setVertexLevels() { + _vertices.forEach { $0.clearLevelInfo() } + guard let startVertex = startVertex else { + assertionFailure() + return + } + var queue: [Vertex] = [startVertex] + startVertex.levelChecked = true + + //BFS + while !queue.isEmpty { + let currentVertex = queue.first! + for edge in currentVertex.edges { + let neighbor = edge.neighbor + if !neighbor.levelChecked { + neighbor.levelChecked = true + neighbor.level = currentVertex.level + 1 + queue.append(neighbor) + } + } + queue.removeFirst() + } + } + + public func findShortestPathsWithVisualization(completion: () -> Void) { + guard let startVertex = self.startVertex else { + assertionFailure("start vertex is nil") + return + } + clearCache() + startVertex.pathLengthFromStart = 0 + startVertex.pathVerticesFromStart.append(startVertex) + var currentVertex: Vertex? = startVertex + + var totalVertices = _vertices + + breakableLoop: while let vertex = currentVertex { + totalVertices.remove(vertex) + while pauseVisualization == true { + if stopVisualization == true { + break breakableLoop + } + } + if stopVisualization == true { + break breakableLoop + } + DispatchQueue.main.async { + vertex.setVisitedColor() + } + usleep(750000) + vertex.visited = true + let filteredEdges = vertex.edges.filter { !$0.neighbor.visited } + for edge in filteredEdges { + let neighbor = edge.neighbor + let weight = edge.weight + let edgeRepresentation = edge.edgeRepresentation + + while pauseVisualization == true { + if stopVisualization == true { + break breakableLoop + } + } + if stopVisualization == true { + break breakableLoop + } + DispatchQueue.main.async { + edgeRepresentation?.setCheckingColor() + neighbor.setCheckingPathColor() + self.delegate?.willCompareVertices(startVertexPathLength: vertex.pathLengthFromStart, + edgePathLength: weight, + endVertexPathLength: neighbor.pathLengthFromStart) + } + usleep(_visualizationOneSleepDuration) + + + let theoreticNewWeight = vertex.pathLengthFromStart + weight + + if theoreticNewWeight < neighbor.pathLengthFromStart { + while pauseVisualization == true { + if stopVisualization == true { + break breakableLoop + } + } + if stopVisualization == true { + break breakableLoop + } + neighbor.pathLengthFromStart = theoreticNewWeight + neighbor.pathVerticesFromStart = vertex.pathVerticesFromStart + neighbor.pathVerticesFromStart.append(neighbor) + } + usleep(_visualizationOneSleepDuration) + + DispatchQueue.main.async { + self.delegate?.didFinishCompare() + edge.edgeRepresentation?.setDefaultColor() + edge.neighbor.setDefaultColor() + } + usleep(_visualizationOneSleepDuration) + } + if totalVertices.isEmpty { + currentVertex = nil + break + } + currentVertex = totalVertices.min { $0.pathLengthFromStart < $1.pathLengthFromStart } + } + if stopVisualization == true { + DispatchQueue.main.async { + self.delegate?.didStop() + } + } else { + completion() + } + } + + public func parseNeighborsFor(vertex: Vertex, completion: @escaping () -> ()) { + DispatchQueue.main.async { + vertex.setVisitedColor() + } + DispatchQueue.global(qos: .background).async { + vertex.visited = true + + let nonVisitedVertices = self._vertices.filter { $0.visited == false } + if nonVisitedVertices.isEmpty { + self.state = .completed + DispatchQueue.main.async { + self.delegate?.didCompleteGraphParsing() + } + return + } + + let filteredEdges = vertex.edges.filter { !$0.neighbor.visited } + breakableLoop: for edge in filteredEdges { + while self.pauseVisualization == true { + if self.stopVisualization == true { + break breakableLoop + } + } + if self.stopVisualization == true { + break breakableLoop + } + let weight = edge.weight + let neighbor = edge.neighbor + + DispatchQueue.main.async { + edge.neighbor.setCheckingPathColor() + edge.edgeRepresentation?.setCheckingColor() + self.delegate?.willCompareVertices(startVertexPathLength: vertex.pathLengthFromStart, + edgePathLength: weight, + endVertexPathLength: neighbor.pathLengthFromStart) + } + usleep(self._interactiveOneSleepDuration) + + let theoreticNewWeight = vertex.pathLengthFromStart + weight + if theoreticNewWeight < neighbor.pathLengthFromStart { + while self.pauseVisualization == true { + if self.stopVisualization == true { + break breakableLoop + } + } + if self.stopVisualization == true { + break breakableLoop + } + neighbor.pathLengthFromStart = theoreticNewWeight + neighbor.pathVerticesFromStart = vertex.pathVerticesFromStart + neighbor.pathVerticesFromStart.append(neighbor) + } + + usleep(self._interactiveOneSleepDuration) + while self.pauseVisualization == true { + if self.stopVisualization == true { + break breakableLoop + } + } + if self.stopVisualization == true { + break breakableLoop + } + DispatchQueue.main.async { + self.delegate?.didFinishCompare() + edge.neighbor.setDefaultColor() + edge.edgeRepresentation?.setDefaultColor() + } + usleep(self._interactiveOneSleepDuration) + } + if self.stopVisualization == true { + DispatchQueue.main.async { + self.delegate?.didStop() + } + } else { + let nextVertexPathLength = nonVisitedVertices.sorted { $0.pathLengthFromStart < $1.pathLengthFromStart }.first!.pathLengthFromStart + self.nextVertices = nonVisitedVertices.filter { $0.pathLengthFromStart == nextVertexPathLength } + completion() + } + } + } + + public func didTapVertex(vertex: Vertex) { + if nextVertices.contains(vertex) { + delegate?.willStartVertexNeighborsChecking() + state = .parsing + parseNeighborsFor(vertex: vertex) { + self.state = .interactiveVisualization + self.delegate?.didFinishVertexNeighborsChecking() + } + } else { + self.delegate?.didTapWrongVertex() + } + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/GraphColors.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/GraphColors.swift new file mode 100644 index 000000000..95e9b8d74 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/GraphColors.swift @@ -0,0 +1,15 @@ +import UIKit + +public class GraphColors { + public static let sharedInstance: GraphColors = GraphColors() + private init() { } + + public var mainWindowBackgroundColor: UIColor = #colorLiteral(red: 0.921431005, green: 0.9214526415, blue: 0.9214410186, alpha: 1) + public var topViewBackgroundColor: UIColor = #colorLiteral(red: 1, green: 0.4932718873, blue: 0.4739984274, alpha: 1) + public var graphBackgroundColor: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + public var defaultVertexColor: UIColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1) + public var defaultEdgeColor: UIColor = #colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1) + public var checkingColor: UIColor = #colorLiteral(red: 0.9411764741, green: 0.4980392158, blue: 0.3529411852, alpha: 1) + public var visitedColor: UIColor = #colorLiteral(red: 0, green: 0.5898008943, blue: 1, alpha: 1) + public var buttonsBackgroundColor: UIColor = #colorLiteral(red: 0, green: 0.3285208941, blue: 0.5748849511, alpha: 1) +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/GraphView.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/GraphView.swift new file mode 100644 index 000000000..ec4f74705 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/GraphView.swift @@ -0,0 +1,244 @@ +import UIKit + +public class GraphView: UIView { + private var graph: Graph + private var panningView: VertexView? = nil + private var graphColors = GraphColors.sharedInstance + + public init(frame: CGRect, graph: Graph) { + self.graph = graph + super.init(frame: frame) + backgroundColor = graphColors.graphBackgroundColor + layer.cornerRadius = 15 + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + + } + + public func removeGraph() { + for vertex in graph.vertices { + if let view = vertex.view { + view.removeFromSuperview() + vertex.view = nil + } + } + for vertex in graph.vertices { + for edge in vertex.edges { + if let edgeRepresentation = edge.edgeRepresentation { + edgeRepresentation.layer.removeFromSuperlayer() + edgeRepresentation.label.removeFromSuperview() + edge.edgeRepresentation = nil + } + } + } + } + + public func createNewGraph() { + setupVertexViews() + setupEdgeRepresentations() + addGraph() + } + + public func reset() { + for vertex in graph.vertices { + vertex.edges.forEach { $0.edgeRepresentation?.setDefaultColor() } + vertex.setDefaultColor() + } + } + + private func addGraph() { + for vertex in graph.vertices { + for edge in vertex.edges { + if let edgeRepresentation = edge.edgeRepresentation { + layer.addSublayer(edgeRepresentation.layer) + addSubview(edgeRepresentation.label) + } + } + } + for vertex in graph.vertices { + if let view = vertex.view { + addSubview(view) + } + } + } + + private func setupVertexViews() { + var level = 0 + var buildViewQueue = [graph.startVertex!] + let itemWidth: CGFloat = 40 + while !buildViewQueue.isEmpty { + let levelItemsCount = CGFloat(buildViewQueue.count) + let xStep = (frame.width - levelItemsCount * itemWidth) / (levelItemsCount + 1) + var previousVertexMaxX: CGFloat = 0.0 + for vertex in buildViewQueue { + let x: CGFloat = previousVertexMaxX + xStep + let y: CGFloat = CGFloat(level * 100) + previousVertexMaxX = x + itemWidth + let frame = CGRect(x: x, y: y, width: itemWidth, height: itemWidth) + let vertexView = VertexView(frame: frame) + vertex.view = vertexView + vertexView.vertex = vertex + vertex.view?.setIdLabel(text: vertex.identifier) + vertex.view?.setPathLengthLabel(text: "\(vertex.pathLengthFromStart)") + vertex.view?.addTarget(self, action: #selector(didTapVertex(sender:)), for: .touchUpInside) + let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(recognizer:))) + vertex.view?.addGestureRecognizer(panGesture) + } + level += 1 + buildViewQueue = graph.vertices.filter { $0.level == level } + } + } + + private var movingVertexInputEdges: [EdgeRepresentation] = [] + private var movingVertexOutputEdges: [EdgeRepresentation] = [] + + @objc private func handlePan(recognizer: UIPanGestureRecognizer) { + guard let vertexView = recognizer.view as? VertexView, let vertex = vertexView.vertex else { + return + } + if panningView != nil { + if panningView != vertexView { + return + } + } + + switch recognizer.state { + case .began: + let movingVertexViewFrame = vertexView.frame + let sizedVertexView = CGRect(x: movingVertexViewFrame.origin.x - 10, + y: movingVertexViewFrame.origin.y - 10, + width: movingVertexViewFrame.width + 20, + height: movingVertexViewFrame.height + 20) + vertex.edges.forEach { edge in + if let edgeRepresentation = edge.edgeRepresentation{ + if sizedVertexView.contains(edgeRepresentation.layer.startPoint!) { + movingVertexOutputEdges.append(edgeRepresentation) + } else { + movingVertexInputEdges.append(edgeRepresentation) + } + } + } + panningView = vertexView + case .changed: + if movingVertexOutputEdges.isEmpty && movingVertexInputEdges.isEmpty { + return + } + let translation = recognizer.translation(in: self) + if vertexView.frame.origin.x + translation.x <= 0 + || vertexView.frame.origin.y + translation.y <= 0 + || (vertexView.frame.origin.x + vertexView.frame.width + translation.x) >= frame.width + || (vertexView.frame.origin.y + vertexView.frame.height + translation.y) >= frame.height { + break + } + movingVertexInputEdges.forEach { edgeRepresentation in + let originalLabelCenter = edgeRepresentation.label.center + edgeRepresentation.label.center = CGPoint(x: originalLabelCenter.x + translation.x * 0.625, + y: originalLabelCenter.y + translation.y * 0.625) + + CATransaction.begin() + CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions) + let newPath = path(fromEdgeRepresentation: edgeRepresentation, movingVertex: vertex, translation: translation, outPath: false) + edgeRepresentation.layer.path = newPath + CATransaction.commit() + + } + + movingVertexOutputEdges.forEach { edgeRepresentation in + let originalLabelCenter = edgeRepresentation.label.center + edgeRepresentation.label.center = CGPoint(x: originalLabelCenter.x + translation.x * 0.375, + y: originalLabelCenter.y + translation.y * 0.375) + + CATransaction.begin() + CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions) + let newPath = path(fromEdgeRepresentation: edgeRepresentation, movingVertex: vertex, translation: translation, outPath: true) + edgeRepresentation.layer.path = newPath + CATransaction.commit() + } + + vertexView.center = CGPoint(x: vertexView.center.x + translation.x, + y: vertexView.center.y + translation.y) + recognizer.setTranslation(CGPoint.zero, in: self) + case .ended: + movingVertexInputEdges = [] + movingVertexOutputEdges = [] + panningView = nil + default: + break + } + } + + private func path(fromEdgeRepresentation edgeRepresentation: EdgeRepresentation, movingVertex: Vertex, translation: CGPoint, outPath: Bool) -> CGPath { + let bezier = UIBezierPath() + + if outPath == true { + bezier.move(to: edgeRepresentation.layer.endPoint!) + let startPoint = CGPoint(x: edgeRepresentation.layer.startPoint!.x + translation.x, + y: edgeRepresentation.layer.startPoint!.y + translation.y) + edgeRepresentation.layer.startPoint = startPoint + bezier.addLine(to: startPoint) + } else { + bezier.move(to: edgeRepresentation.layer.startPoint!) + let endPoint = CGPoint(x: edgeRepresentation.layer.endPoint!.x + translation.x, + y: edgeRepresentation.layer.endPoint!.y + translation.y) + edgeRepresentation.layer.endPoint = endPoint + bezier.addLine(to: endPoint) + } + return bezier.cgPath + } + + @objc private func didTapVertex(sender: AnyObject) { + DispatchQueue.main.async { + if self.graph.state == .completed { + for vertex in self.graph.vertices { + vertex.edges.forEach { $0.edgeRepresentation?.setDefaultColor() } + vertex.setVisitedColor() + } + if let vertexView = sender as? VertexView, let vertex = vertexView.vertex { + for (index, pathVertex) in vertex.pathVerticesFromStart.enumerated() { + pathVertex.setCheckingPathColor() + if vertex.pathVerticesFromStart.count > index + 1 { + let nextVertex = vertex.pathVerticesFromStart[index + 1] + + if let edge = pathVertex.edges.filter({ $0.neighbor == nextVertex }).first { + edge.edgeRepresentation?.setCheckingColor() + } + } + + } + } + } else if self.graph.state == .interactiveVisualization { + if let vertexView = sender as? VertexView, let vertex = vertexView.vertex { + if vertex.visited { + return + } else { + self.graph.didTapVertex(vertex: vertex) + } + } + } + } + } + + private func setupEdgeRepresentations() { + var edgeQueue: [Vertex] = [graph.startVertex!] + + //BFS + while !edgeQueue.isEmpty { + let currentVertex = edgeQueue.first! + currentVertex.haveAllEdges = true + for edge in currentVertex.edges { + let neighbor = edge.neighbor + let weight = edge.weight + if !neighbor.haveAllEdges { + let edgeRepresentation = EdgeRepresentation(from: currentVertex, to: neighbor, weight: weight) + edge.edgeRepresentation = edgeRepresentation + let index = neighbor.edges.index(where: { $0.neighbor == currentVertex })! + neighbor.edges[index].edgeRepresentation = edgeRepresentation + edgeQueue.append(neighbor) + } + } + edgeQueue.removeFirst() + } + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/SimpleObjects/Edge.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/SimpleObjects/Edge.swift new file mode 100644 index 000000000..6f4493bbe --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/SimpleObjects/Edge.swift @@ -0,0 +1,12 @@ +import Foundation + +public class Edge { + public var neighbor: Vertex + public var weight: Double + public var edgeRepresentation: EdgeRepresentation? + + public init(vertex: Vertex, weight: Double) { + self.neighbor = vertex + self.weight = weight + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/SimpleObjects/Vertex.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/SimpleObjects/Vertex.swift new file mode 100644 index 000000000..f18ec3114 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/SimpleObjects/Vertex.swift @@ -0,0 +1,63 @@ +import UIKit + +public class Vertex { + private var graphColors: GraphColors = GraphColors.sharedInstance + + public var view: VertexView? + + public var identifier: String + public var edges: [Edge] = [] + public var pathVerticesFromStart: [Vertex] = [] + public var level: Int = 0 + public var levelChecked: Bool = false + public var haveAllEdges: Bool = false + public var visited: Bool = false + public var pathLengthFromStart: Double = Double.infinity { + didSet { + DispatchQueue.main.async { + self.view?.setPathLengthLabel(text: "\(self.pathLengthFromStart)") + } + } + } + + public init(identifier: String) { + self.identifier = identifier + } + + public func clearCache() { + pathLengthFromStart = Double.infinity + pathVerticesFromStart = [] + visited = false + } + + public func clearLevelInfo() { + level = 0 + levelChecked = false + } + + public func setVisitedColor() { + view?.backgroundColor = graphColors.visitedColor + view?.setLabelsTextColor(color: UIColor.white) + } + + public func setCheckingPathColor() { + view?.backgroundColor = graphColors.checkingColor + } + + public func setDefaultColor() { + view?.backgroundColor = graphColors.defaultVertexColor + view?.setLabelsTextColor(color: UIColor.black) + } +} + +extension Vertex: Hashable { + public var hashValue: Int { + return identifier.hashValue + } +} + +extension Vertex: Equatable { + public static func ==(lhs: Vertex, rhs: Vertex) -> Bool { + return lhs.hashValue == rhs.hashValue + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Window.swift b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Window.swift new file mode 100644 index 000000000..9a169b8c7 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/Sources/Window.swift @@ -0,0 +1,322 @@ +import Foundation +import UIKit + +public protocol GraphDelegate: class { + func willCompareVertices(startVertexPathLength: Double, edgePathLength: Double, endVertexPathLength: Double) + func didFinishCompare() + func didCompleteGraphParsing() + func didTapWrongVertex() + func didStop() + func willStartVertexNeighborsChecking() + func didFinishVertexNeighborsChecking() +} + +public class Window: UIView, GraphDelegate { + public var graphView: GraphView! + + private var topView: UIView! + private var createGraphButton: RoundedButton! + private var startVisualizationButton: RoundedButton! + private var startInteractiveVisualizationButton: RoundedButton! + private var startButton: UIButton! + private var pauseButton: UIButton! + private var stopButton: UIButton! + private var comparisonLabel: UILabel! + private var activityIndicator: UIActivityIndicatorView! + private var graph: Graph! + private var numberOfVertices: UInt! + private var graphColors = GraphColors.sharedInstance + + public override init(frame: CGRect) { + super.init(frame: frame) + self.frame = frame + backgroundColor = graphColors.mainWindowBackgroundColor + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func configure(graph: Graph) { + self.graph = graph + graph.createNewGraph() + graph.delegate = self + let frame = CGRect(x: 10, y: 170, width: self.frame.width - 20, height: self.frame.height - 180) + graphView = GraphView(frame: frame, graph: graph) + + + graphView.createNewGraph() + addSubview(graphView) + + configureCreateGraphButton() + configureStartVisualizationButton() + configureStartInteractiveVisualizationButton() + configureStartButton() + configurePauseButton() + configureStopButton() + configureComparisonLabel() + configureActivityIndicator() + + topView = UIView(frame: CGRect(x: 10, y: 10, width: frame.width - 20, height: 150)) + topView.backgroundColor = graphColors.topViewBackgroundColor + topView.layer.cornerRadius = 15 + addSubview(topView) + + topView.addSubview(createGraphButton) + topView.addSubview(startVisualizationButton) + topView.addSubview(startInteractiveVisualizationButton) + topView.addSubview(startButton) + topView.addSubview(pauseButton) + topView.addSubview(stopButton) + topView.addSubview(comparisonLabel) + topView.addSubview(activityIndicator) + } + + private func configureCreateGraphButton() { + let frame = CGRect(x: center.x - 200, y: 12, width: 100, height: 34) + createGraphButton = RoundedButton(frame: frame) + createGraphButton.setTitle("New graph", for: .normal) + createGraphButton.addTarget(self, action: #selector(createGraphButtonTap), for: .touchUpInside) + } + + private func configureStartVisualizationButton() { + let frame = CGRect(x: center.x - 50, y: 12, width: 100, height: 34) + startVisualizationButton = RoundedButton(frame: frame) + startVisualizationButton.setTitle("Auto", for: .normal) + startVisualizationButton.addTarget(self, action: #selector(startVisualizationButtonDidTap), for: .touchUpInside) + } + + private func configureStartInteractiveVisualizationButton() { + let frame = CGRect(x: center.x + 100, y: 12, width: 100, height: 34) + startInteractiveVisualizationButton = RoundedButton(frame: frame) + startInteractiveVisualizationButton.setTitle("Interactive", for: .normal) + startInteractiveVisualizationButton.addTarget(self, action: #selector(startInteractiveVisualizationButtonDidTap), for: .touchUpInside) + } + + private func configureStartButton() { + let frame = CGRect(x: center.x - 65, y: 56, width: 30, height: 30) + startButton = UIButton(frame: frame) + let playImage = UIImage(named: "Start.png") + startButton.setImage(playImage, for: .normal) + startButton.isEnabled = false + startButton.addTarget(self, action: #selector(didTapStartButton), for: .touchUpInside) + } + + private func configurePauseButton() { + let frame = CGRect(x: center.x - 15, y: 56, width: 30, height: 30) + pauseButton = UIButton(frame: frame) + let pauseImage = UIImage(named: "Pause.png") + pauseButton.setImage(pauseImage, for: .normal) + pauseButton.isEnabled = false + pauseButton.addTarget(self, action: #selector(didTapPauseButton), for: .touchUpInside) + } + + private func configureStopButton() { + let frame = CGRect(x: center.x + 35, y: 56, width: 30, height: 30) + stopButton = UIButton(frame: frame) + let stopImage = UIImage(named: "Stop.png") + stopButton.setImage(stopImage, for: .normal) + stopButton.isEnabled = false + stopButton.addTarget(self, action: #selector(didTapStopButton), for: .touchUpInside) + } + + private func configureComparisonLabel() { + let size = CGSize(width: 250, height: 42) + let origin = CGPoint(x: center.x - 125, y: 96) + let frame = CGRect(origin: origin, size: size) + comparisonLabel = UILabel(frame: frame) + comparisonLabel.textAlignment = .center + comparisonLabel.text = "Have fun!" + } + + private func configureActivityIndicator() { + let size = CGSize(width: 50, height: 42) + let origin = CGPoint(x: center.x - 25, y: 100) + let activityIndicatorFrame = CGRect(origin: origin, size: size) + activityIndicator = UIActivityIndicatorView(frame: activityIndicatorFrame) + activityIndicator.style = .whiteLarge + } + + @objc private func createGraphButtonTap() { + comparisonLabel.text = "" + graphView.removeGraph() + graph.removeGraph() + graph.createNewGraph() + graphView.createNewGraph() + graph.state = .initial + } + + @objc private func startVisualizationButtonDidTap() { + comparisonLabel.text = "" + pauseButton.isEnabled = true + stopButton.isEnabled = true + createGraphButton.isEnabled = false + startVisualizationButton.isEnabled = false + startInteractiveVisualizationButton.isEnabled = false + createGraphButton.alpha = 0.5 + startVisualizationButton.alpha = 0.5 + startInteractiveVisualizationButton.alpha = 0.5 + + if graph.state == .completed { + graphView.reset() + graph.reset() + } + graph.state = .autoVisualization + DispatchQueue.global(qos: .background).async { + self.graph.findShortestPathsWithVisualization { + self.graph.state = .completed + + DispatchQueue.main.async { + self.startButton.isEnabled = false + self.pauseButton.isEnabled = false + self.stopButton.isEnabled = false + self.createGraphButton.isEnabled = true + self.startVisualizationButton.isEnabled = true + self.startInteractiveVisualizationButton.isEnabled = true + self.createGraphButton.alpha = 1 + self.startVisualizationButton.alpha = 1 + self.startInteractiveVisualizationButton.alpha = 1 + self.comparisonLabel.text = "Completed!" + } + } + } + } + + @objc private func startInteractiveVisualizationButtonDidTap() { + comparisonLabel.text = "" + pauseButton.isEnabled = true + stopButton.isEnabled = true + createGraphButton.isEnabled = false + startVisualizationButton.isEnabled = false + startInteractiveVisualizationButton.isEnabled = false + createGraphButton.alpha = 0.5 + startVisualizationButton.alpha = 0.5 + startInteractiveVisualizationButton.alpha = 0.5 + + if graph.state == .completed { + graphView.reset() + graph.reset() + } + + guard let startVertex = graph.startVertex else { + assertionFailure("startVertex is nil") + return + } + startVertex.pathLengthFromStart = 0 + startVertex.pathVerticesFromStart.append(startVertex) + graph.state = .parsing + graph.parseNeighborsFor(vertex: startVertex) { + self.graph.state = .interactiveVisualization + DispatchQueue.main.async { + self.comparisonLabel.text = "Pick next vertex" + } + } + } + + @objc private func didTapStartButton() { + startButton.isEnabled = false + pauseButton.isEnabled = true + DispatchQueue.global(qos: .utility).async { + self.graph.pauseVisualization = false + } + } + + @objc private func didTapPauseButton() { + startButton.isEnabled = true + pauseButton.isEnabled = false + DispatchQueue.global(qos: .utility).async { + self.graph.pauseVisualization = true + } + } + + @objc private func didTapStopButton() { + startButton.isEnabled = false + pauseButton.isEnabled = false + comparisonLabel.text = "" + activityIndicator.startAnimating() + if graph.state == .parsing || graph.state == .autoVisualization { + graph.stopVisualization = true + } else if graph.state == .interactiveVisualization { + didStop() + } + } + + private func setButtonsToInitialState() { + createGraphButton.isEnabled = true + startVisualizationButton.isEnabled = true + startInteractiveVisualizationButton.isEnabled = true + startButton.isEnabled = false + pauseButton.isEnabled = false + stopButton.isEnabled = false + createGraphButton.alpha = 1 + startVisualizationButton.alpha = 1 + startInteractiveVisualizationButton.alpha = 1 + } + + private func showError(error: String) { + DispatchQueue.main.async { + let size = CGSize(width: 250, height: 42) + let origin = CGPoint(x: self.topView.center.x - 125, y: 96) + let frame = CGRect(origin: origin, size: size) + let errorView = ErrorView(frame: frame) + errorView.setText(text: error) + self.topView.addSubview(errorView) + UIView.animate(withDuration: 2, animations: { + errorView.alpha = 0 + }, completion: { _ in + errorView.removeFromSuperview() + }) + } + } + + // MARK: GraphDelegate + public func didCompleteGraphParsing() { + graph.state = .completed + setButtonsToInitialState() + comparisonLabel.text = "Completed!" + } + + public func didTapWrongVertex() { + if !subviews.contains { $0 is ErrorView } { + showError(error: "You have picked wrong next vertex") + } + } + + public func willCompareVertices(startVertexPathLength: Double, edgePathLength: Double, endVertexPathLength: Double) { + DispatchQueue.main.async { + if startVertexPathLength + edgePathLength < endVertexPathLength { + self.comparisonLabel.text = "\(startVertexPathLength) + \(edgePathLength) < \(endVertexPathLength) 👍" + } else { + self.comparisonLabel.text = "\(startVertexPathLength) + \(edgePathLength) >= \(endVertexPathLength) 👎" + } + } + } + + public func didFinishCompare() { + DispatchQueue.main.async { + self.comparisonLabel.text = "" + } + } + + public func didStop() { + graph.state = .initial + graph.stopVisualization = false + graph.pauseVisualization = false + graphView.reset() + graph.reset() + setButtonsToInitialState() + activityIndicator.stopAnimating() + } + + public func willStartVertexNeighborsChecking() { + DispatchQueue.main.async { + self.comparisonLabel.text = "" + } + } + + public func didFinishVertexNeighborsChecking() { + DispatchQueue.main.async { + self.comparisonLabel.text = "Pick next vertex" + } + } +} diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/contents.xcplayground b/Dijkstra Algorithm/VisualizedDijkstra.playground/contents.xcplayground new file mode 100644 index 000000000..35968656f --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Dijkstra Algorithm/VisualizedDijkstra.playground/playground.xcworkspace/contents.xcworkspacedata b/Dijkstra Algorithm/VisualizedDijkstra.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Dijkstra Algorithm/VisualizedDijkstra.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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/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 29b5916e9..aed9dfe59 100755 --- a/DiningPhilosophers/README.md +++ b/DiningPhilosophers/README.md @@ -1,62 +1,60 @@ # Dining Philosophers -Dining philosophers problem Algorithm implemented in Swift (concurrent algorithm design to illustrate synchronization issues and techniques for resolving them using GCD and Semaphore in Swift) +The dining philosophers problem Algorithm implemented in Swift (concurrent algorithm design to illustrate synchronization issues and techniques for resolving them using [GCD](https://apple.github.io/swift-corelibs-libdispatch/) and [Semaphore](https://developer.apple.com/reference/dispatch/dispatchsemaphore) in Swift) Written for Swift Algorithm Club by Jacopo Mangiavacchi # Introduction -In computer science, the dining philosophers problem is an example problem often used in concurrent algorithm design to illustrate synchronization issues and techniques for resolving them. +In computer science, the dining philosophers problem is often used in the concurrent algorithm design to illustrate synchronization issues and techniques for resolving them. It was originally formulated in 1965 by Edsger Dijkstra as a student exam exercise, presented in terms of computers competing for access to tape drive peripherals. Soon after, Tony Hoare gave the problem its present formulation. -This Swift implementation is based on the Chandy/Misra solution and it use GCD Dispatch and Semaphone on Swift cross platform +This Swift implementation is based on the Chandy/Misra solution, and it uses the GCD Dispatch and Semaphores on the Swift cross platform. # Problem statement Five silent philosophers sit at a round table with bowls of spaghetti. Forks are placed between each pair of adjacent philosophers. -Each philosopher must alternately think and eat. However, a philosopher can only eat spaghetti when they have both left and right forks. Each fork can be held by only one philosopher and so a philosopher can use the fork only if it is not being used by another philosopher. After an individual philosopher finishes eating, they need to put down both forks so that the forks become available to others. A philosopher can take the fork on their right or the one on their left as they become available, but cannot start eating before getting both forks. +Each philosopher must alternately think and eat. A philosopher can only eat spaghetti when they have both left and right forks. Since each fork can be held by only one philosopher, a philosopher can use the fork only if it is not being used by another philosopher. When a philosopher finishes eating, they need to put down both forks so that the forks become available to others. A philosopher can take the fork on their right or the one on their left as they become available, but they cannot start eating before getting both forks. Eating is not limited by the remaining amounts of spaghetti or stomach space; an infinite supply and an infinite demand are assumed. The problem is how to design a discipline of behavior (a concurrent algorithm) such that no philosopher will starve; i.e., each can forever continue to alternate between eating and thinking, assuming that no philosopher can know when others may want to eat or think. -This is an illustration of an dinining table: +This is an illustration of a dining table: ![Dining Philosophers table](https://upload.wikimedia.org/wikipedia/commons/7/7b/An_illustration_of_the_dining_philosophers_problem.png) - # Solution -There are different solutions for this classic algorithm and this Swift implementation is based on the on the Chandy/Misra solution that more generally allows for arbitrary agents to contend for an arbitrary number of resources in a completely distributed scenario with no needs for a central authority to control the locking and serialization of resources. - -However, it is very important to note here that this solution violates the requirement that "the philosophers do not speak to each other" (due to the request messages). +There are different solutions for this classic algorithm, and this Swift implementation is based on the Chandy/Misra solution. This implementation allows agents to contend for an arbitrary number of resources in a completely distributed scenario with no need for a central authority to control the locking and serialization of resources. +However, this solution violates the requirement that "the philosophers do not speak to each other" (due to the request messages). # Description For every pair of philosophers contending for a resource, create a fork and give it to the philosopher with the lower ID (n for agent Pn). Each fork can either be dirty or clean. Initially, all forks are dirty. -When a philosopher wants to use a set of resources (i.e. eat), said philosopher must obtain the forks from their contending neighbors. For all such forks the philosopher does not have, they send a request message. -When a philosopher with a fork receives a request message, they keep the fork if it is clean, but give it up when it is dirty. If the philosopher sends the fork over, they clean the fork before doing so. +When a philosopher wants to use a set of resources (i.e. eat), said philosopher must obtain the forks from their contending neighbors. The philospher send a message for all such forks needed. When a philosopher with a fork receives a request message, they keep the fork if it is clean, but give it up when it is dirty. If the philosopher sends the fork over, they clean the fork before doing so. After a philosopher is done eating, all their forks become dirty. If another philosopher had previously requested one of the forks, the philosopher that has just finished eating cleans the fork and sends it. -This solution also allows for a large degree of concurrency, and will solve an arbitrarily large problem. +This solution also allows for a large degree of concurrency, and it will solve an arbitrarily large problem. -It also solves the starvation problem. The clean / dirty labels act as a way of giving preference to the most "starved" processes, and a disadvantage to processes that have just "eaten". One could compare their solution to one where philosophers are not allowed to eat twice in a row without letting others use the forks in between. Chandy and Misra's solution is more flexible than that, but has an element tending in that direction. +In addition, it solves the starvation problem. The clean / dirty labels give a preference to the most "starved" processes and a disadvantage to processes that have just "eaten". One could compare their solution to one where philosophers are not allowed to eat twice in a row without letting others use the forks in between. The Chandy and Misra's solution is more flexible but has an element tending in that direction. -In their analysis they derive a system of preference levels from the distribution of the forks and their clean/dirty states. They show that this system may describe an acyclic graph, and if so, the operations in their protocol cannot turn that graph into a cyclic one. This guarantees that deadlock cannot occur. However, if the system is initialized to a perfectly symmetric state, like all philosophers holding their left side forks, then the graph is cyclic at the outset, and their solution cannot prevent a deadlock. Initializing the system so that philosophers with lower IDs have dirty forks ensures the graph is initially acyclic. +Based on the Chandy and Misra's analysis, a system of preference levels is derived from the distribution of the forks and their clean/dirty states. This system may describe an acyclic graph, and if so, the solution's protocol cannot turn that graph into a cyclic one. This guarantees that deadlock cannot occur. However, if the system is initialized to a perfectly symmetric state, such as all philosophers holding their left side forks, then the graph is cyclic at the outset, and the solution cannot prevent a deadlock. Initializing the system so that philosophers with lower IDs have dirty forks ensures the graph is initially acyclic. # Swift implementation -This Swift 3.0 implementation of the Chandy/Misra solution is based on GCD and Semaphore tecnique and it could 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. The ForkPair DispatchSemaphore static array is used for waking the neighbour Philosophers any time a fork pair is put down on the table. -A background DispatchQueue is then used to let any Philosopher run asyncrounosly on the background and a global DispatchSemaphore is simply used in order to keep the main thread on wait forever and let the Philosophers contienue forever in their alternate think and eat cycle. - +A background DispatchQueue is then used to let any Philosopher run asyncrounosly on the background, and a global DispatchSemaphore is used to keep the main thread on wait forever and let the Philosophers continue forever in their alternate think and eat cycle. + # See also 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 deff344b9..f473e76fb 100755 --- a/DiningPhilosophers/Sources/main.swift +++ b/DiningPhilosophers/Sources/main.swift @@ -6,25 +6,27 @@ // // +// last checked with Xcode 9.0b4 +#if swift(>=4.0) + print("Hello, Swift 4!") +#endif -import Foundation import Dispatch let numberOfPhilosophers = 4 struct ForkPair { - static let forksSemaphore:[DispatchSemaphore] = Array(repeating: DispatchSemaphore(value: 1), count: numberOfPhilosophers) - + static let forksSemaphore: [DispatchSemaphore] = Array(repeating: DispatchSemaphore(value: 1), count: numberOfPhilosophers) + let leftFork: DispatchSemaphore let rightFork: DispatchSemaphore - + init(leftIndex: Int, rightIndex: Int) { //Order forks by index to prevent deadlock if leftIndex > rightIndex { leftFork = ForkPair.forksSemaphore[leftIndex] rightFork = ForkPair.forksSemaphore[rightIndex] - } - else { + } else { leftFork = ForkPair.forksSemaphore[rightIndex] rightFork = ForkPair.forksSemaphore[leftIndex] } @@ -35,7 +37,7 @@ struct ForkPair { leftFork.wait() rightFork.wait() } - + func putDown() { //The order does not matter here leftFork.signal() @@ -43,65 +45,61 @@ struct ForkPair { } } - struct Philosophers { let forkPair: ForkPair let philosopherIndex: Int - + var leftIndex = -1 var rightIndex = -1 - + init(philosopherIndex: Int) { leftIndex = philosopherIndex rightIndex = philosopherIndex - 1 - + if rightIndex < 0 { rightIndex += numberOfPhilosophers } self.forkPair = ForkPair(leftIndex: leftIndex, rightIndex: rightIndex) self.philosopherIndex = philosopherIndex - + print("Philosopher: \(philosopherIndex) left: \(leftIndex) right: \(rightIndex)") } - + func run() { while true { - print("Acquiring lock for Philosofer: \(philosopherIndex) Left:\(leftIndex) Right:\(rightIndex)") + print("Acquiring lock for Philosopher: \(philosopherIndex) Left:\(leftIndex) Right:\(rightIndex)") forkPair.pickUp() print("Start Eating Philosopher: \(philosopherIndex)") //sleep(1000) - print("Releasing lock for Philosofer: \(philosopherIndex) Left:\(leftIndex) Right:\(rightIndex)") + print("Releasing lock for Philosopher: \(philosopherIndex) Left:\(leftIndex) Right:\(rightIndex)") forkPair.putDown() } } } - -// Layout of the table (P = philosopher, f = fork) for 4 Philosopher +// Layout of the table (P = philosopher, f = fork) for 4 Philosophers // P0 // f3 f0 // P3 P1 // f2 f1 // P2 - let globalSem = DispatchSemaphore(value: 0) for i in 0.. 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 f1bc63315..616f8443d 100644 --- a/Fixed Size Array/FixedSizeArray.playground/Contents.swift +++ b/Fixed Size Array/FixedSizeArray.playground/Contents.swift @@ -10,25 +10,25 @@ struct FixedSizeArray { private var defaultValue: T private var array: [T] private (set) var count = 0 - + init(maxSize: Int, defaultValue: T) { self.maxSize = maxSize self.defaultValue = defaultValue self.array = [T](repeating: defaultValue, count: maxSize) } - + subscript(index: Int) -> T { assert(index >= 0) assert(index < count) return array[index] } - + mutating func append(_ newElement: T) { assert(count < maxSize) array[count] = newElement count += 1 } - + mutating func removeAt(index: Int) -> T { assert(index >= 0) assert(index < count) @@ -38,7 +38,7 @@ struct FixedSizeArray { array[count] = defaultValue return result } - + mutating func removeAll() { for i in 0..= 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/Contents.swift b/Graph/Graph.playground/Contents.swift index 77de8484b..fde396e64 100644 --- a/Graph/Graph.playground/Contents.swift +++ b/Graph/Graph.playground/Contents.swift @@ -1,7 +1,7 @@ import Graph for graph in [AdjacencyMatrixGraph(), AdjacencyListGraph()] { - + let v1 = graph.createVertex(1) let v2 = graph.createVertex(2) let v3 = graph.createVertex(3) 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 82a5f2377..790f6ac18 100644 --- a/Graph/Graph.xcodeproj/project.pbxproj +++ b/Graph/Graph.xcodeproj/project.pbxproj @@ -158,15 +158,16 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 49BFA2FC1CDF886B00522D66 = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; 49BFA3061CDF886B00522D66 = { CreatedOnToolsVersion = 7.3; + LastSwiftMigration = 0820; }; }; }; @@ -246,14 +247,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; @@ -282,6 +291,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -296,14 +306,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; @@ -325,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 = ""; }; @@ -348,7 +367,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -369,7 +389,8 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.Graph"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -381,6 +402,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.GraphTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -392,6 +414,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.swift-algorithm-club.GraphTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Graph/Graph.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme b/Graph/Graph.xcodeproj/xcshareddata/xcschemes/Graph.xcscheme index c4750d846..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/AdjacencyListGraph.swift b/Graph/Graph/AdjacencyListGraph.swift index e5e9fc2db..a24e732e7 100644 --- a/Graph/Graph/AdjacencyListGraph.swift +++ b/Graph/Graph/AdjacencyListGraph.swift @@ -7,12 +7,10 @@ import Foundation - - -private class EdgeList where T: Equatable, T: Hashable { +private class EdgeList where T: Hashable { var vertex: Vertex - var edges: [Edge]? = nil + var edges: [Edge]? init(vertex: Vertex) { self.vertex = vertex @@ -24,7 +22,7 @@ private class EdgeList where T: Equatable, T: Hashable { } -open class AdjacencyListGraph: AbstractGraph where T: Equatable, T: Hashable { +open class AdjacencyListGraph: AbstractGraph where T: Hashable { fileprivate var adjacencyList: [EdgeList] = [] @@ -37,34 +35,30 @@ open class AdjacencyListGraph: AbstractGraph where T: Equatable, T: Hashab } open override var vertices: [Vertex] { - get { - var vertices = [Vertex]() - for edgeList in adjacencyList { - vertices.append(edgeList.vertex) - } - return vertices + var vertices = [Vertex]() + for edgeList in adjacencyList { + vertices.append(edgeList.vertex) } + return vertices } open override var edges: [Edge] { - get { - var allEdges = Set>() - for edgeList in adjacencyList { - guard let edges = edgeList.edges else { - continue - } + var allEdges = Set>() + for edgeList in adjacencyList { + guard let edges = edgeList.edges else { + continue + } - for edge in edges { - allEdges.insert(edge) - } + for edge in edges { + allEdges.insert(edge) } - return Array(allEdges) } + return Array(allEdges) } open override func createVertex(_ data: T) -> Vertex { // check if the vertex already exists - let matchingVertices = vertices.filter() { vertex in + let matchingVertices = vertices.filter { vertex in return vertex.data == data } @@ -82,7 +76,7 @@ open class AdjacencyListGraph: AbstractGraph where T: Equatable, T: Hashab // works let edge = Edge(from: from, to: to, weight: weight) let edgeList = adjacencyList[from.index] - if let _ = edgeList.edges { + if edgeList.edges != nil { edgeList.addEdge(edge) } else { edgeList.edges = [edge] @@ -94,7 +88,6 @@ open class AdjacencyListGraph: AbstractGraph where T: Equatable, T: Hashab addDirectedEdge(vertices.1, to: vertices.0, withWeight: weight) } - open override func weightFrom(_ sourceVertex: Vertex, to destinationVertex: Vertex) -> Double? { guard let edges = adjacencyList[sourceVertex.index].edges else { return nil @@ -114,27 +107,25 @@ open class AdjacencyListGraph: AbstractGraph where T: Equatable, T: Hashab } open override var description: String { - get { - var rows = [String]() - for edgeList in adjacencyList { + var rows = [String]() + for edgeList in adjacencyList { - guard let edges = edgeList.edges else { - continue - } + guard let edges = edgeList.edges else { + continue + } - var row = [String]() - for edge in edges { - var value = "\(edge.to.data)" - if edge.weight != nil { - value = "(\(value): \(edge.weight!))" - } - row.append(value) + var row = [String]() + for edge in edges { + var value = "\(edge.to.data)" + if edge.weight != nil { + value = "(\(value): \(edge.weight!))" } - - rows.append("\(edgeList.vertex.data) -> [\(row.joined(separator: ", "))]") + row.append(value) } - return rows.joined(separator: "\n") + rows.append("\(edgeList.vertex.data) -> [\(row.joined(separator: ", "))]") } + + return rows.joined(separator: "\n") } } diff --git a/Graph/Graph/AdjacencyMatrixGraph.swift b/Graph/Graph/AdjacencyMatrixGraph.swift index 8f357bc8c..4d3ae9731 100644 --- a/Graph/Graph/AdjacencyMatrixGraph.swift +++ b/Graph/Graph/AdjacencyMatrixGraph.swift @@ -7,7 +7,7 @@ import Foundation -open class AdjacencyMatrixGraph: AbstractGraph where T: Equatable, T: Hashable { +open class AdjacencyMatrixGraph: AbstractGraph where T: Hashable { // If adjacencyMatrix[i][j] is not nil, then there is an edge from // vertex i to vertex j. @@ -23,30 +23,26 @@ open class AdjacencyMatrixGraph: AbstractGraph where T: Equatable, T: Hash } open override var vertices: [Vertex] { - get { - return _vertices - } + return _vertices } open override var edges: [Edge] { - get { - var edges = [Edge]() - for row in 0 ..< adjacencyMatrix.count { - for column in 0 ..< adjacencyMatrix.count { - if let weight = adjacencyMatrix[row][column] { - edges.append(Edge(from: vertices[row], to: vertices[column], weight: weight)) - } + var edges = [Edge]() + for row in 0 ..< adjacencyMatrix.count { + for column in 0 ..< adjacencyMatrix.count { + if let weight = adjacencyMatrix[row][column] { + edges.append(Edge(from: vertices[row], to: vertices[column], weight: weight)) } } - return edges } + return edges } // Adds a new vertex to the matrix. // Performance: possibly O(n^2) because of the resizing of the matrix. open override func createVertex(_ data: T) -> Vertex { // check if the vertex already exists - let matchingVertices = vertices.filter() { vertex in + let matchingVertices = vertices.filter { vertex in return vertex.data == data } @@ -96,23 +92,21 @@ open class AdjacencyMatrixGraph: AbstractGraph where T: Equatable, T: Hash } open override var description: String { - get { - var grid = [String]() - let n = self.adjacencyMatrix.count - for i in 0..: Equatable where T: Equatable, T: Hashable { +public struct Edge: Equatable where T: Hashable { public let from: Vertex public let to: Vertex @@ -19,25 +19,21 @@ public struct Edge: Equatable where T: Equatable, T: Hashable { extension Edge: CustomStringConvertible { public var description: String { - get { - guard let unwrappedWeight = weight else { - return "\(from.description) -> \(to.description)" - } - return "\(from.description) -(\(unwrappedWeight))-> \(to.description)" + guard let unwrappedWeight = weight else { + return "\(from.description) -> \(to.description)" } + return "\(from.description) -(\(unwrappedWeight))-> \(to.description)" } } extension Edge: Hashable { - public var hashValue: Int { - get { - var string = "\(from.description)\(to.description)" - if weight != nil { - string.append("\(weight!)") - } - return string.hashValue + public func hash(into hasher: inout Hasher) { + hasher.combine(from) + hasher.combine(to) + if weight != nil { + hasher.combine(weight) } } diff --git a/Graph/Graph/Graph.swift b/Graph/Graph/Graph.swift index 2ad91608b..ddae4eddd 100644 --- a/Graph/Graph/Graph.swift +++ b/Graph/Graph/Graph.swift @@ -7,7 +7,7 @@ import Foundation -open class AbstractGraph: CustomStringConvertible where T: Equatable, T: Hashable { +open class AbstractGraph: CustomStringConvertible where T: Hashable { public required init() {} @@ -21,21 +21,15 @@ open class AbstractGraph: CustomStringConvertible where T: Equatable, T: Hash } open var description: String { - get { - fatalError("abstract property accessed") - } + fatalError("abstract property accessed") } open var vertices: [Vertex] { - get { - fatalError("abstract property accessed") - } + fatalError("abstract property accessed") } open var edges: [Edge] { - get { - fatalError("abstract property accessed") - } + fatalError("abstract property accessed") } // Adds a new vertex to the matrix. diff --git a/Graph/Graph/Vertex.swift b/Graph/Graph/Vertex.swift index 43cabcd1f..48645f0fa 100644 --- a/Graph/Graph/Vertex.swift +++ b/Graph/Graph/Vertex.swift @@ -7,7 +7,7 @@ import Foundation -public struct Vertex: Equatable where T: Equatable, T: Hashable { +public struct Vertex: Equatable where T: Hashable { public var data: T public let index: Int @@ -17,24 +17,21 @@ public struct Vertex: Equatable where T: Equatable, T: Hashable { extension Vertex: CustomStringConvertible { public var description: String { - get { - return "\(index): \(data)" - } + return "\(index): \(data)" } } extension Vertex: Hashable { - public var hashValue: Int { - get { - return "\(data)\(index)".hashValue - } + public func hasher(into hasher: inout Hasher) { + hasher.combine(data) + hasher.combine(index) } } -public func ==(lhs: Vertex, rhs: Vertex) -> Bool { +public func ==(lhs: Vertex, rhs: Vertex) -> Bool { guard lhs.index == rhs.index else { return false } diff --git a/Graph/GraphTests/GraphTests.swift b/Graph/GraphTests/GraphTests.swift index 766ce91cf..289d9aa64 100644 --- a/Graph/GraphTests/GraphTests.swift +++ b/Graph/GraphTests/GraphTests.swift @@ -54,7 +54,7 @@ class GraphTests: XCTestCase { } } - func testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedGraphWithType(graphType: AbstractGraph.Type) { + func testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedGraphWithType(_ graphType: AbstractGraph.Type) { let graph = graphType.init() let a = graph.createVertex(1) @@ -71,7 +71,7 @@ class GraphTests: XCTestCase { XCTAssertEqual(edgesFromA.first?.to, b) } - func testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedGraphWithType(graphType: AbstractGraph.Type) { + func testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedGraphWithType(_ graphType: AbstractGraph.Type) { let graph = graphType.init() let a = graph.createVertex(1) @@ -89,7 +89,7 @@ class GraphTests: XCTestCase { XCTAssertEqual(edgesFromB.first?.to, a) } - func testEdgesFromReturnsNoEdgesInNoEdgeGraphWithType(graphType: AbstractGraph.Type) { + func testEdgesFromReturnsNoEdgesInNoEdgeGraphWithType(_ graphType: AbstractGraph.Type) { let graph = graphType.init() let a = graph.createVertex(1) @@ -99,7 +99,7 @@ class GraphTests: XCTestCase { XCTAssertEqual(graph.edgesFrom(b).count, 0) } - func testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedGraphWithType(graphType: AbstractGraph.Type) { + func testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedGraphWithType(_ graphType: AbstractGraph.Type) { let graph = graphType.init() let verticesCount = 100 var vertices: [Vertex] = [] @@ -125,34 +125,34 @@ class GraphTests: XCTestCase { } func testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedMatrixGraph() { - testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedGraphWithType(AdjacencyMatrixGraph) + testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedGraphWithType(AdjacencyMatrixGraph.self) } func testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedMatrixGraph() { - testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedGraphWithType(AdjacencyMatrixGraph) + testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedGraphWithType(AdjacencyMatrixGraph.self) } func testEdgesFromReturnsNoInNoEdgeMatrixGraph() { - testEdgesFromReturnsNoEdgesInNoEdgeGraphWithType(AdjacencyMatrixGraph) + testEdgesFromReturnsNoEdgesInNoEdgeGraphWithType(AdjacencyMatrixGraph.self) } func testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedMatrixGraph() { - testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedGraphWithType(AdjacencyMatrixGraph) + testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedGraphWithType(AdjacencyMatrixGraph.self) } func testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedListGraph() { - testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedGraphWithType(AdjacencyListGraph) + testEdgesFromReturnsCorrectEdgeInSingleEdgeDirecedGraphWithType(AdjacencyListGraph.self) } func testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedListGraph() { - testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedGraphWithType(AdjacencyListGraph) + testEdgesFromReturnsCorrectEdgeInSingleEdgeUndirectedGraphWithType(AdjacencyListGraph.self) } func testEdgesFromReturnsNoInNoEdgeListGraph() { - testEdgesFromReturnsNoEdgesInNoEdgeGraphWithType(AdjacencyListGraph) + testEdgesFromReturnsNoEdgesInNoEdgeGraphWithType(AdjacencyListGraph.self) } func testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedListGraph() { - testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedGraphWithType(AdjacencyListGraph) + testEdgesFromReturnsCorrectEdgesInBiggerGraphInDirectedGraphWithType(AdjacencyListGraph.self) } } diff --git a/Graph/README.markdown b/Graph/README.markdown index 1ff1a82ac..1331a07d3 100644 --- a/Graph/README.markdown +++ b/Graph/README.markdown @@ -1,26 +1,29 @@ # Graph -A graph is something that looks like this: +> 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) -In computer-science lingo, a graph is a set of *vertices* paired with a set of *edges*. The vertices are the round things and the edges are the lines between them. Edges connect a vertex to other vertices. +In computer science, a graph is defined as a set of *vertices* paired with a set of *edges*. The vertices are represented by circles, and the edges are the lines between them. Edges connect a vertex to other vertices. -> **Note:** Vertices are sometimes also called "nodes" and edges are also called "links". +> **Note:** Vertices are sometimes called "nodes", and edges are called "links". -For example, a graph can represent a social network. Each person is a vertex, and people who know each other are connected by edges. A somewhat historically inaccurate example: +A graph can represent a social network. Each person is a vertex, and people who know each other are connected by edges. Here is a somewhat historically inaccurate example: ![Social network](Images/SocialNetwork.png) -Graphs come in all kinds of shapes and sizes. The edges can have a *weight*, where a positive or negative numeric value is assigned to each edge. Consider an example of a graph representing airplane flights. Cities can be vertices, and flights can be edges. Then an edge weight could describe flight time or the price of a ticket. +Graphs have various shapes and sizes. The edges can have a *weight*, where a positive or negative numeric value is assigned to each edge. Consider an example of a graph representing airplane flights. Cities can be vertices, and flights can be edges. Then, an edge weight could describe flight time or the price of a ticket. ![Airplane flights](Images/Flights.png) With this hypothetical airline, flying from San Francisco to Moscow is cheapest by going through New York. -Edges can also be *directed*. So far the edges you've seen have been undirected, so if Ada knows Charles, then Charles also knows Ada. A directed edge, on the other hand, implies a one-way relationship. A directed edge from vertex X to vertex Y connects X to Y, but *not* Y to X. +Edges can also be *directed*. In examples mentioned above, the edges are undirected. For instance, if Ada knows Charles, then Charles also knows Ada. A directed edge, on the other hand, implies a one-way relationship. A directed edge from vertex X to vertex Y connects X to Y, but *not* Y to X. -Continuing from the flights example, a directed edge from San Francisco to Juneau in Alaska would indicate that there is a flight from San Francisco to Juneau, but not from Juneau to San Francisco (I suppose that means you're walking back). +Continuing from the flights example, a directed edge from San Francisco to Juneau in Alaska indicates that there is a flight from San Francisco to Juneau, but not from Juneau to San Francisco (I suppose that means you're walking back). ![One-way flights](Images/FlightsDirected.png) @@ -28,51 +31,51 @@ The following are also graphs: ![Tree and linked list](Images/TreeAndList.png) -On the left is a [tree](../Tree/) structure, on the right a [linked list](../Linked List/). Both can be considered graphs, but in a simpler form. After all, they have vertices (nodes) and edges (links). +On the left is a [tree](../Tree/) structure, on the right a [linked list](../Linked%20List/). They can be considered graphs but in a simpler form. They both have vertices (nodes) and edges (links). -The very first graph I showed you contained *cycles*, where you can start off at a vertex, follow a path, and come back to the original vertex. A tree is a graph without such cycles. +The first graph includes *cycles*, where you can start off at a vertex, follow a path, and come back to the original vertex. A tree is a graph without such cycles. -Another very common type of graph is the *directed acyclic graph* or DAG: +Another common type of graph is the *directed acyclic graph* or DAG: ![DAG](Images/DAG.png) -Like a tree this does not have any cycles in it (no matter where you start, there is no path back to the starting vertex), but unlike a tree the edges are directional and the shape doesn't necessarily form a hierarchy. +Like a tree, this graph does not have any cycles (no matter where you start, there is no path back to the starting vertex), but this graph has directional edges with the shape that does not necessarily form a hierarchy. ## Why use graphs? -Maybe you're shrugging your shoulders and thinking, what's the big deal? Well, it turns out that graphs are an extremely useful data structure. +Maybe you're shrugging your shoulders and thinking, what's the big deal? Well, it turns out that a graph is a useful data structure. -If you have some programming problem where you can represent some of your data as vertices and some of it as edges between those vertices, then you can draw your problem as a graph and use well-known graph algorithms such as [breadth-first search](../Breadth-First Search/) or [depth-first search](../Depth-First Search) to find a solution. +If you have a programming problem where you can represent your data as vertices and edges, then you can draw your problem as a graph and use well-known graph algorithms such as [breadth-first search](../Breadth-First%20Search/) or [depth-first search](../Depth-First%20Search) to find a solution. -For example, let's say you have a list of tasks where some tasks have to wait on others before they can begin. You can model this using an acyclic directed graph: +For example, suppose you have a list of tasks where some tasks have to wait on others before they can begin. You can model this using an acyclic directed graph: ![Tasks as a graph](Images/Tasks.png) -Each vertex represents a task. Here, an edge between two vertices means that the source task must be completed before the destination task can start. So task C cannot start before B and D are finished, and B nor D can start before A is finished. +Each vertex represents a task. An edge between two vertices means the source task must be completed before the destination task can start. As an example, task C cannot start before B and D are finished, and B nor D can start before A is finished. -Now that the problem is expressed using a graph, you can use a depth-first search to perform a [topological sort](../Topological Sort/). This will put the tasks in an optimal order so that you minimize the time spent waiting for tasks to complete. (One possible order here is A, B, D, E, C, F, G, H, I, J, K.) +Now that the problem is expressed using a graph, you can use a depth-first search to perform a [topological sort](../Topological%20Sort/). This will put the tasks in an optimal order so that you minimize the time spent waiting for tasks to complete. (One possible order here is A, B, D, E, C, F, G, H, I, J, K.) -Whenever you're faced with a tough programming problem, ask yourself, "how can I express this problem using a graph?" Graphs are all about representing relationships between your data. The trick is in how you define "relationship". +Whenever you are faced with a tough programming problem, ask yourself, "how can I express this problem using a graph?" Graphs are all about representing relationships between your data. The trick is in how you define "relationships". -If you're a musician you might appreciate this graph: +If you are a musician you might appreciate this graph: ![Chord map](Images/ChordMap.png) -The vertices are chords from the C major scale. The edges -- the relationships between the chords -- represent how [likely one chord is to follow another](http://mugglinworks.com/chordmaps/genmap.htm). This is a directed graph, so the direction of the arrows shows how you can go from one chord to the next. It's also a weighted graph, where the weight of the edges -- portrayed here by line thickness -- shows a strong relationship between two chords. As you can see, a G7-chord is very likely to be followed by a C chord, and much less likely by a Am chord. +The vertices are chords from the C major scale. The edges -- the relationships between the chords -- represent how [likely one chord is to follow another](http://mugglinworks.com/chordmaps/genmap.htm). This is a directed graph, so the direction of the arrows shows how you can go from one chord to the next. It is also a weighted graph, where the weight of the edges -- portrayed here by line thickness -- shows a strong relationship between two chords. As you can see, a G7-chord is very likely to be followed by a C chord, and much less likely by a Am chord. -You're probably already using graphs without even knowing it. Your data model is also a graph (from Apple's Core Data documentation): +You are probably already using graphs without even knowing it. Your data model is also a graph (from Apple's Core Data documentation): ![Core Data model](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreDataVersioning/Art/recipe_version2.0.jpg) -Another common graph that's used by programmers is the state machine, where edges depict the conditions for transitioning between states. Here is a state machine that models my cat: +Another common graph used by programmers is the state machine, where edges depict the conditions for transitioning between states. Here is a state machine that models my cat: ![State machine](Images/StateMachine.png) -Graphs are awesome. Facebook made a fortune from their social graph. If you're going to learn any data structure, it should be the graph and the vast collection of standard graph algorithms. +Graphs are awesome. Facebook made a fortune from their social graph. If you are going to learn any data structure, you must choose the graph and the vast collection of standard graph algorithms. ## Vertices and edges, oh my! -In theory, a graph is just a bunch of vertex objects and a bunch of edge objects. But how do you describe these in code? +In theory, a graph is just a bunch of vertex and edge objects, but how do you describe this in code? There are two main strategies: adjacency list and adjacency matrix. @@ -80,9 +83,9 @@ There are two main strategies: adjacency list and adjacency matrix. ![Adjacency list](Images/AdjacencyList.png) -The adjacency list describes outgoing edges. A has an edge to B but B does not have an edge back to A, so A does not appear in B's adjacency list. Finding an edge or weight between two vertices can be expensive because there is no random access to edges–you must traverse the adjacency lists until it is found. +The adjacency list describes outgoing edges. A has an edge to B, but B does not have an edge back to A, so A does not appear in B's adjacency list. Finding an edge or weight between two vertices can be expensive because there is no random access to edges. You must traverse the adjacency lists until it is found. -**Adjacency Matrix.** In an adjacency matrix implementation, a matrix with rows and columns representing vertices stores a weight to indicate if vertices are connected, and by what weight. For example, if there is a directed edge of weight 5.6 from vertex A to vertex B, then the entry with row for vertex A, column for vertex B would have value 5.6: +**Adjacency Matrix.** In an adjacency matrix implementation, a matrix with rows and columns representing vertices stores a weight to indicate if vertices are connected and by what weight. For example, if there is a directed edge of weight 5.6 from vertex A to vertex B, then the entry with row for vertex A and column for vertex B would have the value 5.6: ![Adjacency matrix](Images/AdjacencyMatrix.png) @@ -103,7 +106,7 @@ Let *V* be the number of vertices in the graph, and *E* the number of edges. Th In the case of a *sparse* graph, where each vertex is connected to only a handful of other vertices, an adjacency list is the best way to store the edges. If the graph is *dense*, where each vertex is connected to most of the other vertices, then a matrix is preferred. -We'll show you sample implementations of both adjacency list and adjacency matrix. +Here are sample implementations of both adjacency list and adjacency matrix: ## The code: edges and vertices @@ -137,13 +140,13 @@ It stores arbitrary data with a generic type `T`, which is `Hashable` to enforce ## The code: graphs -> **Note:** There are many, many ways to implement graphs. The code given here is just one possible implementation. You probably want to tailor the graph code to each individual problem you're trying to solve. For instance, your edges may not need a `weight` property, or you may not have the need to distinguish between directed and undirected edges. +> **Note:** There are many ways to implement graphs. The code given here is just one possible implementation. You probably want to tailor the graph code to each individual problem you are trying to solve. For instance, your edges may not need a `weight` property, or you may not have the need to distinguish between directed and undirected edges. -Here's an example of a very simple graph: +Here is an example of a simple graph: ![Demo](Images/Demo1.png) -We can represent it as an adjacency matrix or adjacency list. The classes implementing those concept both inherit a common API from `AbstractGraph`, so they can be created in an identical fashion, with different optimized data structures behind the scenes. +We can represent it as an adjacency matrix or adjacency list. The classes implementing those concepts both inherit a common API from `AbstractGraph`, so they can be created in an identical fashion, with different optimized data structures behind the scenes. Let's create some directed, weighted graphs, using each representation, to store the example: @@ -165,7 +168,7 @@ for graph in [AdjacencyMatrixGraph(), AdjacencyListGraph()] { } ``` -As mentioned earlier, to create an undirected edge you need to make two directed edges. If we wanted undirected graphs, we'd call this method instead, which takes care of that work for us: +As mentioned earlier, to create an undirected edge you need to make two directed edges. For undirected graphs, we call the following method instead: ```swift graph.addUndirectedEdge(v1, to: v2, withWeight: 1.0) @@ -198,7 +201,7 @@ private class EdgeList where T: Equatable, T: Hashable { } ``` -They are implemented as a class as opposed to structs so we can modify them by reference, in place, like when adding an edge to a new vertex, where the source vertex already has an edge list: +They are implemented as a class as opposed to structs, so we can modify them by reference, in place, like when adding an edge to a new vertex, where the source vertex already has an edge list: ```swift open override func createVertex(_ data: T) -> Vertex { @@ -231,7 +234,7 @@ where the general form `a -> [(b: w), ...]` means an edge exists from `a` to `b` ## The code: adjacency matrix -We'll keep track of the adjacency matrix in a two-dimensional `[[Double?]]` array. An entry of `nil` indicates no edge, while any other value indicates an edge of the given weight. If `adjacencyMatrix[i][j]` is not nil, then there is an edge from vertex `i` to vertex `j`. +We will keep track of the adjacency matrix in a two-dimensional `[[Double?]]` array. An entry of `nil` indicates no edge, while any other value indicates an edge of the given weight. If `adjacencyMatrix[i][j]` is not nil, then there is an edge from vertex `i` to vertex `j`. To index into the matrix using vertices, we use the `index` property in `Vertex`, which is assigned when creating the vertex through the graph object. When creating a new vertex, the graph must resize the matrix: @@ -277,6 +280,6 @@ Then the adjacency matrix looks like this: ## See also -This article described what a graph is and how you can implement the basic data structure. But we have many more articles on practical uses for graphs, so check those out too! +This article described what a graph is, and how you can implement the basic data structure. We have other articles on practical uses of graphs, so check those out too! *Written by Donald Pinckney and Matthijs Hollemans* diff --git a/Hash Set/HashSet.playground/Sources/HashSet.swift b/Hash Set/HashSet.playground/Sources/HashSet.swift index c3365110b..3ccaa94ea 100644 --- a/Hash Set/HashSet.playground/Sources/HashSet.swift +++ b/Hash Set/HashSet.playground/Sources/HashSet.swift @@ -2,31 +2,31 @@ public struct HashSet { fileprivate var dictionary = Dictionary() - + public init() { - + } - + public mutating func insert(_ element: T) { dictionary[element] = true } - + public mutating func remove(_ element: T) { dictionary[element] = nil } - + public func contains(_ element: T) -> Bool { return dictionary[element] != nil } - + public func allElements() -> [T] { return Array(dictionary.keys) } - + public var count: Int { return dictionary.count } - + public var isEmpty: Bool { return dictionary.isEmpty } 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 Set/README.markdown b/Hash Set/README.markdown index 368920a1c..fb829c313 100644 --- a/Hash Set/README.markdown +++ b/Hash Set/README.markdown @@ -196,7 +196,7 @@ difference2.allElements() // [5, 6] If you look at the [documentation](http://swiftdoc.org/v2.1/type/Set/) for Swift's own `Set`, you'll notice it has tons more functionality. An obvious extension would be to make `HashSet` conform to `SequenceType` so that you can iterate it with a `for`...`in` loop. -Another thing you could do is replace the `Dictionary` with an actual [hash table](../Hash Table), but one that just stores the keys and doesn't associate them with anything. So you wouldn't need the `Bool` values anymore. +Another thing you could do is replace the `Dictionary` with an actual [hash table](../Hash%20Table), but one that just stores the keys and doesn't associate them with anything. So you wouldn't need the `Bool` values anymore. If you often need to look up whether an element belongs to a set and perform unions, then the [union-find](../Union-Find/) data structure may be more suitable. It uses a tree structure instead of a dictionary to make the find and union operations very efficient. diff --git a/Hash Table/HashTable.playground/Sources/HashTable.swift b/Hash Table/HashTable.playground/Sources/HashTable.swift index 947737c32..f2a1be040 100644 --- a/Hash Table/HashTable.playground/Sources/HashTable.swift +++ b/Hash Table/HashTable.playground/Sources/HashTable.swift @@ -30,34 +30,17 @@ 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] - + /// The number of key-value pairs in the hash table. private(set) public var count = 0 - + /// 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. */ @@ -65,7 +48,7 @@ public struct HashTable: CustomStringConvertible { assert(capacity > 0) buckets = Array(repeatElement([], count: capacity)) } - + /** Accesses the value associated with the given key for reading and writing. @@ -82,7 +65,7 @@ public struct HashTable: CustomStringConvertible { } } } - + /** Returns the value for the given key. */ @@ -95,14 +78,14 @@ public struct HashTable: CustomStringConvertible { } return nil // key not in hash table } - + /** Updates the value stored in the hash table for the given key, or adds a new key-value pair if the key does not exist. */ @discardableResult public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { let index = self.index(forKey: key) - + // Do we already have this key in the bucket? for (i, element) in buckets[index].enumerated() { if element.key == key { @@ -111,20 +94,20 @@ public struct HashTable: CustomStringConvertible { return oldValue } } - + // This key isn't in the bucket yet; add it to the chain. buckets[index].append((key: key, value: value)) count += 1 return nil } - + /** Removes the given key and its associated value from the hash table. */ @discardableResult public mutating func removeValue(forKey key: Key) -> Value? { let index = self.index(forKey: key) - + // Find the element in the bucket's chain and remove it. for (i, element) in buckets[index].enumerated() { if element.key == key { @@ -135,7 +118,7 @@ public struct HashTable: CustomStringConvertible { } return nil // key not in hash table } - + /** Removes all key-value pairs from the hash table. */ @@ -143,11 +126,30 @@ public struct HashTable: CustomStringConvertible { buckets = Array(repeatElement([], count: buckets.count)) count = 0 } - + /** 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 4399b0f86..fb0bda788 100644 --- a/Hash Table/README.markdown +++ b/Hash Table/README.markdown @@ -2,13 +2,13 @@ A hash table allows you to store and retrieve objects by a "key". -Also called dictionary, map, associative array. There are other ways to implement these, such as with a tree or even a plain array, but hash table is the most common. +A hash table is used to implement structures, such as a dictionary, a map, and an associative array. These structures can be implemented by a tree or a plain array, but it is efficient to use a hash table. -This should explain why Swift's built-in `Dictionary` type requires that keys conform to the `Hashable` protocol: internally it uses a hash table, just like the one you'll learn about here. +This should explain why Swift's built-in `Dictionary` type requires that keys conform to the `Hashable` protocol: internally it uses a hash table, like the one you will learn about here. ## How it works -At its most basic, a hash table is nothing more than an array. Initially, this array is empty. When you put a value into the hash table under a certain key, it uses that key to calculate an index in the array, like so: +A hash table is nothing more than an array. Initially, this array is empty. When you put a value into the hash table under a certain key, it uses that key to calculate an index in the array. Here is an example: ```swift hashTable["firstName"] = "Steve" @@ -48,33 +48,33 @@ hashTable["hobbies"] = "Programming Swift" +--------------+ ``` -The trick is in how the hash table calculates those array indices. That's where the hashing comes in. When you write, +The trick is how the hash table calculates those array indices. That is where the hashing comes in. When you write the following statement, ```swift hashTable["firstName"] = "Steve" ``` -the hash table takes the key `"firstName"` and asks it for its `hashValue` property. That's why keys must be `Hashable`. +the hash table takes the key `"firstName"` and asks it for its `hashValue` property. Hence, keys must be `Hashable`. -When you do `"firstName".hashValue`, it returns a big integer: -4799450059917011053. Likewise, `"hobbies".hashValue` has the hash value 4799450060928805186. (The values you see may vary.) +When you write `"firstName".hashValue`, it returns a big integer: -4799450059917011053. Likewise, `"hobbies".hashValue` has the hash value 4799450060928805186. (The values you see may vary.) -Of course, these numbers are way too big to be used as indices into our array. One of them is even negative! A common way to make these big numbers more suitable is to first make the hash positive and then take the modulo with the array size. +These numbers are big to be used as indices into our array, and one of them is even negative! A common way to make these big numbers suitable is to first make the hash positive and then take the modulo with the array size. -Our array has size 5, so the index for the `"firstName"` key becomes `abs(-4799450059917011053) % 5 = 3`. You can calculate for yourself that the array index for `"hobbies"` is 1. +Our array has size 5, so the index for the `"firstName"` key becomes `abs(-4799450059917011053) % 5 = 3`. You can calculate that the array index for `"hobbies"` is 1. -Using hashes in this manner is what makes the dictionary so efficient: to find an element in the hash table you only have to hash the key to get an array index and then look up the element in the underlying array. All these operations take a constant amount of time, so inserting, retrieving, and removing are all **O(1)**. +Using hashes in this manner is what makes the dictionary efficient: to find an element in the hash table, you must hash the key to get an array index and then look up the element in the underlying array. All these operations take a constant amount of time, so inserting, retrieving, and removing are all **O(1)**. -> **Note:** As you can see, it's hard to predict where in the array your objects end up. That's why dictionaries do not guarantee any particular order of the elements in the hash table. +> **Note:** It is difficult to predict where in the array your objects end up. Hence, dictionaries do not guarantee any particular order of the elements in the hash table. ## Avoiding collisions There is one problem: because we take the modulo of the hash value with the size of the array, it can happen that two or more keys get assigned the same array index. This is called a collision. -One way to avoid collisions is to have a very large array. That reduces the likelihood of two keys mapping to the same index. Another trick is to use a prime number for the array size. However, collisions are bound to occur so you need some way to handle them. +One way to avoid collisions is to have a large array which reduces the likelihood of two keys mapping to the same index. Another trick is to use a prime number for the array size. However, collisions are bound to occur, so you need to find a way to handle them. -Because our table is so small it's easy to show a collision. For example, the array index for the key `"lastName"` is also 3. That's a problem, as we don't want to overwrite the value that's already at this array index. +Because our table is small, it is easy to show a collision. For example, the array index for the key `"lastName"` is also 3, but we do not want to overwrite the value that is already at this array index. -There are a few ways to handle collisions. A common one is to use chaining. The array now looks as follows: +A common way to handle collisions is to use chaining. The array looks as follows: ```swift buckets: @@ -91,25 +91,25 @@ There are a few ways to handle collisions. A common one is to use chaining. The +-----+ ``` -With chaining, keys and their values are not stored directly in the array. Instead, each array element is really a list of zero or more key/value pairs. The array elements are usually called the *buckets* and the lists are called the *chains*. So here we have 5 buckets and two of these buckets have chains. The other three buckets are empty. +With chaining, keys and their values are not stored directly in the array. Instead, each array element is a list of zero or more key/value pairs. The array elements are usually called the *buckets* and the lists are called the *chains*. Here we have 5 buckets, and two of these buckets have chains. The other three buckets are empty. -If we now write the following to retrieve an item from the hash table, +If we write the following statement to retrieve an item from the hash table, ```swift let x = hashTable["lastName"] ``` -then this first hashes the key `"lastName"` to calculate the array index, which is 3. Bucket 3 has a chain, so we step through that list to find the value with the key `"lastName"`. That is done by comparing the keys, so here that involves a string comparison. The hash table sees that this key belongs to the last item in the chain and returns the corresponding value, `"Jobs"`. +it first hashes the key `"lastName"` to calculate the array index, which is 3. Since bucket 3 has a chain, we step through the list to find the value with the key `"lastName"`. This is done by comparing the keys using a string comparison. The hash table checks that the key belongs to the last item in the chain and returns the corresponding value, `"Jobs"`. -Common ways to implement this chaining mechanism are to use a linked list or another array. Technically speaking the order of the items in the chain doesn't matter, so you also can think of it as a set instead of a list. (Now you can also imagine where the term "bucket" comes from; we just dump all the objects together into the bucket.) +Common ways to implement this chaining mechanism are to use a linked list or another array. Since the order of the items in the chain does not matter, you can think of it as a set instead of a list. (Now you can also imagine where the term "bucket" comes from; we just dump all the objects together into the bucket.) -It's important that chains do not become too long or looking up items in the hash table becomes really slow. Ideally, we would have no chains at all but in practice it is impossible to avoid collisions. You can improve the odds by giving the hash table enough buckets and by using high-quality hash functions. +Chains should not become long because looking up items in the hash table would become a slow process. Ideally, we would have no chains at all, but in practice it is impossible to avoid collisions. You can improve the odds by giving the hash table enough buckets using high-quality hash functions. -> **Note:** An alternative to chaining is "open addressing". The idea is this: if an array index is already taken, we put the element in the next unused bucket. Of course, this approach has its own upsides and downsides. +> **Note:** An alternative to chaining is "open addressing". The idea is this: if an array index is already taken, we put the element in the next unused bucket. This approach has its own upsides and downsides. ## The code -Let's look at a basic implementation of a hash table in Swift. We'll build it up step-by-step. +Let's look at a basic implementation of a hash table in Swift. We will build it up step-by-step. ```swift public struct HashTable { @@ -127,9 +127,9 @@ public struct HashTable { } ``` -The `HashTable` is a generic container and the two generic types are named `Key` (which must be `Hashable`) and `Value`. We also define two other types: `Element` is a key/value pair for use in a chain and `Bucket` is an array of such `Elements`. +The `HashTable` is a generic container, and the two generic types are named `Key` (which must be `Hashable`) and `Value`. We also define two other types: `Element` is a key/value pair for using in a chain, and `Bucket` is an array of such `Elements`. -The main array is named `buckets`. It has a fixed size, the so-called capacity, provided by the `init(capacity)` method. We're also keeping track of how many items have been added to the hash table using the `count` variable. +The main array is named `buckets`. It has a fixed size, the so-called capacity, provided by the `init(capacity)` method. We are also keeping track of how many items have been added to the hash table using the `count` variable. An example of how to create a new hash table object: @@ -137,17 +137,17 @@ An example of how to create a new hash table object: var hashTable = HashTable(capacity: 5) ``` -Currently the hash table doesn't do anything yet, so let's add the remaining functionality. First, add a helper method that calculates the array index for a given key: +The hash table does not do anything yet, so let's add the remaining functionality. First, add a helper method that calculates the array index for a given key: ```swift private func index(forKey key: Key) -> Int { - return abs(key.hashValue) % buckets.count + return abs(key.hashValue % buckets.count) } ``` -This performs the calculation you saw earlier: it takes the absolute value of the key's `hashValue` modulo the size of the buckets array. We've put this in a function of its own because it gets used in a few different places. +This performs the calculation you saw earlier: it takes the absolute value of the key's `hashValue` modulo the size of the buckets array. We have put this in a function of its own because it gets used in a few different places. -There are four common things you'll do with a hash table or dictionary: +There are four common things you will do with a hash table or dictionary: - insert a new element - look up an element @@ -180,7 +180,7 @@ We can do all these things with a `subscript` function: } ``` -This calls three helper functions to do the actual work. Let's take a look at `value(forKey:)` first, which retrieves an object from the hash table. +This calls three helper functions to do the actual work. Let's take a look at `value(forKey:)`which retrieves an object from the hash table. ```swift public func value(forKey key: Key) -> Value? { @@ -193,17 +193,16 @@ This calls three helper functions to do the actual work. Let's take a look at `v return nil // key not in hash table } ``` +First it calls `index(forKey:)` to convert the key into an array index. That gives us the bucket number, but this bucket may be used by more than one key if there were collisions. The `value(forKey:)` loops through the chain from that bucket and compares the keys one-by-one. If found, it returns the corresponding value, otherwise it returns `nil`. -First it calls `index(forKey:)` to convert the key into an array index. That gives us the bucket number, but if there were collisions this bucket may be used by more than one key. So `value(forKey:)` loops through the chain from that bucket and compares the keys one-by-one. If found, it returns the corresponding value, otherwise it returns `nil`. - -The code to insert a new element or update an existing element lives in `updateValue(_:forKey:)`. It's a little bit more complicated: +The code to insert a new element or update an existing element lives in `updateValue(_:forKey:)`. This is more complicated: ```swift public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { let index = self.index(forKey: key) // Do we already have this key in the bucket? - for (i, element) in buckets[index].enumerate() { + for (i, element) in buckets[index].enumerated() { if element.key == key { let oldValue = element.value buckets[index][i].value = value @@ -218,9 +217,9 @@ The code to insert a new element or update an existing element lives in `updateV } ``` -Again, the first thing we do is convert the key into an array index to find the bucket. Then we loop through the chain for that bucket. If we find the key in the chain, it means we must update it with the new value. If the key is not in the chain, we insert the new key/value pair to the end of the chain. +Again, the first step is to convert the key into an array index to find the bucket. Then we loop through the chain for that bucket. If we find the key in the chain, we must update it with the new value. If the key is not in the chain, we insert the new key/value pair to the end of the chain. -As you can see, it's important that chains are kept short (by making the hash table large enough). Otherwise, you spend a lot of time in these `for`...`in` loops and the performance of the hash table will no longer be **O(1)** but more like **O(n)**. +As you can see, it is important to keep the chains short (by making the hash table large enough). Otherwise, you spend excessive time in these `for`...`in` loops and the performance of the hash table will no longer be **O(1)** but more like **O(n)**. Removing is similar in that again it loops through the chain: @@ -246,16 +245,16 @@ Try this stuff out in a playground. It should work just like a standard Swift `D ## Resizing the hash table -This version of `HashTable` always uses an array of a fixed size or capacity. That's fine if you've got a good idea of many items you'll be storing in the hash table. For the capacity, choose a prime number that is greater than the maximum number of items you expect to store and you're good to go. +This version of `HashTable` always uses an array of a fixed size or capacity. If you have many items to store in the hash table, for the capacity, choose a prime number greater than the maximum number of items. The *load factor* of a hash table is the percentage of the capacity that is currently used. If there are 3 items in a hash table with 5 buckets, then the load factor is `3/5 = 60%`. -If the hash table is too small and the chains are long, the load factor can become greater than 1. That's not a good idea. +If the hash table is small, and the chains are long, the load factor can become greater than 1, that is not a good idea. -If the load factor becomes too high, say > 75%, you can resize the hash table. Adding the code for this is left as an exercise for the reader. Keep in mind that making the buckets array larger will change the array indices that the keys map to! To account for this, you'll have to insert all the elements again after resizing the array. +If the load factor becomes high, greater than 75%, you can resize the hash table. Adding the code for this condition is left as an exercise for the reader. Keep in mind that making the buckets array larger will change the array indices that the keys map to! This requires you to insert all the elements again after resizing the array. ## Where to go from here? -`HashTable` is quite basic. It might be fun to integrate it better with the Swift standard library by making it a `SequenceType`, for example. +`HashTable` is quite basic. It might be efficient to integrate it with the Swift standard library by making it a `SequenceType`. *Written for Swift Algorithm Club by Matthijs Hollemans* 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 f46d7c394..ed89b4462 100644 --- a/HaversineDistance/HaversineDistance.playground/Contents.swift +++ b/HaversineDistance/HaversineDistance.playground/Contents.swift @@ -1,7 +1,6 @@ - import UIKit -func haversineDinstance(la1: Double, lo1: Double, la2: Double, lo2: Double, radius: Double = 6367444.7) -> Double { +func haversineDistance(la1: Double, lo1: Double, la2: Double, lo2: Double, radius: Double = 6367444.7) -> Double { let haversin = { (angle: Double) -> Double in return (1 - cos(angle))/2 @@ -13,7 +12,7 @@ func haversineDinstance(la1: Double, lo1: Double, la2: Double, lo2: Double, radi // 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) @@ -28,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 5f047f82b..5bec58626 100644 --- a/Heap Sort/README.markdown +++ b/Heap Sort/README.markdown @@ -40,21 +40,21 @@ And fix up the heap to make it valid max-heap again: As you can see, the largest items are making their way to the back. We repeat this process until we arrive at the root node and then the whole array is sorted. -> **Note:** This process is very similar to [selection sort](../Selection Sort/), 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. +> **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: ```swift 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) - } - return elements + 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) } + return elements + } } ``` @@ -71,9 +71,9 @@ We can write a handy helper function for that: ```swift 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) - return h.sort() + let reverseOrder = { i1, i2 in sort(i2, i1) } + var h = Heap(array: a, sort: reverseOrder) + return h.sort() } ``` 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 } + guard i < nodes.count else { return } - assert(isOrderedBefore(value, elements[i])) - elements[i] = value - shiftUp(i) + 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 } + @discardableResult public mutating func remove(at index: Int) -> T? { + guard index < nodes.count else { return nil } - let size = elements.count - 1 + 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 - } - - mutating func shiftDown() { - shiftDown(0, heapSize: elements.count) + + nodes[childIndex] = child } - + /** * 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 88ea25eeb..fee881129 --- a/Heap/README.markdown +++ b/Heap/README.markdown @@ -1,19 +1,21 @@ # Heap -A heap is a [binary tree](../Binary Tree/) that lives inside an array, so it doesn't use parent/child pointers. The tree is partially sorted according to something called the "heap property" that determines the order of the nodes in the tree. +> 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: -- For building [priority queues](../Priority Queue/). -- The heap is the data structure supporting [heap sort](../Heap Sort/). -- Heaps are fast for when you often need to compute the minimum (or maximum) element of a collection. -- Impressing your non-programmer friends. +- To build [priority queues](../Priority%20Queue/). +- To support [heap sorts](../Heap%20Sort/). +- To compute the minimum (or maximum) element of a collection quickly. +- To impress your non-programmer friends. ## The heap property -There are two kinds of heaps: a *max-heap* and a *min-heap*. They are identical, except that the order in which they store the tree nodes is opposite. +There are two kinds of heaps: a *max-heap* and a *min-heap* which are different by the order in which they store the tree nodes. -In a max-heap, parent nodes must always have a greater value than each of their children. For a min-heap it's the other way around: every parent node has a smaller value than its child nodes. This is called the "heap property" and it is true for every single node in the tree. +In a max-heap, parent nodes have a greater value than each of their children. In a min-heap, every parent node has a smaller value than its child nodes. This is called the "heap property", and it is true for every single node in the tree. An example: @@ -21,40 +23,41 @@ An example: This is a max-heap because every parent node is greater than its children. `(10)` is greater than `(7)` and `(2)`. `(7)` is greater than `(5)` and `(1)`. -As a result of this heap property, a max-heap always stores its largest item at the root of the tree. For a min-heap, the root is always the smallest item in the tree. That is very useful because heaps are often used as a [priority queue](../Priority Queue/) where you want to quickly access the "most important" element. +As a result of this heap property, a max-heap always stores its largest item at the root of the tree. For a min-heap, the root is always the smallest item in the tree. The heap property is useful because heaps are often used as a [priority queue](../Priority%20Queue/) to access the "most important" element quickly. + +> **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. -> **Note:** You can't really say anything else about the sort order of the heap. For example, in a max-heap the maximum element is always at index 0 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 compare to regular trees? -## How does heap compare to regular trees? +A heap is not a replacement for a binary search tree, and there are similarities and differences between them. Here are some main differences: -A heap isn't intended to be a replacement for a binary search tree. But there are many similarities between the two and also some differences. Here are some of the bigger differences: -**Order of the nodes.** In a [binary search tree (BST)](../Binary Search Tree/), the left child must always be smaller than its parent and the right child must be greater. This is not true for a heap. In max-heap both children must be smaller; in a min-heap they both must be greater. +**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. **Memory.** Traditional trees take up more memory than just the data they store. You need to allocate additional storage for the node objects and pointers to the left/right child nodes. A heap only uses a plain array for storage and uses no pointers. -**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 Tree/) or [red-black tree](../Red-Black Tree/). But with heaps we don't actually need the entire tree to be sorted. We just want the heap property to be fulfilled, and so balancing isn't an issue. Because of the way the heap is structured, heaps can guarantee **O(log n)** performance. +**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.** Searching a binary tree is really fast -- that's its whole purpose. In a heap, searching is slow. The purpose of a heap is to always put the largest (or smallest) node at the front, and to allow relatively fast inserts and deletes. Searching isn't a top priority. +**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 that lived in an array +## The tree inside an array -An array may seem like an odd way to implement a tree-like structure but it is very efficient in both time and space. +An array may seem like an odd way to implement a tree-like structure, but it is efficient in both time and space. -This is how we're going to store the tree from the above example: +This is how we are going to store the tree from the above example: [ 10, 7, 2, 5, 1 ] That's all there is to it! We don't need any more storage than just this simple array. -So how do we know which nodes are the parents and which are the children if we're not allowed to use any pointers? Good question! There is a well-defined relationship between the array index of a tree node and the array indices of its parent and children. +So how do we know which nodes are the parents and which are the children if we are not allowed to use any pointers? Good question! There is a well-defined relationship between the array index of a tree node and the array indices of its parent and children. If `i` is the index of a node, then the following formulas give the array indices of its parent and child nodes: parent(i) = floor((i - 1)/2) left(i) = 2i + 1 right(i) = 2i + 2 - + Note that `right(i)` is simply `left(i) + 1`. The left and right nodes are always stored right next to each other. Let's use these formulas on the example. Fill in the array index and we should get the positions of the parent and child nodes in the array: @@ -63,73 +66,73 @@ Let's use these formulas on the example. Fill in the array index and we should g |------|-------------|--------------|------------|-------------| | 10 | 0 | -1 | 1 | 2 | | 7 | 1 | 0 | 3 | 4 | -| 2 | 2 | 0 | 5 | 6 | +| 2 | 2 | 0 | 5 | 6 | | 5 | 3 | 1 | 7 | 8 | | 1 | 4 | 1 | 9 | 10 | Verify for yourself that these array indices indeed correspond to the picture of the tree. -> **Note:** The root node `(10)` doesn't have a parent because `-1` is not a valid array index. Likewise, nodes `(2)`, `(5)`, and `(1)` don't have children because those indices are greater than the array size. So we always have to make sure the indices we calculate are actually valid before we use them. +> **Note:** The root node `(10)` does not have a parent because `-1` is not a valid array index. Likewise, nodes `(2)`, `(5)`, and `(1)` do not have children because those indices are greater than the array size, so we always have to make sure the indices we calculate are actually valid before we use them. Recall that in a max-heap, the parent's value is always greater than (or equal to) the values of its children. This means the following must be true for all array indices `i`: ```swift array[parent(i)] >= array[i] ``` - + Verify that this heap property holds for the array from the example heap. -As you can see, these equations let us find the parent or child index for any node without the need for pointers. True, it's slightly more complicated than just dereferencing a pointer but that's 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's important to understand this relationship between array index and position in the tree. Here's a slightly larger heap, this tree has 15 nodes divided over four levels: +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: ![Large heap](Images/LargeHeap.png) -The numbers in this picture aren't the values of the nodes but the array indices that store the nodes! Those array indices correspond to the different levels of the tree like this: +The numbers in this picture are not the values of the nodes but the array indices that store the nodes! Here is the array indices correspond to the different levels of the tree: ![The heap array](Images/Array.png) -For the formulas to work, parent nodes must always appear before child nodes in the array. You can see that in the above picture. +For the formulas to work, parent nodes must appear before child nodes in the array. You can see that in the above picture. Note that this scheme has limitations. You can do the following with a regular binary tree but not with a heap: ![Impossible with a heap](Images/RegularTree.png) -You can’t start a new level unless the current lowest level is completely full. So heaps always have this kind of shape: +You can not start a new level unless the current lowest level is completely full, so heaps always have this kind of shape: ![The shape of a heap](Images/HeapShape.png) -> **Note:** Technically speaking you *could* emulate a regular binary tree with a heap, but it would waste a lot of space and you’d need some way to mark array indices as being empty. +> **Note:** You *could* emulate a regular binary tree with a heap, but it would be a waste of space, and you would need to mark array indices as being empty. Pop quiz! Let's say we have the array: [ 10, 14, 25, 33, 81, 82, 99 ] - + Is this a valid heap? The answer is yes! A sorted array from low-to-high is a valid min-heap. We can draw this heap as follows: ![A sorted array is a valid heap](Images/SortedArray.png) The heap property holds for each node because a parent is always smaller than its children. (Verify for yourself that an array sorted from high-to-low is always a valid max-heap.) -> **Note:** But not every min-heap is necessarily a sorted array! It only works one way... To turn a heap back into a sorted array, you need to use [heap sort](../Heap Sort/). +> **Note:** But not every min-heap is necessarily a sorted array! It only works one way. To turn a heap back into a sorted array, you need to use [heap sort](../Heap%20Sort/). ## More math! -In case you are curious, here are a few more formulas that describe certain properties of a heap. You don't need to know these by heart but they come in handy sometimes. Feel free to skip this section! +In case you are curious, here are a few more formulas that describe certain properties of a heap. You do not need to know these by heart, but they come in handy sometimes. Feel free to skip this section! -The *height* of a tree is defined as the number of steps it takes to go from the root node to the lowest leaf node. Or more formally: the height is the maximum number of edges between the nodes. A heap of height *h* has *h + 1* levels. +The *height* of a tree is defined as the number of steps it takes to go from the root node to the lowest leaf node, or more formally: the height is the maximum number of edges between the nodes. A heap of height *h* has *h + 1* levels. -This heap has height 3 and therefore has 4 levels: +This heap has height 3, so it has 4 levels: ![Large heap](Images/LargeHeap.png) -A heap with *n* nodes has height *h = floor(log_2(n))*. This is because we always fill up the lowest level completely before we add a new level. The example has 15 nodes, so the height is `floor(log_2(15)) = floor(3.91) = 3`. +A heap with *n* nodes has height *h = floor(log2(n))*. This is because we always fill up the lowest level completely before we add a new level. The example has 15 nodes, so the height is `floor(log2(15)) = floor(3.91) = 3`. If the lowest level is completely full, then that level contains *2^h* nodes. The rest of the tree above it contains *2^h - 1* nodes. Fill in the numbers from the example: the lowest level has 8 nodes, which indeed is `2^3 = 8`. The first three levels contain a total of 7 nodes, i.e. `2^3 - 1 = 8 - 1 = 7`. The total number of nodes *n* in the entire heap is therefore *2^(h+1) - 1*. In the example, `2^4 - 1 = 16 - 1 = 15`. -There are at most *ceil(n/2^(h+1))* nodes of height *h* in an *n*-element heap. +There are at most *ceil(n/2^(h+1))* nodes of height *h* in an *n*-element heap. The leaf nodes are always located at array indices *floor(n/2)* to *n-1*. We will make use of this fact to quickly build up the heap from an array. Verify this for the example if you don't believe it. ;-) @@ -141,35 +144,35 @@ There are two primitive operations necessary to make sure the heap is a valid ma - `shiftUp()`: If the element is greater (max-heap) or smaller (min-heap) than its parent, it needs to be swapped with the parent. This makes it move up the tree. -- `shiftDown()`. If the element is smaller (max-heap) or greater (min-heap) than its children, it needs to move down the tree. This operation is also called "heapify". +- `shiftDown()`. If the element is smaller (max-heap) or greater (min-heap) than its children, it needs to move down the tree. This operation is also called "heapify". Shifting up or down is a recursive procedure that takes **O(log n)** time. -The other operations are built on these primitives. They are: +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. -- `remove()`: Removes and returns the maximum value (max-heap) or the minimum value (min-heap). To fill up the hole that's left by removing the element, the very last element is moved to the root position and then `shiftDown()` fixes up the heap. (This is sometimes called "extract min" or "extract max".) +- `remove()`: Removes and returns the maximum value (max-heap) or the minimum value (min-heap). To fill up the hole left by removing the element, the very last element is moved to the root position and then `shiftDown()` fixes up the heap. (This is sometimes called "extract min" or "extract max".) -- `removeAtIndex(index)`: Just like `remove()` except it lets you remove any item from the heap, not just the root. This calls both `shiftDown()`, in case the new element is out-of-order with its children, and `shiftUp()`, in case the element is out-of-order with its parents. +- `removeAtIndex(index)`: Just like `remove()` with the exception that it allows you to remove any item from the heap, not just the root. This calls both `shiftDown()`, in case the new element is out-of-order with its children, and `shiftUp()`, in case the element is out-of-order with its parents. -- `replace(index, value)`: Assigns a smaller (min-heap) or larger (max-heap) value to a node. Because this invalidates the heap property, it uses `shiftUp()` to patch things up. (Also called "decrease key" and "increase key".) +- `replace(index, value)`: Assigns a smaller (min-heap) or larger (max-heap) value to a node. Because this invalidates the heap property, it uses `shiftUp()` to patch things up. (Also called "decrease key" and "increase key".) -All of the above take time **O(log n)** because shifting up or down is the most expensive thing they do. There are also a few operations that take more time: +All of the above take time **O(log n)** because shifting up or down is expensive. There are also a few operations that take more time: -- `search(value)`. Heaps aren't built for efficient searches but the `replace()` and `removeAtIndex()` operations require the array index of the node, so you need to find that index somehow. Time: **O(n)**. +- `search(value)`. Heaps are not built for efficient searches, but the `replace()` and `removeAtIndex()` operations require the array index of the node, so you need to find that index. Time: **O(n)**. -- `buildHeap(array)`: Converts an (unsorted) array into a heap by repeatedly calling `insert()`. If you’re smart about this, it can be done in **O(n)** time. +- `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 Sort/). Since the heap is really 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)**. -> **Note:** By far the most common things you'll do with a heap are inserting new values with `insert()` and removing the maximum or minimum value with `remove()`. Both take **O(log n)** time. The other operations exist to support more advanced usage, such as building a priority queue where the "importance" of items can change after they've been added to the queue. +> **Note:** By far the most common things you will do with a heap are inserting new values with `insert()` and removing the maximum or minimum value with `remove()`. Both take **O(log n)** time. The other operations exist to support more advanced usage, such as building a priority queue where the "importance" of items can change after they have been added to the queue. ## Inserting into the heap -Let's go through an example insertion to see in more detail how this works. We'll insert the value `16` into this heap: +Let's go through an example of insertion to see in details how this works. We will insert the value `16` into this heap: ![The heap before insertion](Images/Heap1.png) @@ -185,13 +188,13 @@ This corresponds to the following tree: 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.) +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're going to swap `(16)` and `(2)`. +To restore the heap property, we swap `(16)` and `(2)`. ![The heap before insertion](Images/Insert2.png) -We're not done yet because `(10)` is also smaller than `(16)`. We keep swapping our inserted value with its parent, until the parent is larger or we reach the top of the tree. This is called **shift-up** or **sifting** and is done after every insertion. It makes a number that is too large or too small "float up" the tree. +We are not done yet because `(10)` is also smaller than `(16)`. We keep swapping our inserted value with its parent, until the parent is larger or we reach the top of the tree. This is called **shift-up** or **sifting** and is done after every insertion. It makes a number that is too large or too small "float up" the tree. Finally, we get: @@ -199,7 +202,7 @@ Finally, we get: And now every parent is greater than its children again. -The time required for shifting up is proportional to the height of the tree so it takes **O(log n)** time. (The time it takes to append the node to the end of the array is only **O(1)**, so that doesn't slow it down.) +The time required for shifting up is proportional to the height of the tree, so it takes **O(log n)** time. (The time it takes to append the node to the end of the array is only **O(1)**, so that does not slow it down.) ## Removing the root @@ -211,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'll do the opposite: we're going to 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) @@ -219,19 +222,19 @@ Let's look at how to **shift-down** `(1)`. To maintain the heap property for thi ![The last node goes to the root](Images/Remove3.png) -Keep shifting down until the node doesn't have any children or it is larger than both its children. For our heap we only need one more swap to restore the heap property: +Keep shifting down until the node does not have any children or it is larger than both its children. For our heap, we only need one more swap to restore the heap property: ![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 so it 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. ## Removing any node -The vast majority of the time you'll be removing the object at the root of the heap because that's what heaps are designed for. +The vast majority of the time you will be removing the object at the root of the heap because that is what heaps are designed for. -However, it can be useful to remove an arbitrary element. This is a more general version of `remove()` and may involve either `shiftDown()` or `shiftUp()`. +However, it can be useful to remove an arbitrary element. This is a general version of `remove()` and may involve either `shiftDown()` or `shiftUp()`. Let's take the example tree again and remove `(7)`: @@ -241,13 +244,13 @@ As a reminder, the array is: [ 10, 7, 2, 5, 1 ] -As you know, removing an element could potentially invalidate the max-heap or min-heap property. To fix this, we swap the node that we're removing with the last element: +As you know, removing an element could potentially invalidate the max-heap or min-heap property. To fix this, we swap the node that we are removing with the last element: [ 10, 1, 2, 5, 7 ] -The last element is the one that we'll return; we'll call `removeLast()` to remove it from the heap. The `(1)` is now out-of-order because it's smaller than its child, `(5)`, but sits higher in the tree. We call `shiftDown()` to repair this. +The last element is the one that we will return; we will call `removeLast()` to remove it from the heap. The `(1)` is now out-of-order because it is smaller than its child, `(5)` but sits higher in the tree. We call `shiftDown()` to repair this. -However, shifting down is not the only situation we need to handle -- it may also happen that the new element must be shifted up. Consider what happens if you remove `(5)` from the following heap: +However, shifting down is not the only situation we need to handle. It may also happen that the new element must be shifted up. Consider what happens if you remove `(5)` from the following heap: ![We need to shift up](Images/Remove5.png) @@ -267,16 +270,16 @@ In code it would look like this: } ``` -We simply call `insert()` for each of the values in the array. Simple enough, but not very efficient. This takes **O(n log n)** time in total because there are **n** elements and each insertion takes **log n** time. +We simply call `insert()` for each of the values in the array. Simple enough but not very efficient. This takes **O(n log n)** time in total because there are **n** elements and each insertion takes **log n** time. -If you didn't gloss over the math section, you'd have seen that for any heap the elements at array indices *n/2* to *n-1* are the leaves of the tree. We can simply skip those leaves. We only have to process the other nodes, since they are parents with one or more children and therefore may be in the wrong order. +If you didn't gloss over the math section, you'd have seen that for any heap the elements at array indices *n/2* to *n-1* are the leaves of the tree. We can simply skip those leaves. We only have to process the other nodes, since they are parents with one or more children and therefore may be in the wrong order. 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) } } @@ -286,9 +289,9 @@ Here, `elements` is the heap's own array. We walk backwards through this array, ## Searching the heap -Heaps aren't made for fast searches, but if you want to remove an arbitrary element using `removeAtIndex()` or change the value of an element with `replace()`, then you need to obtain the index of that element somehow. Searching is one way to do this but it's kind of slow. +Heaps are not made for fast searches, but if you want to remove an arbitrary element using `removeAtIndex()` or change the value of an element with `replace()`, then you need to obtain the index of that element. Searching is one way to do this, but it is slow. -In a [binary search tree](../Binary Search Tree/) you can depend on the order of the nodes to guarantee a fast search. A heap orders its nodes differently and so a binary search won't work. You'll potentially have to look at every node in the tree. +In a [binary search tree](../Binary%20Search%20Tree/), depending on the order of the nodes, a fast search can be guaranteed. Since a heap orders its nodes differently, a binary search will not work, and you need to check every node in the tree. Let's take our example heap again: @@ -296,21 +299,21 @@ Let's take our example heap again: If we want to search for the index of node `(1)`, we could just step through the array `[ 10, 7, 2, 5, 1 ]` with a linear search. -But even though the heap property wasn't conceived with searching in mind, we can still take advantage of it. We know that in a max-heap a parent node is always larger than its children, so we can ignore those children (and their children, and so on...) if the parent is already smaller than the value we're looking for. +Even though the heap property was not conceived with searching in mind, we can still take advantage of it. We know that in a max-heap a parent node is always larger than its children, so we can ignore those children (and their children, and so on...) if the parent is already smaller than the value we are looking for. -Let's say we want to see if the heap contains the value `8` (it doesn't). We start at the root `(10)`. This is obviously not what we're looking for, so we recursively look at its left and right child. The left child is `(7)`. That is also not what we want, but since this is a max-heap, we know there's no point in looking at the children of `(7)`. They will always be smaller than `7` and are therefore never equal to `8`. Likewise for the right child, `(2)`. +Let's say we want to see if the heap contains the value `8` (it doesn't). We start at the root `(10)`. This is not what we are looking for, so we recursively look at its left and right child. The left child is `(7)`. That is also not what we want, but since this is a max-heap, we know there is no point in looking at the children of `(7)`. They will always be smaller than `7` and are therefore never equal to `8`; likewise, for the right child, `(2)`. Despite this small optimization, searching is still an **O(n)** operation. -> **Note:** There is away to turn lookups into a **O(1)** operation by keeping an additional dictionary that maps node values to indices. This may be worth doing if you often need to call `replace()` to change the "priority" of objects in a [priority queue](../Priority Queue/) that's built on a heap. +> **Note:** There is a way to turn lookups into a **O(1)** operation by keeping an additional dictionary that maps node values to indices. This may be worth doing if you often need to call `replace()` to change the "priority" of objects in a [priority queue](../Priority%20Queue/) that's built on a heap. ## The code See [Heap.swift](Heap.swift) for the implementation of these concepts in Swift. Most of the code is quite straightforward. The only tricky bits are in `shiftUp()` and `shiftDown()`. -You've seen that there are two types of heaps: a max-heap and a min-heap. The only difference between them is in how they order their nodes: largest value first or smallest value first. +You have seen that there are two types of heaps: a max-heap and a min-heap. The only difference between them is in how they order their nodes: largest value first or smallest value first. -Rather than create two different versions, `MaxHeap` and `MinHeap`, there is just one `Heap` object and it takes an `isOrderedBefore` closure. This closure contains the logic that determines the order of two values. You've probably seen this before because it's also how Swift's `sort()` works. +Rather than create two different versions, `MaxHeap` and `MinHeap`, there is just one `Heap` object and it takes an `isOrderedBefore` closure. This closure contains the logic that determines the order of two values. You have probably seen this before because it is also how Swift's `sort()` works. To make a max-heap of integers, you write: diff --git a/Heap/Tests/HeapTests.swift b/Heap/Tests/HeapTests.swift old mode 100644 new mode 100755 index c5b479f14..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(index: 10) + let v = h.remove(at: 10) XCTAssertEqual(v, nil) XCTAssertTrue(verifyMaxHeap(h)) - XCTAssertEqual(h.elements, [100, 50, 70, 10, 20, 60, 65]) + XCTAssertEqual(h.nodes, [100, 50, 70, 10, 20, 60, 65]) - let v1 = h.removeAt(index: 5) + 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(index: 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(index: 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(index: 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(index: 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 9319e2089..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 = 0800; + 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 @@ Scheme -> Manage Schemes...** - - Uncheck **Autocreate schemes** - - Check **Shared** - -![Screenshot of scheme settings](Images/scheme-settings-for-travis.png) - -## Want to chat? - -This isn't just a repo with a bunch of code... If you want to learn more about how an algorithm works or want to discuss better ways of solving problems, then open a [Github issue](https://github.com/raywenderlich/swift-algorithm-club/issues) and we'll talk! diff --git a/Huffman Coding/Huffman.playground/Sources/Heap.swift b/Huffman Coding/Huffman.playground/Sources/Heap.swift index 5ace98a17..b578ef6e4 100644 --- a/Huffman Coding/Huffman.playground/Sources/Heap.swift +++ b/Huffman Coding/Huffman.playground/Sources/Heap.swift @@ -143,7 +143,7 @@ public struct Heap { 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/Huffman Coding/Huffman.swift b/Huffman Coding/Huffman.swift index 7e2961a2f..cad80327d 100644 --- a/Huffman Coding/Huffman.swift +++ b/Huffman Coding/Huffman.swift @@ -13,7 +13,7 @@ public class Huffman { /* Tree nodes don't use pointers to refer to each other, but simple integer indices. That allows us to use structs for the nodes. */ typealias NodeIndex = Int - + /* A node in the compression tree. Leaf nodes represent the actual bytes that are present in the input data. The count of an intermediary node is the sum of the counts of all nodes below it. The root node's count is the number of @@ -25,15 +25,15 @@ public class Huffman { var left: NodeIndex = -1 var right: NodeIndex = -1 } - + /* The tree structure. The first 256 entries are for the leaf nodes (not all of those may be used, depending on the input). We add additional nodes as we build the tree. */ var tree = [Node](repeating: Node(), count: 256) - + /* This is the last node we add to the tree. */ var root: NodeIndex = -1 - + /* The frequency table describes how often a byte occurs in the input data. You need it to decompress the Huffman-encoded data. The frequency table should be serialized along with the compressed data. */ @@ -41,7 +41,7 @@ public class Huffman { var byte: UInt8 = 0 var count = 0 } - + public init() { } } @@ -59,7 +59,7 @@ extension Huffman { ptr = ptr.successor() } } - + /* Takes a frequency table and rebuilds the tree. This is the first step of decompression. */ fileprivate func restoreTree(fromTable frequencyTable: [Freq]) { @@ -70,7 +70,7 @@ extension Huffman { } buildTree() } - + /* Returns the frequency table. This is the first 256 nodes from the tree but only those that are actually used, without the parent/left/right pointers. You would serialize this along with the compressed file. */ @@ -91,13 +91,13 @@ extension Huffman { for node in tree where node.count > 0 { queue.enqueue(node) } - + while queue.count > 1 { // Find the two nodes with the smallest frequencies that do not have // a parent node yet. let node1 = queue.dequeue()! let node2 = queue.dequeue()! - + // Create a new intermediate node. var parentNode = Node() parentNode.count = node1.count + node2.count @@ -105,15 +105,15 @@ extension Huffman { parentNode.right = node2.index parentNode.index = tree.count tree.append(parentNode) - + // Link the two nodes into their new parent node. tree[node1.index].parent = parentNode.index tree[node2.index].parent = parentNode.index - + // Put the intermediate node back into the queue. queue.enqueue(parentNode) } - + // The final remaining node in the queue becomes the root of the tree. let rootNode = queue.dequeue()! root = rootNode.index @@ -125,7 +125,7 @@ extension Huffman { public func compressData(data: NSData) -> NSData { countByteFrequency(inData: data) buildTree() - + let writer = BitWriter() var ptr = data.bytes.assumingMemoryBound(to: UInt8.self) for _ in 0.. NSData { restoreTree(fromTable: frequencyTable) - + let reader = BitReader(data: data) let outData = NSMutableData() let byteCount = tree[root].count - + var i = 0 while i < byteCount { var b = findLeafNode(reader: reader, nodeIndex: root) @@ -172,7 +172,7 @@ extension Huffman { } return outData } - + /* Walks the tree from the root down to the leaf node. At every node, read the next bit and use that to determine whether to step to the left or right. When we get to the leaf node, we simply return its index, which is equal to diff --git a/Huffman Coding/NSData+Bits.swift b/Huffman Coding/NSData+Bits.swift index 4ae3c5c6d..1f5333296 100644 --- a/Huffman Coding/NSData+Bits.swift +++ b/Huffman Coding/NSData+Bits.swift @@ -5,7 +5,7 @@ public class BitWriter { public var data = NSMutableData() var outByte: UInt8 = 0 var outCount = 0 - + public func writeBit(bit: Bool) { if outCount == 8 { data.append(&outByte, length: 1) @@ -14,7 +14,7 @@ public class BitWriter { outByte = (outByte << 1) | (bit ? 1 : 0) outCount += 1 } - + public func flush() { if outCount > 0 { if outCount < 8 { @@ -31,11 +31,11 @@ public class BitReader { var ptr: UnsafePointer var inByte: UInt8 = 0 var inCount = 8 - + public init(data: NSData) { ptr = data.bytes.assumingMemoryBound(to: UInt8.self) } - + public func readBit() -> Bool { if inCount == 8 { inByte = ptr.pointee // load the next byte diff --git a/Huffman Coding/README.markdown b/Huffman Coding/README.markdown index 6b2c0d245..d1181d215 100644 --- a/Huffman Coding/README.markdown +++ b/Huffman Coding/README.markdown @@ -1,51 +1,51 @@ # Huffman Coding -The idea: Encode objects that occur often with a smaller number of bits than objects that occur less frequently. +The idea: To encode objects that occur often with a smaller number of bits than objects that occur less frequently. -Although you can encode any type of objects with this scheme, it's most common to compress a stream of bytes. Let's say you have the following text, where each character is one byte: +Although any type of objects can be encoded with this scheme, it is common to compress a stream of bytes. Suppose you have the following text, where each character is one byte: so much words wow many compression -If you count how often each byte appears, you can clearly see that some bytes occur more than others: +If you count how often each byte appears, you can see some bytes occur more than others: space: 5 u: 1 - o: 5 h: 1 - s: 4 d: 1 - m: 3 a: 1 - w: 3 y: 1 - c: 2 p: 1 - r: 2 e: 1 - n: 2 i: 1 - + o: 5 h: 1 + s: 4 d: 1 + m: 3 a: 1 + w: 3 y: 1 + c: 2 p: 1 + r: 2 e: 1 + n: 2 i: 1 + We can assign bit strings to each of these bytes. The more common a byte is, the fewer bits we assign to it. We might get something like this: space: 5 010 u: 1 11001 o: 5 000 h: 1 10001 s: 4 101 d: 1 11010 m: 3 111 a: 1 11011 - w: 3 0010 y: 1 01111 - c: 2 0011 p: 1 11000 - r: 2 1001 e: 1 01110 - n: 2 0110 i: 1 10000 + w: 3 0010 y: 1 01111 + c: 2 0011 p: 1 11000 + r: 2 1001 e: 1 01110 + n: 2 0110 i: 1 10000 Now if we replace the original bytes with these bit strings, the compressed output becomes: - 101 000 010 111 11001 0011 10001 010 0010 000 1001 11010 101 + 101 000 010 111 11001 0011 10001 010 0010 000 1001 11010 101 s o _ m u c h _ w o r d s - + 010 0010 000 0010 010 111 11011 0110 01111 010 0011 000 111 _ w o w _ m a n y _ c o m - + 11000 1001 01110 101 101 10000 000 0110 0 p r e s s i o n The extra 0-bit at the end is there to make a full number of bytes. We were able to compress the original 34 bytes into merely 16 bytes, a space savings of over 50%! -But hold on... to be able to decode these bits we need to have the original frequency table. That table needs to be transmitted or saved along with the compressed data, otherwise the decoder doesn't know how to interpret the bits. Because of the overhead of this frequency table (about 1 kilobyte), it doesn't really pay to use Huffman encoding on very small inputs. +To be able to decode these bits, we need to have the original frequency table. That table needs to be transmitted or saved along with the compressed data. Otherwise, the decoder does not know how to interpret the bits. Because of the overhead of this frequency table (about 1 kilobyte), it is not beneficial to use Huffman encoding on small inputs. ## How it works -When compressing a stream of bytes, the algorithm first creates a frequency table that counts how often each byte occurs. Based on this table it creates a binary tree that describes the bit strings for each of the input bytes. +When compressing a stream of bytes, the algorithm first creates a frequency table that counts how often each byte occurs. Based on this table, the algorithm creates a binary tree that describes the bit strings for each of the input bytes. For our example, the tree looks like this: @@ -53,17 +53,17 @@ For our example, the tree looks like this: Note that the tree has 16 leaf nodes (the grey ones), one for each byte value from the input. Each leaf node also shows the count of how often it occurs. The other nodes are "intermediate" nodes. The number shown in these nodes is the sum of the counts of their child nodes. The count of the root node is therefore the total number of bytes in the input. -The edges between the nodes either say "1" or "0". These correspond to the bit-encodings of the leaf nodes. Notice how each left branch is always 1 and each right branch is always 0. +The edges between the nodes are either "1" or "0". These correspond to the bit-encodings of the leaf nodes. Notice how each left branch is always 1 and each right branch is always 0. -Compression is then a matter of looping through the input bytes, and for each byte traverse the tree from the root node to that byte's leaf node. Every time we take a left branch, we emit a 1-bit. When we take a right branch, we emit a 0-bit. +Compression is then a matter of looping through the input bytes and for each byte traversing the tree from the root node to that byte's leaf node. Every time we take a left branch, we emit a 1-bit. When we take a right branch, we emit a 0-bit. -For example, to go from the root node to `c`, we go right (`0`), right again (`0`), left (`1`), and left again (`1`). So the Huffman code for `c` is `0011`. +For example, to go from the root node to `c`, we go right (`0`), right again (`0`), left (`1`), and left again (`1`). This gives the Huffman code as `0011` for `c`. -Decompression works in exactly the opposite way. It reads the compressed bits one-by-one and traverses the tree until we get to a leaf node. The value of that leaf node is the uncompressed byte. For example, if the bits are `11010`, we start at the root and go left, left again, right, left, and a final right to end up at `d`. +Decompression works in exactly the opposite way. It reads the compressed bits one-by-one and traverses the tree until it reaches to a leaf node. The value of that leaf node is the uncompressed byte. For example, if the bits are `11010`, we start at the root and go left, left again, right, left, and a final right to end up at `d`. ## The code -Before we get to the actual Huffman coding scheme, it's useful to have some helper code that can write individual bits to an `NSData` object. The smallest piece of data that `NSData` understands is the byte, but we're dealing in bits, so we need to translate between the two. +Before we get to the actual Huffman coding scheme, it is useful to have some helper code that can write individual bits to an `NSData` object. The smallest piece of data that `NSData` understands is the byte, but we are dealing in bits, so we need to translate between the two. ```swift public class BitWriter { @@ -92,11 +92,11 @@ public class BitWriter { } ``` -To add a bit to the `NSData` you call `writeBit()`. This stuffs each new bit into the `outByte` variable. Once you've written 8 bits, `outByte` gets added to the `NSData` object for real. +To add a bit to the `NSData`, you can call `writeBit()`. This helper object stuffs each new bit into the `outByte` variable. Once you have written 8 bits, `outByte` gets added to the `NSData` object for real. The `flush()` method is used for outputting the very last byte. There is no guarantee that the number of compressed bits is a nice round multiple of 8, in which case there may be some spare bits at the end. If so, `flush()` adds a few 0-bits to make sure that we write a full byte. -We'll use a similar helper object for reading individual bits from `NSData`: +Here is a similar helper object for reading individual bits from `NSData`: ```swift public class BitReader { @@ -122,22 +122,22 @@ public class BitReader { } ``` -This works in a similar fashion. We read one whole byte from the `NSData` object and put it in `inByte`. Then `readBit()` returns the individual bits from that byte. Once `readBit()` has been called 8 times, we read the next byte from the `NSData`. +By using this helper object, we can read one whole byte from the `NSData` object and put it in `inByte`. Then, `readBit()` returns the individual bits from that byte. Once `readBit()` has been called 8 times, we read the next byte from the `NSData`. -> **Note:** It's no big deal if you're unfamiliar with this sort of bit manipulation. Just know that these two helper objects make it simple for us to write and read bits and not worry about all the messy stuff of making sure they end up in the right place. +> **Note:** If you are unfamiliar with this type of bit manipulation, just know that these two helper objects make it simple for us to write and read bits. ## The frequency table -The first step in Huffman compression is to read the entire input stream and build a frequency table. This table contains a list of all 256 possible byte values and how often each of these bytes occurs in the input data. +The first step in the Huffman compression is to read the entire input stream and build a frequency table. This table contains a list of all 256 possible byte values and shows how often each of these bytes occurs in the input data. -We could store this frequency information in a dictionary or an array, but since we're going to need to build a tree anyway, we might as well store the frequency table as the leaves of the tree. +We could store this frequency information in a dictionary or an array, but since we need to build a tree, we might store the frequency table as the leaves of the tree. Here are the definitions we need: ```swift class Huffman { typealias NodeIndex = Int - + struct Node { var count = 0 var index: NodeIndex = -1 @@ -152,9 +152,9 @@ class Huffman { } ``` -The tree structure is stored in the `tree` array and will be made up of `Node` objects. Since this is a [binary tree](../Binary Tree/), each node needs two children, `left` and `right`, and a reference back to its `parent` node. Unlike a typical binary tree, however, these nodes don't to use pointers to refer to each other but simple integer indices in the `tree` array. (We also store the array `index` of the node itself; the reason for this will become clear later.) +The tree structure is stored in the `tree` array and will be made up of `Node` objects. Since this is a [binary tree](../Binary%20Tree/), each node needs two children, `left` and `right`, and a reference back to its `parent` node. Unlike a typical binary tree, these nodes do not use pointers to refer to each other but use simple integer indices in the `tree` array. (We also store the array `index` of the node itself; the reason for this will become clear later.) -Note that `tree` currently has room for 256 entries. These are for the leaf nodes because there are 256 possible byte values. Of course, not all of those may end up being used, depending on the input data. Later, we'll add more nodes as we build up the actual tree. For the moment there isn't a tree yet, just 256 separate leaf nodes with no connections between them. All the node counts are 0. +Note that the `tree` currently has room for 256 entries. These are for the leaf nodes because there are 256 possible byte values. Of course, not all of those may end up being used, depending on the input data. Later, we will add more nodes as we build up the actual tree. For the moment, there is not a tree yet. It includes 256 separate leaf nodes with no connections between them. All the node counts are 0. We use the following method to count how often each byte occurs in the input data: @@ -170,20 +170,20 @@ We use the following method to count how often each byte occurs in the input dat } ``` -This steps through the `NSData` object from beginning to end and for each byte increments the `count` of the corresponding leaf node. That's all there is to it. After `countByteFrequency()` completes, the first 256 `Node` objects in the `tree` array represent the frequency table. +This steps through the `NSData` object from beginning to end and for each byte increments the `count` of the corresponding leaf node. After `countByteFrequency()` completes, the first 256 `Node` objects in the `tree` array represent the frequency table. -Now, I mentioned that in order to decompress a Huffman-encoded block of data, you also need to have the original frequency table. If you were writing the compressed data to a file, then somewhere in the file you'd include the frequency table. +To decompress a Huffman-encoded block of data, we need to have the original frequency table. If we were writing the compressed data to a file, then somewhere in the file we should include the frequency table. -You could simply dump the first 256 elements from the `tree` array but that's not very efficient. It's likely that not all of these 256 elements will be used, plus you don't need to serialize the `parent`, `right`, and `left` pointers. All we need is the frequency information, not the entire tree. +We could dump the first 256 elements from the `tree` array, but that is not efficient. Not all of these 256 elements will be used, and we do not need to serialize the `parent`, `right`, and `left` pointers. All we need is the frequency information and not the entire tree. -Instead, we'll add a method to export the frequency table without all the pieces we don't need: +Instead, we will add a method to export the frequency table without all the pieces we do not need: ```swift struct Freq { var byte: UInt8 = 0 var count = 0 } - + func frequencyTable() -> [Freq] { var a = [Freq]() for i in 0..<256 where tree[i].count > 0 { @@ -193,7 +193,7 @@ Instead, we'll add a method to export the frequency table without all the pieces } ``` -The `frequencyTable()` method looks at those first 256 nodes from the tree but keeps only those that are actually used, without the `parent`, `left`, and `right` pointers. It returns an array of `Freq` objects. You have to serialize this array along with the compressed data so that it can be properly decompressed later. +The `frequencyTable()` method looks at those first 256 nodes from the tree but keeps only those that are used, without the `parent`, `left`, and `right` pointers. It returns an array of `Freq` objects. You have to serialize this array along with the compressed data, so that it can be properly decompressed later. ## The tree @@ -201,7 +201,7 @@ As a reminder, there is the compression tree for the example: ![The compression tree](Images/Tree.png) -The leaf nodes represent the actual bytes that are present in the input data. The intermediary nodes connect the leaves in such a way that the path from the root to a frequently-used byte value is shorter than the path to a less common byte value. As you can see, `m`, `s`, space, and `o` are the most common letters in our input data and therefore they are highest up in the tree. +The leaf nodes represent the actual bytes that are present in the input data. The intermediary nodes connect the leaves in such a way that the path from the root to a frequently-used byte value is shorter than the path to a less common byte value. As you can see, `m`, `s`, space, and `o` are the most common letters in our input data and the highest up in the tree. To build the tree, we do the following: @@ -209,7 +209,7 @@ To build the tree, we do the following: 2. Create a new parent node that links these two nodes together. 3. This repeats over and over until only one node with no parent remains. This becomes the root node of the tree. -This is an ideal place to use a [priority queue](../Priority Queue/). A priority queue is a data structure that is optimized so that finding the minimum value is always very fast. Here, we repeatedly need to find the node with the smallest count. +This is an ideal place to use a [priority queue](../Priority%20Queue/). A priority queue is a data structure that is optimized, so that finding the minimum value is always fast. Here, we repeatedly need to find the node with the smallest count. The function `buildTree()` then becomes: @@ -233,7 +233,7 @@ The function `buildTree()` then becomes: tree[node1.index].parent = parentNode.index // 4 tree[node2.index].parent = parentNode.index - + queue.enqueue(parentNode) // 5 } @@ -252,15 +252,15 @@ Here is how it works step-by-step: 4. Link the two nodes into their new parent node. Now this new intermediate node has become part of the tree. -5. Put the new intermediate node back into the queue. At this point we're done with `node1` and `node2`, but the `parentNode` still need to be connected to other nodes in the tree. +5. Put the new intermediate node back into the queue. At this point we are done with `node1` and `node2`, but the `parentNode` still needs to be connected to other nodes in the tree. -6. Repeat steps 2-5 until there is only one node left in the queue. This becomes the root node of the tree, and we're done. +6. Repeat steps 2-5 until there is only one node left in the queue. This becomes the root node of the tree, and we are done. The animation shows what the process looks like: ![Building the tree](Images/BuildTree.gif) -> **Note:** Instead of using a priority queue, you can repeatedly iterate through the `tree` array to find the next two smallest nodes, but that makes the compressor quite slow, **O(n^2)**. Using the priority queue, the running time is only **O(n log n)** where **n** is the number of nodes. +> **Note:** Instead of using a priority queue, you can repeatedly iterate through the `tree` array to find the next two smallest nodes, but that makes the compressor slow as **O(n^2)**. Using the priority queue, the running time is only **O(n log n)** where **n** is the number of nodes. > **Fun fact:** Due to the nature of binary trees, if we have *x* leaf nodes we can at most add *x - 1* additional nodes to the tree. Given that at most there will be 256 leaf nodes, the tree will never contain more than 511 nodes total. @@ -286,11 +286,11 @@ Now that we know how to build the compression tree from the frequency table, we } ``` -This first calls `countByteFrequency()` to build the frequency table, then `buildTree()` to put together the compression tree. It also creates a `BitWriter` object for writing individual bits. +This first calls `countByteFrequency()` to build the frequency table and then calls `buildTree()` to put together the compression tree. It also creates a `BitWriter` object for writing individual bits. -Then it loops through the entire input and for each byte calls `traverseTree()`. That method will step through the tree nodes and for each node write a 1 or 0 bit. Finally, we return the `BitWriter`'s data object. +Then, it loops through the entire input and calls `traverseTree()`for each byte. This method will step through the tree nodes and for each node write a 1 or 0 bit. Finally, we return the `BitWriter`'s data object. -> **Note:** Compression always requires two passes through the entire input data: first to build the frequency table, second to convert the bytes to their compressed bit sequences. +> **Note:** Compression always requires two passes through the entire input data: first to build the frequency table, and second to convert the bytes to their compressed bit sequences. The interesting stuff happens in `traverseTree()`. This is a recursive method: @@ -309,15 +309,15 @@ The interesting stuff happens in `traverseTree()`. This is a recursive method: } ``` -When we call this method from `compressData()`, the `nodeIndex` parameter is the array index of the leaf node for the byte that we're about to encode. This method recursively walks the tree from a leaf node up to the root, and then back again. +When we call this method from `compressData()`, the `nodeIndex` parameter is the array index of the leaf node for the byte that we need to encode. This method recursively walks the tree from a leaf node up to the root and then back again. -As we're going back from the root to the leaf node, we write a 1 bit or a 0 bit for every node we encounter. If a child is the left node, we emit a 1; if it's the right node, we emit a 0. +As we are going back from the root to the leaf node, we write a 1 bit or a 0 bit for every node we encounter. If a child is the left node, we emit a 1; if it is the right node, we emit a 0. In a picture: ![How compression works](Images/Compression.png) -Even though the illustration of the tree shows a 0 or 1 for each edge between the nodes, the bit values 0 and 1 aren't actually stored in the tree! The rule is that we write a 1 bit if we take the left branch and a 0 bit if we take the right branch, so just knowing the direction we're going in is enough to determine what bit value to write. +Even though the illustration of the tree shows a 0 or 1 for each edge between the nodes, the bit values 0 and 1 are not actually stored in the tree! The rule is that we write a 1 bit if we take the left branch and a 0 bit if we take the right branch, so just knowing the direction we are going in is enough to determine what bit value to write. You use the `compressData()` method as follows: @@ -332,9 +332,9 @@ if let originalData = s1.dataUsingEncoding(NSUTF8StringEncoding) { ## Decompression -Decompression is pretty much compression in reverse. However, the compressed bits are useless to us without the frequency table. Earlier you saw the `frequencyTable()` method that returns an array of `Freq` objects. The idea is that if you were saving the compressed data into a file or sending it across the network, you'd also save that `[Freq]` array along with it. +Decompression is the compression in reverse. However, the compressed bits are useless without the frequency table. As mentioned, the `frequencyTable()` method returns an array of `Freq` objects. If we were saving the compressed data into a file or sending it across the network, we'd also save that `[Freq]` array along with it. -We first need some way to turn the `[Freq]` array back into a compression tree. Fortunately, this is pretty easy: +We first need some way to turn the `[Freq]` array back into a compression tree: ```swift fileprivate func restoreTree(fromTable frequencyTable: [Freq]) { @@ -349,7 +349,7 @@ We first need some way to turn the `[Freq]` array back into a compression tree. We convert the `Freq` objects into leaf nodes and then call `buildTree()` to do the rest. -Here is the code for `decompressData()`, which takes an `NSData` object with Huffman-encoded bits and a frequency table, and returns the original data: +Here is the code for `decompressData()`, which takes an `NSData` object with Huffman-encoded bits and a frequency table, and it returns the original data: ```swift func decompressData(data: NSData, frequencyTable: [Freq]) -> NSData { @@ -385,26 +385,26 @@ This also uses a helper method to traverse the tree: } ``` -`findLeafNode()` walks the tree from the root down to the leaf node given by `nodeIndex`. At each intermediate node we read a new bit and then step to the left (bit is 1) or the right (bit is 0). When we get to the leaf node, we simply return its index, which is equal to the original byte value. +`findLeafNode()` walks the tree from the root down to the leaf node given by `nodeIndex`. At each intermediate node, we read a new bit and then step to the left (bit is 1) or the right (bit is 0). When we get to the leaf node, we simply return its index, which is equal to the original byte value. In a picture: ![How decompression works](Images/Decompression.png) -Here's how you would use the decompression method: +Here is how we use the decompression method: ```swift let frequencyTable = huffman1.frequencyTable() - + let huffman2 = Huffman() let decompressedData = huffman2.decompressData(compressedData, frequencyTable: frequencyTable) - + let s2 = String(data: decompressedData, encoding: NSUTF8StringEncoding)! ``` -First you get the frequency table from somewhere (in this case the `Huffman` object we used to encode the data) and then call `decompressData()`. The string that results should be equal to the one you compressed in the first place. +First we get the frequency table from somewhere (in this case the `Huffman` object we used to encode the data) and then call `decompressData()`. The string that results should be equal to the one we compressed in the first place. -You can see how this works in more detail in the Playground. +we can see how this works in more detail in the Playground. ## See also 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 b25916b23..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, >) \ No newline at end of file +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 { @@ -30,19 +30,19 @@ func multiply(_ num1: Int, by num2: Int, base: Int = 10) -> Int { product[i + num2Array.count] += carry } - return Int(product.reversed().map{ String($0) }.reduce("", +))! + 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 { + 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/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 fb20a667d..3d14a243a 100644 --- a/Karatsuba Multiplication/KaratsubaMultiplication.swift +++ b/Karatsuba Multiplication/KaratsubaMultiplication.swift @@ -19,27 +19,48 @@ 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 - - guard num1Array.count > 1 && num2Array.count > 1 else { - return num1 * num2 + 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/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 35fb27ff8..9b4a50fa0 100644 --- a/Knuth-Morris-Pratt/KnuthMorrisPratt.playground/Contents.swift +++ b/Knuth-Morris-Pratt/KnuthMorrisPratt.playground/Contents.swift @@ -1,116 +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 46a88c134..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) + 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] + } + } - 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 + guard !indexes.isEmpty else { + return nil } + return indexes + } } diff --git a/Knuth-Morris-Pratt/README.markdown b/Knuth-Morris-Pratt/README.markdown index f01b87b3d..2e4d5360b 100644 --- a/Knuth-Morris-Pratt/README.markdown +++ b/Knuth-Morris-Pratt/README.markdown @@ -1,9 +1,9 @@ # Knuth-Morris-Pratt String Search -Goal: Write a linear-time string matching algorithm in Swift that returns the indexes of all the occurrencies of a given pattern. - +Goal: Write a linear-time string matching algorithm in Swift that returns the indexes of all the occurrencies of a given pattern. + In other words, we want to implement an `indexesOf(pattern: String)` extension on `String` that returns an array `[Int]` of integers, representing all occurrences' indexes of the search pattern, or `nil` if the pattern could not be found inside the string. - + For example: ```swift @@ -14,9 +14,9 @@ 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 String Search/) 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. +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. The pre-processing stage produces an array (called `suffixPrefix` in the code) of integers in which every element `suffixPrefix[i]` records the length of the longest proper suffix of `P[0...i]` (where `P` is the pattern) that matches a prefix of `P`. In other words, `suffixPrefix[i]` is the longest proper substring of `P` that ends at position `i` and that is a prefix of `P`. Just a quick example. Consider `P = "abadfryaabsabadffg"`, then `suffixPrefix[4] = 0`, `suffixPrefix[9] = 2`, `suffixPrefix[14] = 4`. There are different ways to obtain the values of `SuffixPrefix` array. We will use the method based on the [Z-Algorithm](../Z-Algorithm/). This function takes in input the pattern and produces an array of integers. Each element represents the length of the longest substring starting at position `i` of `P` and that matches a prefix of `P`. You can notice that the two arrays are similar, they record the same informations but on the different places. We only have to find a method to map `Z[i]` to `suffixPrefix[j]`. It is not that difficult and this is the code that will do for us: @@ -93,10 +93,10 @@ extension String { ``` Let's make an example reasoning with the code above. Let's consider the string `P = ACTGACTA"`, the consequentially obtained `suffixPrefix` array equal to `[0, 0, 0, 0, 0, 0, 3, 1]`, and the text `T = "GCACTGACTGACTGACTAG"`. The algorithm begins with the text and the pattern aligned like below. We have to compare `T[0]` with `P[0]`. - + 1 0123456789012345678 - text: GCACTGACTGACTGACTAG + text: GCACTGACTGACTGACTAG textIndex: ^ pattern: ACTGACTA patternIndex: ^ @@ -104,54 +104,54 @@ Let's make an example reasoning with the code above. Let's consider the string ` suffixPrefix: 00000031 We have a mismatch and we move on comparing `T[1]` and `P[0]`. We have to check if a pattern occurrence is present but there is not. So, we have to shift the pattern right and by doing so we have to check `suffixPrefix[1 - 1]`. Its value is `0` and we restart by comparing `T[1]` with `P[0]`. Again a mismath occurs, so we go on with `T[2]` and `P[0]`. - + 1 0123456789012345678 text: GCACTGACTGACTGACTAG - textIndex: ^ + textIndex: ^ pattern: ACTGACTA patternIndex: ^ suffixPrefix: 00000031 This time we have a match. And it continues until position `8`. Unfortunately the length of the match is not equal to the pattern length, we cannot report an occurrence. But we are still lucky because we can use the values computed in the `suffixPrefix` array now. In fact, the length of the match is `7`, and if we look at the element `suffixPrefix[7 - 1]` we discover that is `3`. This information tell us that that the prefix of `P` matches the suffix of the susbtring `T[0...8]`. So the `suffixPrefix` array guarantees us that the two substring match and that we do not have to compare their characters, so we can shift right the pattern for more than one character! The comparisons restart from `T[9]` and `P[3]`. - + 1 0123456789012345678 - text: GCACTGACTGACTGACTAG + text: GCACTGACTGACTGACTAG textIndex: ^ pattern: ACTGACTA patternIndex: ^ suffixPrefix: 00000031 They match so we continue the compares until position `13` where a misatch occurs beetwen charcter `G` and `A`. Just like before, we are lucky and we can use the `suffixPrefix` array to shift right the pattern. - + 1 0123456789012345678 - text: GCACTGACTGACTGACTAG + text: GCACTGACTGACTGACTAG textIndex: ^ pattern: ACTGACTA patternIndex: ^ suffixPrefix: 00000031 Again, we have to compare. But this time the comparisons finally take us to an occurrence, at position `17 - 7 = 10`. - + 1 0123456789012345678 - text: GCACTGACTGACTGACTAG + text: GCACTGACTGACTGACTAG textIndex: ^ pattern: ACTGACTA patternIndex: ^ suffixPrefix: 00000031 The algorithm than tries to compare `T[18]` with `P[1]` (because we used the element `suffixPrefix[8 - 1] = 1`) but it fails and at the next iteration it ends its work. - + The pre-processing stage involves only the pattern. The running time of the Z-Algorithm is linear and takes `O(n)`, where `n` is the length of the pattern `P`. After that, the search stage does not "overshoot" the length of the text `T` (call it `m`). It can be be proved that number of comparisons of the search stage is bounded by `2 * m`. The final running time of the Knuth-Morris-Pratt algorithm is `O(n + m)`. > **Note:** To execute the code in the [KnuthMorrisPratt.swift](./KnuthMorrisPratt.swift) you have to copy the [ZAlgorithm.swift](../Z-Algorithm/ZAlgorithm.swift) file contained in the [Z-Algorithm](../Z-Algorithm/) folder. The [KnuthMorrisPratt.playground](./KnuthMorrisPratt.playground) already includes the definition of the `Zeta` function. -Credits: This code is based on the handbook ["Algorithm on String, Trees and Sequences: Computer Science and Computational Biology"](https://books.google.it/books/about/Algorithms_on_Strings_Trees_and_Sequence.html?id=Ofw5w1yuD8kC&redir_esc=y) by Dan Gusfield, Cambridge University Press, 1997. +Credits: This code is based on the handbook ["Algorithm on String, Trees and Sequences: Computer Science and Computational Biology"](https://books.google.it/books/about/Algorithms_on_Strings_Trees_and_Sequence.html?id=Ofw5w1yuD8kC&redir_esc=y) by Dan Gusfield, Cambridge University Press, 1997. *Written for Swift Algorithm Club by Matteo Dunnhofer* diff --git a/Kth Largest Element/README.markdown b/Kth Largest Element/README.markdown index d50d691a1..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,11 +40,11 @@ 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 -There is a clever algorithm that combines the ideas of [binary search](../Binary Search/) and [quicksort](../Quicksort/) to arrive at an **O(n)** solution. +There is a clever algorithm that combines the ideas of [binary search](../Binary%20Search/) and [quicksort](../Quicksort/) to arrive at an **O(n)** solution. Recall that binary search splits the array in half over and over again, to quickly narrow in on the value you're searching for. That's what we'll do here too. @@ -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 { @@ -120,7 +120,7 @@ public func randomizedSelect(array: [T], order k: Int) -> T { return a[low] } } - + precondition(a.count > 0) return randomizedSelect(&a, 0, a.count - 1, k) } diff --git a/Kth Largest Element/kthLargest.playground/Contents.swift b/Kth Largest Element/kthLargest.playground/Contents.swift index 9fba2b483..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,69 +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 1f90b096f..aad15eac6 100644 --- a/Kth Largest Element/kthLargest.swift +++ b/Kth Largest Element/kthLargest.playground/Sources/kthLargest.swift @@ -4,35 +4,18 @@ 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 } } - // 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. @@ -46,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/Contents.swift b/LRU Cache/LRUCache.playground/Contents.swift new file mode 100644 index 000000000..35dcc5d59 --- /dev/null +++ b/LRU Cache/LRUCache.playground/Contents.swift @@ -0,0 +1,10 @@ +let cache = LRUCache(2) +cache.set("a", val: 1) +cache.set("b", val: 2) +cache.get("a") // returns 1 +cache.set("c", val: 3) // evicts key "b" +cache.get("b") // returns nil (not found) +cache.set("d", val: 4) // evicts key "a" +cache.get("a") // returns nil (not found) +cache.get("c") // returns 3 +cache.get("d") // returns 4 diff --git a/LRU Cache/LRUCache.playground/Sources/LRUCache.swift b/LRU Cache/LRUCache.playground/Sources/LRUCache.swift new file mode 100644 index 000000000..3b9ca1139 --- /dev/null +++ b/LRU Cache/LRUCache.playground/Sources/LRUCache.swift @@ -0,0 +1,59 @@ +// +// LRUCache.swift +// +// +// Created by Kai Chen on 16/07/2017. +// +// + +import Foundation + +public class LRUCache { + private let maxSize: Int + private var cache: [KeyType: Any] = [:] + private var priority: LinkedList = LinkedList() + private var key2node: [KeyType: LinkedList.LinkedListNode] = [:] + + public init(_ maxSize: Int) { + self.maxSize = maxSize + } + + public func get(_ key: KeyType) -> Any? { + guard let val = cache[key] else { + return nil + } + + remove(key) + insert(key, val: val) + + return val + } + + public func set(_ key: KeyType, val: Any) { + if cache[key] != nil { + remove(key) + } else if priority.count >= maxSize, let keyToRemove = priority.last?.value { + remove(keyToRemove) + } + + insert(key, val: val) + } + + private func remove(_ key: KeyType) { + cache.removeValue(forKey: key) + guard let node = key2node[key] else { + return + } + priority.remove(node: node) + key2node.removeValue(forKey: key) + } + + private func insert(_ key: KeyType, val: Any) { + cache[key] = val + priority.insert(key, atIndex: 0) + guard let first = priority.first else { + return + } + key2node[key] = first + } +} diff --git a/LRU Cache/LRUCache.playground/Sources/LinkedList.swift b/LRU Cache/LRUCache.playground/Sources/LinkedList.swift new file mode 100755 index 000000000..a5594eb31 --- /dev/null +++ b/LRU Cache/LRUCache.playground/Sources/LinkedList.swift @@ -0,0 +1,246 @@ +public final class LinkedList { + + public class LinkedListNode { + var value: T + var next: LinkedListNode? + weak var previous: LinkedListNode? + + public init(value: T) { + self.value = value + } + } + + public typealias Node = LinkedListNode + + fileprivate var head: Node? + + public init() {} + + public var isEmpty: Bool { + return head == nil + } + + public var first: Node? { + return head + } + + public var last: Node? { + if var node = head { + while let next = node.next { + node = next + } + return node + } else { + return nil + } + } + + public var count: Int { + if var node = head { + var c = 1 + while let next = node.next { + node = next + c += 1 + } + return c + } 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 + } + } + return nil + } + + public subscript(index: Int) -> T { + let node = self.node(atIndex: index) + assert(node != nil) + return node!.value + } + + public func append(_ value: T) { + let newNode = Node(value: value) + self.append(newNode) + } + + public func append(_ node: Node) { + let newNode = LinkedListNode(value: node.value) + if let lastNode = last { + newNode.previous = lastNode + lastNode.next = newNode + } else { + head = newNode + } + } + + public func append(_ list: LinkedList) { + var nodeToCopy = list.head + while let node = nodeToCopy { + self.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) { + let newNode = Node(value: value) + self.insert(newNode, atIndex: 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 { + head = 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 + } + prev?.next = next + next?.previous = prev + } + + public func removeAll() { + head = nil + } + + @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 + } + + @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!) + } +} + +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 += ", " } + } + return s + "]" + } +} + +extension LinkedList { + public func reverse() { + var node = head + while let currentNode = node { + node = currentNode.next + swap(¤tNode.next, ¤tNode.previous) + head = currentNode + } + } +} + +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 + } + 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 + } + return result + } +} + +extension LinkedList { + convenience init(array: Array) { + self.init() + + for element in array { + self.append(element) + } + } +} + +extension LinkedList: ExpressibleByArrayLiteral { + public convenience init(arrayLiteral elements: T...) { + self.init() + + for element in elements { + self.append(element) + } + } +} diff --git a/LRU Cache/LRUCache.playground/contents.xcplayground b/LRU Cache/LRUCache.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/LRU Cache/LRUCache.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file 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/LRU Cache/LRUCache.swift b/LRU Cache/LRUCache.swift new file mode 100644 index 000000000..4d15599de --- /dev/null +++ b/LRU Cache/LRUCache.swift @@ -0,0 +1,59 @@ +// +// LRUCache.swift +// +// +// Created by Kai Chen on 16/07/2017. +// +// + +import Foundation + +public class LRUCache { + private let maxSize: Int + private var cache: [KeyType: Any] = [:] + private var priority: LinkedList = LinkedList() + private var key2node: [KeyType: LinkedList.LinkedListNode] = [:] + + public init(_ maxSize: Int) { + self.maxSize = maxSize + } + + public func get(_ key: KeyType) -> Any? { + guard let val = cache[key] else { + return nil + } + + remove(key) + insert(key, val: val) + + return val + } + + public func set(_ key: KeyType, val: Any) { + if cache[key] != nil { + remove(key) + } else if priority.count >= maxSize, let keyToRemove = priority.last?.value { + remove(keyToRemove) + } + + insert(key, val: val) + } + + private func remove(_ key: KeyType) { + cache.removeValue(forKey: key) + guard let node = key2node[key] else { + return + } + priority.remove(node: node) + key2node.removeValue(forKey: key) + } + + private func insert(_ key: KeyType, val: Any) { + cache[key] = val + priority.insert(key, atIndex: 0) + guard let first = priority.first else { + return + } + key2node[key] = first + } +} diff --git a/LRU Cache/Readme.md b/LRU Cache/Readme.md new file mode 100644 index 000000000..8e2941991 --- /dev/null +++ b/LRU Cache/Readme.md @@ -0,0 +1,57 @@ +# LRU Cache + +Caches are used to hold objects in memory. A caches size is finite; If the system doesn't have enough memory, the cache must be purged or the program will crash. [Least Recently Used][1] (LRU) is a popular algorithm in cache design. + +In this implementation of the LRU Cache, a size is declared during instantiation, and any insertions that go beyond the size will purge the least recently used element of the cache. A *priority queue* is used to enforce this behavior. + +## The priority queue + +The key to the LRU cache is the priority queue. For simplicity, you'll model the queue using a linked list. All interactions with the LRU cache should respect this queue; Calling `get` and `set` should update the priority queue to reflect the most recently accessed element. + +### Interesting tidbits + + +#### Adding values + +Each time we access an element, either `set` or `get` we need to insert the element in the head of priority list. We use a helper method to handle this procedure: + +```swift +private func insert(_ key: KeyType, val: Any) { + cache[key] = val + priority.insert(key, atIndex: 0) + guard let first = priority.first else { + return + } + key2node[key] = first +} +``` + +#### Purging the cache + +When the cache is full, a purge must take place starting with the least recently used element. In this case, we need to `remove` the lowest priority node. The operation is like this: + +```swift +private func remove(_ key: KeyType) { + cache.removeValue(key) + guard let node = key2node[key] else { + return + } + priority.remove(node) + key2node.removeValue(key) +} +``` + +### Optimizing Performance + +Removing elements from the priority queue is a frequent operation for the LRU cache. Since priority queue is modelled using a linked list, this is an expensive operation that costs `O(n)` time. This is the bottleneck for both the `set` and `get` methods. + +To help alleviate this problem, a hash table is used to store the references of each node: + +``` +private var key2node: [KeyType: LinkedList.LinkedListNode] = [:] +``` + +*Written for the Swift Algorithm Club by Kai Chen, with additions by Kelvin Lau* + + +[1]: https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU diff --git a/Linear Regression/LinearRegression.playground/Contents.swift b/Linear Regression/LinearRegression.playground/Contents.swift index 8b579b3c1..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.. Double { } func multiply(_ a: [Double], _ b: [Double]) -> [Double] { - return zip(a,b).map(*) + return zip(a, b).map(*) } func linearRegression(_ xs: [Double], _ ys: [Double]) -> (Double) -> Double { diff --git a/Linear Regression/README.markdown b/Linear Regression/README.markdown index 1a1a1eb8b..d07bf1497 100644 --- a/Linear Regression/README.markdown +++ b/Linear Regression/README.markdown @@ -21,7 +21,7 @@ Let's start by looking at the data plotted out: We could imagine a straight line drawn through the points on this graph. It's not (in this case) going to go exactly through every point, but we could place the line so that it goes as close to all the points as possible. -To say this in another way, we want to make the distance from the line to each point as small as possible. This is most often done by minimising the square of the distance from the line to each point. +To say this in another way, we want to make the distance from the line to each point as small as possible. This is most often done by minimizing the square of the distance from the line to each point. We can describe the straight line in terms of two variables: @@ -62,7 +62,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/Linear Search/LinearSearch.swift b/Linear Search/LinearSearch.swift index 882795180..c5374d0a4 100644 --- a/Linear Search/LinearSearch.swift +++ b/Linear Search/LinearSearch.swift @@ -4,3 +4,7 @@ func linearSearch(_ array: [T], _ object: T) -> Int? { } return nil } + +func linearSearch1(_ array: [T], _ object: T) -> Array.Index? { + return array.index { $0 == object } +} diff --git a/Linked List/LinkedList.playground/Contents.swift b/Linked List/LinkedList.playground/Contents.swift index a90b7180c..76b911310 100644 --- a/Linked List/LinkedList.playground/Contents.swift +++ b/Linked List/LinkedList.playground/Contents.swift @@ -1,257 +1,465 @@ -//: Playground - noun: a place where people can play +//: # Linked Lists -public class LinkedListNode { - var value: T - var next: LinkedListNode? - weak var previous: LinkedListNode? +// For best results, don't forget to select "Show Rendered Markup" from XCode's "Editor" menu - public init(value: T) { - self.value = value - } -} - -public class LinkedList { - public typealias Node = LinkedListNode - - fileprivate var head: Node? - - public var isEmpty: Bool { - return head == nil - } +//: Linked List Class Declaration: - public var first: Node? { - return head - } - - public var last: Node? { - if var node = head { - while case let next? = node.next { - node = next - } - return node - } else { - return nil +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 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 + + /// 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 } - } - - 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 - } + + /// Computed property to check if the linked list is empty + public var isEmpty: Bool { + return head == nil } - return nil - } - - public subscript(index: Int) -> T { - let node = self.node(atIndex: index) - assert(node != nil) - return node!.value - } - - public func append(_ value: T) { - let newNode = Node(value: value) - if let lastNode = last { - newNode.previous = lastNode - lastNode.next = newNode - } else { - head = newNode + + /// 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 } - } - - 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 + + /// 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 } - assert(i == 0) // if > 0, then specified index was too large - - return (prev, next) - } - - public func insert(_ value: T, atIndex index: Int) { - let (prev, next) = nodesBeforeAndAfter(index: index) - - let newNode = Node(value: value) - newNode.previous = prev - newNode.next = next - prev?.next = newNode - next?.previous = newNode - - if prev == nil { - 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: 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.. T { - let prev = node.previous - let next = node.next - - if let prev = prev { - prev.next = next - } else { - head = next + + /// 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) + } + + /// 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 + } + } + + /// 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 + } + } + + /// 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) + } + + /// 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 + } + } + + /// 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 + } + } + + /// 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!) + } + + /// 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 - } - - public func removeLast() -> T { - assert(!isEmpty) - return remove(node: last!) - } - - 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() + 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() - for element in array { - self.append(element) + elements.forEach { append($0) } + } +} + +// 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" //list[2] // crash! -list.insert("Swift", atIndex: 1) -list[0] -list[1] -list[2] +let list2 = LinkedList() +list2.append("Goodbye") +list2.append("World") +list.append(list2) // [Hello, World, Goodbye, World] +list2.removeAll() // [ ] +list2.isEmpty // true +list.removeLast() // "World" +list.remove(at: 2) // "Goodbye" + +list.insert("Swift", at: 1) +list[0] // "Hello" +list[1] // "Swift" +list[2] // "World" 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.removeAll() -//list.isEmpty - -list.remove(node: list.first!) // "Hello" +list.remove(node: list.head!) // "Universe" list.count // 2 -list[0] // "Swift" -list[1] // "World" +list[0] // "Swifty" +list[1] // "Hello" -list.removeLast() // "World" +list.count // 2 +list.removeLast() // "Hello" +list.head?.value list.count // 1 -list[0] // "Swift" +list[0] // "Swifty" -list.remove(atIndex: 0) // "Swift" +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 + +// Infer the type from the array +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 06828af92..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 83f898f6c..de8a42c93 100755 --- a/Linked List/LinkedList.swift +++ b/Linked List/LinkedList.swift @@ -1,77 +1,103 @@ -public class LinkedListNode { - var value: T - var next: LinkedListNode? - weak var previous: LinkedListNode? +public final class LinkedList { - public init(value: T) { - self.value = value + /// 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 class LinkedList { - public typealias Node = LinkedListNode - fileprivate var head: Node? + /// Typealiasing the node class to increase readability of code + public typealias Node = LinkedListNode - 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.. { } } - 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 + /// 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 } - 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(_ newNode: Node, atIndex index: Int) { - let (prev, next) = nodesBeforeAndAfter(index: index) - - 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 + } + } + + /// 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 } } + /// 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 @@ -135,31 +195,41 @@ public class LinkedList { 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 @@ -171,13 +241,14 @@ 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 } @@ -185,22 +256,86 @@ extension LinkedList { 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() + + elements.forEach { append($0) } + } +} + +// 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 1884aeb23..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: +--------+ +--------+ +--------+ +--------+ @@ -79,18 +81,18 @@ public class LinkedList { public typealias Node = LinkedListNode private var head: Node? - + public var isEmpty: Bool { return head == nil } - + public var first: Node? { return head } } ``` -Ideally, I'd like to put the `LinkedListNode` class inside `LinkedList` but Swift currently doesn't allow generic types to have nested types. Instead we're using a typealias so inside `LinkedList` we can write the shorter `Node` instead of `LinkedListNode`. +Ideally, we would want a class name to be as descriptive as possible, yet, we don't want to type a long name every time we want to use the class, therefore, we're using a typealias so inside `LinkedList` we can write the shorter `Node` instead of `LinkedListNode`. This linked list only has a `head` pointer, not a tail. Adding a tail pointer is left as an exercise for the reader. (I'll point out which functions would be different if we also had a tail pointer.) @@ -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) +Let's write a method that lets you insert a new node at any index in the list. - return (prev, next) - } +```swift + 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 + } +} ``` -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. +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: -> **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. + new.next = head + head.previous = new + + +---------+ +---------+ +---------+ + new --->| |--> head -->| |---->| |-----//-----> + | C | | A | | B | + | |<-----------| |<----| |<----//------ + +---------+ +---------+ +---------+ -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: +Finally, replace the head with the new node. -```swift - public func insert(value: T, atIndex index: Int) { - let (prev, next) = nodesBeforeAndAfter(index) // 1 + head = new - let newNode = Node(value: value) // 2 - newNode.previous = prev - newNode.next = next - prev?.next = newNode - next?.previous = newNode - - if prev == nil { // 3 - head = newNode - } - } -``` + +---------+ +---------+ +---------+ + 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 -Some remarks about this method: +Now insert new node between the prev and the next. -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. + new.prev = prev; prev.next = new // connect prev and new. + new.next = next; next.prev = new // connect new and 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. + +---------+ +---------+ +---------+ +---------+ + head --->| |---//--->| |---->| |---->| | + | | | A | | C | | B | + nil <---| |---//<---| |<----| |<----| | + +---------+ +---------+ +---------+ +---------+ + [0] [index-1] [index] [index+1] + ^ ^ ^ + | | | + prev new next -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: @@ -364,20 +387,20 @@ What else do we need? Removing nodes, of course! First we'll do `removeAll()`, w If you had a tail pointer, you'd set it to `nil` here too. -Next we'll add some functions that let you remove individual nodes. If you already have a reference to the node, then using `removeNode()` is the most optimal because you don't need to iterate through the list to find the node first. +Next we'll add some functions that let you remove individual nodes. If you already have a reference to the node, then using `remove()` is the most optimal because you don't need to iterate through the list to find the node first. ```swift 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 @@ -412,7 +435,7 @@ If you don't have a reference to the node, you can use `removeLast()` or `remove } ``` -All these removal functions also return the value from the removed element. +All these removal functions also return the value from the removed element. ```swift list.removeLast() // "World" @@ -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)**. @@ -543,4 +646,4 @@ When performing operations on a linked list, you always need to be careful to up When processing lists, you can often use recursion: process the first element and then recursively call the function again on the rest of the list. You’re done when there is no next element. This is why linked lists are the foundation of functional programming languages such as LISP. -*Written for Swift Algorithm Club by Matthijs Hollemans* +*Originally written by Matthijs Hollemans for Ray Wenderlich's Swift Algorithm Club* diff --git a/Linked List/Tests/LinkedListTests.swift b/Linked List/Tests/LinkedListTests.swift index 367ec8c6b..b16af05ba 100755 --- a/Linked List/Tests/LinkedListTests.swift +++ b/Linked List/Tests/LinkedListTests.swift @@ -1,251 +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 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) - } } 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 2952fc299..ca1347890 100644 --- a/Merge Sort/MergeSort.playground/Contents.swift +++ b/Merge Sort/MergeSort.playground/Contents.swift @@ -12,32 +12,28 @@ func merge(leftPile: [T], rightPile: [T]) -> [T] { var leftIndex = 0 var rightIndex = 0 var orderedPile = [T]() + if orderedPile.capacity < leftPile.count + rightPile.count { + 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 } @@ -46,8 +42,6 @@ let sortedArray = mergeSort(array) let array2 = ["Tom", "Harry", "Ron", "Chandler", "Monica"] let sortedArray2 = mergeSort(array2) - - /* Bottom-up iterative version */ func mergeSortBottomUp(_ a: [T], _ isOrderedBefore: (T, T) -> Bool) -> [T] { 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 f77d1cd84..00e35f498 100644 --- a/Merge Sort/MergeSort.swift +++ b/Merge Sort/MergeSort.swift @@ -17,32 +17,30 @@ func mergeSort(_ array: [T]) -> [T] { func merge(leftPile: [T], rightPile: [T]) -> [T] { var leftIndex = 0 var rightIndex = 0 - var orderedPile = [T]() - - 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 - } + var orderedPile: [T] = [] + if orderedPile.capacity < leftPile.count + rightPile.count { + orderedPile.reserveCapacity(leftPile.count + rightPile.count) } - 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 8d97dc409..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) \ No newline at end of file +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 9dca23996..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 9dca23996..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 } - - 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 + 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 + } + + 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.. 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 + 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 + } + + 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/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 d651ddf2e..5db0aeb0d --- a/Minimum Edit Distance/MinimumEditDistance.playground/Contents.swift +++ b/Minimum Edit Distance/MinimumEditDistance.playground/Contents.swift @@ -1,8 +1,11 @@ + +// 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 @@ -17,15 +20,15 @@ extension String { } // 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/Pages/Minimum spanning tree example.xcplaygroundpage/Contents.swift b/Minimum Spanning Tree (Unweighted)/MinimumSpanningTree.playground/Pages/Minimum spanning tree example.xcplaygroundpage/Contents.swift index 86fe27df8..8ad04379c 100644 --- a/Minimum Spanning Tree (Unweighted)/MinimumSpanningTree.playground/Pages/Minimum spanning tree example.xcplaygroundpage/Contents.swift +++ b/Minimum Spanning Tree (Unweighted)/MinimumSpanningTree.playground/Pages/Minimum spanning tree example.xcplaygroundpage/Contents.swift @@ -21,12 +21,10 @@ func breadthFirstSearchMinimumSpanningTree(_ graph: Graph, source: Node) -> Grap return minimumSpanningTree } - /*: ![Graph](Minimum_Spanning_Tree.png) */ - let graph = Graph() let nodeA = graph.addNode("a") 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)/README.markdown b/Minimum Spanning Tree (Unweighted)/README.markdown index cfd29c987..23f18c335 100644 --- a/Minimum Spanning Tree (Unweighted)/README.markdown +++ b/Minimum Spanning Tree (Unweighted)/README.markdown @@ -16,7 +16,7 @@ Drawn as a more conventional tree it looks like this: ![An actual tree](Images/Tree.png) -To calculate the minimum spanning tree on an unweighted graph, we can use the [breadth-first search](../Breadth-First Search/) algorithm. Breadth-first search starts at a source node and traverses the graph by exploring the immediate neighbor nodes first, before moving to the next level neighbors. If we tweak this algorithm by selectively removing edges, then it can convert the graph into the minimum spanning tree. +To calculate the minimum spanning tree on an unweighted graph, we can use the [breadth-first search](../Breadth-First%20Search/) algorithm. Breadth-first search starts at a source node and traverses the graph by exploring the immediate neighbor nodes first, before moving to the next level neighbors. If we tweak this algorithm by selectively removing edges, then it can convert the graph into the minimum spanning tree. Let's step through the example. We start with the source node `a`, add it to a queue and mark it as visited. @@ -185,6 +185,6 @@ print(minimumSpanningTree) // [node: a edges: ["b", "h"]] // [node: h edges: ["g", "i"]] ``` -> **Note:** On an unweighed graph, any spanning tree is always a minimal spanning tree. This means you can also use a [depth-first search](../Depth-First Search) to find the minimum spanning tree. +> **Note:** On an unweighed graph, any spanning tree is always a minimal spanning tree. This means you can also use a [depth-first search](../Depth-First%20Search) to find the minimum spanning tree. *Written by [Chris Pilcher](https://github.com/chris-pilcher) and Matthijs Hollemans* 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) { + 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) + } + } + + return (cost: cost, tree: tree) +} diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/Contents.swift b/Minimum Spanning Tree/MinimumSpanningTree.playground/Contents.swift new file mode 100644 index 000000000..7e3532429 --- /dev/null +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/Contents.swift @@ -0,0 +1,93 @@ +/** + The following code demonstrates getting MST of the graph below by both + Kruskal's and Prim's algorithms. + */ + +func minimumSpanningTreeKruskal(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) + } + } + + 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) + } + + 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) +} + +/*: + ![Graph](mst.png) + */ + +var graph = Graph() +graph.addEdge(vertex1: 1, vertex2: 2, weight: 6) +graph.addEdge(vertex1: 1, vertex2: 3, weight: 1) +graph.addEdge(vertex1: 1, vertex2: 4, weight: 5) +graph.addEdge(vertex1: 2, vertex2: 3, weight: 5) +graph.addEdge(vertex1: 2, vertex2: 5, weight: 3) +graph.addEdge(vertex1: 3, vertex2: 4, weight: 5) +graph.addEdge(vertex1: 3, vertex2: 5, weight: 6) +graph.addEdge(vertex1: 3, vertex2: 6, weight: 4) +graph.addEdge(vertex1: 4, vertex2: 6, weight: 2) +graph.addEdge(vertex1: 5, vertex2: 6, weight: 6) + +print("===== Kruskal's =====") +let result1 = minimumSpanningTreeKruskal(graph: graph) +print("Minimum spanning tree total weight: \(result1.cost)") +print("Minimum spanning tree:") +print(result1.tree) + +print("===== Prim's =====") +let result2 = minimumSpanningTreePrim(graph: graph) +print("Minimum spanning tree total weight: \(result2.cost)") +print("Minimum spanning tree:") +print(result2.tree) diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/Resources/mst.png b/Minimum Spanning Tree/MinimumSpanningTree.playground/Resources/mst.png new file mode 100644 index 000000000..2138fd535 Binary files /dev/null and b/Minimum Spanning Tree/MinimumSpanningTree.playground/Resources/mst.png differ diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Graph.swift b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Graph.swift new file mode 100644 index 000000000..f6e4d4fc1 --- /dev/null +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Graph.swift @@ -0,0 +1,48 @@ +// Undirected edge +public struct Edge: CustomStringConvertible { + public let vertex1: T + public let vertex2: T + public let weight: Int + + public var description: String { + return "[\(vertex1)-\(vertex2), \(weight)]" + } +} + +// Undirected weighted graph +public struct Graph: CustomStringConvertible { + + public private(set) var edgeList: [Edge] + public private(set) var vertices: Set + public private(set) var adjList: [T: [(vertex: T, weight: Int)]] + + public init() { + edgeList = [Edge]() + vertices = Set() + adjList = [T: [(vertex: T, weight: Int)]]() + } + + public var description: String { + var description = "" + for edge in edgeList { + description += edge.description + "\n" + } + return description + } + + public mutating func addEdge(vertex1 v1: T, vertex2 v2: T, weight w: Int) { + edgeList.append(Edge(vertex1: v1, vertex2: v2, weight: w)) + vertices.insert(v1) + vertices.insert(v2) + + adjList[v1] = adjList[v1] ?? [] + adjList[v1]?.append((vertex: v2, weight: w)) + + adjList[v2] = adjList[v2] ?? [] + adjList[v2]?.append((vertex: v1, weight: w)) + } + + public mutating func addEdge(_ edge: Edge) { + addEdge(vertex1: edge.vertex1, vertex2: edge.vertex2, weight: edge.weight) + } +} diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Heap.swift b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Heap.swift new file mode 100644 index 000000000..8a2c2aa07 --- /dev/null +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/Heap.swift @@ -0,0 +1,227 @@ +// +// Heap.swift +// Written for the Swift Algorithm Club by Kevin Randrup and Matthijs Hollemans +// + +public struct Heap { + /** 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 + + /** + * 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. + */ + public init(sort: @escaping (T, T) -> Bool) { + self.isOrderedBefore = 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. + */ + public init(array: [T], sort: @escaping (T, T) -> Bool) { + self.isOrderedBefore = sort + buildHeap(fromArray: 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. + * 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) + } + } + + public var isEmpty: Bool { + return elements.isEmpty + } + + public var count: Int { + return elements.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 { + 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 { + 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 { + 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 + } + + /** + * 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) + } + + 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. + */ + 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) + } + + /** + * 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() + } 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() + 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. + */ + public mutating func removeAt(_ index: Int) -> T? { + guard index < elements.count else { return nil } + + let size = elements.count - 1 + if index != size { + elements.swapAt(index,size) + shiftDown(index, heapSize: size) + shiftUp(index) + } + 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(ofIndex: childIndex) + + while childIndex > 0 && isOrderedBefore(child, elements[parentIndex]) { + elements[childIndex] = elements[parentIndex] + childIndex = parentIndex + parentIndex = self.parentIndex(ofIndex: childIndex) + } + + elements[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 } + + elements.swapAt(parentIndex,first) + parentIndex = first + } + } +} + +// 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 } + return nil + } +} diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/PriorityQueue.swift b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/PriorityQueue.swift new file mode 100644 index 000000000..92f7b6d3f --- /dev/null +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/PriorityQueue.swift @@ -0,0 +1,58 @@ +/* + Priority Queue, a queue where the most "important" items are at the front of + the queue. + + The heap is a natural data structure for a priority queue, so this object + simply wraps the Heap struct. + + All operations are O(lg n). + + Just like a heap can be a max-heap or min-heap, the queue can be a max-priority + queue (largest element first) or a min-priority queue (smallest element first). +*/ +public struct PriorityQueue { + fileprivate var heap: Heap + + /* + To create a max-priority queue, supply a > sort function. For a min-priority + queue, use <. + */ + public init(sort: @escaping (T, T) -> Bool) { + heap = Heap(sort: sort) + } + + public var isEmpty: Bool { + return heap.isEmpty + } + + public var count: Int { + return heap.count + } + + public func peek() -> T? { + return heap.peek() + } + + public mutating func enqueue(_ element: T) { + heap.insert(element) + } + + public mutating func dequeue() -> T? { + return heap.remove() + } + + /* + Allows you to change the priority of an element. In a max-priority queue, + the new priority should be larger than the old one; in a min-priority queue + it should be smaller. + */ + public mutating func changePriority(index i: Int, value: T) { + return heap.replace(index: i, value: value) + } +} + +extension PriorityQueue where T: Equatable { + public func index(of element: T) -> Int? { + return heap.index(of: element) + } +} diff --git a/Union-Find/UnionFind.swift b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/UnionFind.swift similarity index 89% rename from Union-Find/UnionFind.swift rename to Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/UnionFind.swift index 506cdc1fe..29ea0a373 100644 --- a/Union-Find/UnionFind.swift +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/Sources/UnionFind.swift @@ -1,17 +1,19 @@ /* - Union-Find Data Structure + Union-Find Data Structure - Performance: - adding new set is almost O(1) - finding set of element is almost O(1) - union sets is almost O(1) -*/ + Performance: + adding new set is almost O(1) + finding set of element is almost O(1) + union sets is almost O(1) + */ public struct UnionFind { private var index = [T: Int]() private var parent = [Int]() private var size = [Int]() + public init() {} + public mutating func addSetWith(_ element: T) { index[element] = parent.count parent.append(parent.count) diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/contents.xcplayground b/Minimum Spanning Tree/MinimumSpanningTree.playground/contents.xcplayground new file mode 100644 index 000000000..89da2d470 --- /dev/null +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Minimum Spanning Tree/MinimumSpanningTree.playground/playground.xcworkspace/contents.xcworkspacedata b/Minimum Spanning Tree/MinimumSpanningTree.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Minimum Spanning Tree/MinimumSpanningTree.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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 new file mode 100644 index 000000000..b05fc1b1f --- /dev/null +++ b/Minimum Spanning Tree/Prim.swift @@ -0,0 +1,45 @@ +// +// Prim.swift +// +// +// Created by xiang xin on 16/3/17. +// +// + +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) + } + + 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) +} diff --git a/Minimum Spanning Tree/README.markdown b/Minimum Spanning Tree/README.markdown new file mode 100644 index 000000000..6ae7a89d9 --- /dev/null +++ b/Minimum Spanning Tree/README.markdown @@ -0,0 +1,104 @@ +# 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. + +### Kruskal's Algorithm +Sort the edges base on weight. Greedily select the smallest one each time and add into the MST as long as it doesn't form a cycle. +Kruskal's algoritm uses [Union Find](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Union-Find) data structure to check whether any additional edge causes a cycle. The logic is to put all connected vertices into the same set (in Union Find's concept). If the two vertices from a new edge do not belong to the same set, then it's safe to add that edge into the MST. + +The following graph demonstrates the steps: + +![Graph](Images/kruskal.png) + +Preparation +```swift +// Initialize the values to be returned and Union Find data structure. +var cost: Int = 0 +var tree = Graph() +var unionFind = UnionFind() +for vertex in graph.vertices { + +// Initially all vertices are disconnected. +// Each of them belongs to it's individual set. + unionFind.addSetWith(vertex) +} +``` + +Sort the edges +```swift +let sortedEdgeListByWeight = graph.edgeList.sorted(by: { $0.weight < $1.weight }) +``` + +Take one edge at a time and try to insert it into the MST. +```swift +for edge in sortedEdgeListByWeight { + let v1 = edge.vertex1 + let v2 = edge.vertex2 + + // Same set means the two vertices of this edge were already connected in the MST. + // Adding this one will cause a cycle. + if !unionFind.inSameSet(v1, and: v2) { + // Add the edge into the MST and update the final cost. + cost += edge.weight + tree.addEdge(edge) + + // Put the two vertices into the same set. + unionFind.unionSetsContaining(v1, and: v2) + } +} +``` +### Prim's Algorithm +Prim's algorithm doesn't pre-sort all edges. Instead, it uses a [Priority Queue](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Priority%20Queue) to maintain a running sorted next-possile vertices. +Starting from one vertex, loop through all unvisited neighbours and enqueue a pair of values for each neighbour - the vertex and the weight of edge connecting current vertex to the neighbour. Each time it greedily select the top of the priority queue (the one with least weight value) and add the edge into the final MST if the enqueued neighbour hasn't been already visited. + +The following graph demonstrates the steps: + +![Graph](Images/prim.png) + +Preparation +```swift +// Initialize the values to be returned and Priority Queue data structure. +var cost: Int = 0 +var tree = Graph() +var visited = Set() + +// In addition to the (neighbour vertex, weight) pair, parent is added for the purpose of printing out the MST later. +// parent is basically current vertex. aka. the previous vertex before neigbour vertex gets visited. +var priorityQueue = PriorityQueue<(vertex: T, weight: Int, parent: T?)>(sort: { $0.weight < $1.weight }) +``` + +Start from any vertex +```swift +priorityQueue.enqueue((vertex: graph.vertices.first!, weight: 0, parent: nil)) +``` + +```swift +// Take from the top of the priority queue ensures getting the least weight edge. +while let head = priorityQueue.dequeue() { + let vertex = head.vertex + if visited.contains(vertex) { + continue + } + + // If the vertex hasn't been visited before, its edge (parent-vertex) is selected for MST. + visited.insert(vertex) + cost += head.weight + if let prev = head.parent { // The first vertex doesn't have a parent. + tree.addEdge(vertex1: prev, vertex2: vertex, weight: head.weight) + } + + // Add all unvisted neighbours into the priority queue. + 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)) + } + } + } +} +``` diff --git a/MinimumCoinChange/LICENSE b/MinimumCoinChange/LICENSE new file mode 100755 index 000000000..fda6e6146 --- /dev/null +++ b/MinimumCoinChange/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Jacopo Mangiavacchi + +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. diff --git a/MinimumCoinChange/Package.swift b/MinimumCoinChange/Package.swift new file mode 100644 index 000000000..749073782 --- /dev/null +++ b/MinimumCoinChange/Package.swift @@ -0,0 +1,7 @@ +// swift-tools-version:3.1 + +import PackageDescription + +let package = Package( + name: "MinimumCoinChange" +) diff --git a/MinimumCoinChange/README.md b/MinimumCoinChange/README.md new file mode 100755 index 000000000..c9cd71cd1 --- /dev/null +++ b/MinimumCoinChange/README.md @@ -0,0 +1,41 @@ +# Minimum Coin Change +Minimum Coin Change problem algorithm implemented in Swift comparing dynamic programming algorithm design to traditional greedy approach. + +Written for Swift Algorithm Club by Jacopo Mangiavacchi + +![Coins](eurocoins.gif) + +# Introduction + +In the traditional coin change problem you have to find all the different ways to change some given money in a particular amount of coins using a given amount of set of coins (i.e. 1 cent, 2 cents, 5 cents, 10 cents etc.). + +For example using Euro cents the total of 4 cents value of money can be changed in these possible ways: + +- Four 1 cent coins +- Two 2 cent coins +- One 2 cent coin and two 1 cent coins + +The minimum coin change problem is a variation of the generic coin change problem where you need to find the best option for changing the money returning the less number of coins. + +For example using Euro cents the best possible change for 4 cents are two 2 cent coins with a total of two coins. + + +# Greedy Solution + +A simple approach for implementing the Minimum Coin Change algorithm in a very efficient way is to start subtracting from the input value the greater possible coin value from the given amount of set of coins available and iterate subtracting the next greater possible coin value on the resulting difference. + +For example from the total of 4 Euro cents of the example above you can subtract initially 2 cents as the other biggest coins value (from 5 cents to above) are to bigger for the current 4 Euro cent value. Once used the first 2 cents coin you iterate again with the same logic for the rest of 2 cents and select another 2 cents coin and finally return the two 2 cents coins as the best change. + +Most of the time the result for this greedy approach is optimal but for some set of coins the result will not be the optimal. + +Indeed, if we use the a set of these three different coins set with values 1, 3 and 4 and execute this greedy algorithm for asking the best change for the value 6 we will get one coin of 4 and two coins of 1 instead of two coins of 3. + + +# Dynamic Programming Solution + +A classic dynamic programming strategy will iterate selecting in order a possible coin from the given amount of set of coins and finding using recursive calls the minimum coin change on the difference from the passed value and the selected coin. For any interaction the algorithm select from all possible combinations the one with the less number of coins used. + +The dynamic programming approach will always select the optimal change but it will require a number of steps that is at least quadratic in the goal amount to change. + +In this Swift implementation in order to optimize the overall performance we use an internal data structure for caching the result for best minimum coin change for previous values. + diff --git a/MinimumCoinChange/Sources/MinimumCoinChange.swift b/MinimumCoinChange/Sources/MinimumCoinChange.swift new file mode 100644 index 000000000..c83708dee --- /dev/null +++ b/MinimumCoinChange/Sources/MinimumCoinChange.swift @@ -0,0 +1,89 @@ +// +// Minimum Coin Change Problem Playground +// Compare Greedy Algorithm and Dynamic Programming Algorithm in Swift +// +// Created by Jacopo Mangiavacchi on 04/03/17. +// + +import Foundation + +public enum MinimumCoinChangeError: Error { + case noRestPossibleForTheGivenValue +} + +public struct MinimumCoinChange { + internal let sortedCoinSet: [Int] + + public init(coinSet: [Int]) { + self.sortedCoinSet = coinSet.sorted(by: { $0 > $1}) + } + + //Greedy Algorithm + public func changeGreedy(_ value: Int) throws -> [Int] { + guard value > 0 else { return [] } + + var change: [Int] = [] + var newValue = value + + for coin in sortedCoinSet { + while newValue - coin >= 0 { + change.append(coin) + newValue -= coin + } + + if newValue == 0 { + break + } + } + + if newValue > 0 { + throw MinimumCoinChangeError.noRestPossibleForTheGivenValue + } + + return change + } + + //Dynamic Programming Algorithm + public func changeDynamic(_ value: Int) throws -> [Int] { + guard value > 0 else { return [] } + + var cache: [Int : [Int]] = [:] + + func _changeDynamic(_ value: Int) -> [Int] { + guard value > 0 else { return [] } + + if let cached = cache[value] { + return cached + } + + var potentialChangeArray: [[Int]] = [] + + for coin in sortedCoinSet { + if value - coin >= 0 { + var potentialChange: [Int] = [coin] + potentialChange.append(contentsOf: _changeDynamic(value - coin)) + + if potentialChange.reduce(0, +) == value { + potentialChangeArray.append(potentialChange) + } + } + } + + if potentialChangeArray.count > 0 { + let sortedPotentialChangeArray = potentialChangeArray.sorted(by: { $0.count < $1.count }) + cache[value] = sortedPotentialChangeArray[0] + return sortedPotentialChangeArray[0] + } + + return [] + } + + let change: [Int] = _changeDynamic(value) + + if change.reduce(0, +) != value { + throw MinimumCoinChangeError.noRestPossibleForTheGivenValue + } + + return change + } +} diff --git a/MinimumCoinChange/Tests/LinuxMain.swift b/MinimumCoinChange/Tests/LinuxMain.swift new file mode 100644 index 000000000..db61c6011 --- /dev/null +++ b/MinimumCoinChange/Tests/LinuxMain.swift @@ -0,0 +1,6 @@ +import XCTest +@testable import MinimumCoinChangeTests + +XCTMain([ + testCase(MinimumCoinChangeTests.allTests), +]) diff --git a/MinimumCoinChange/Tests/MinimumCoinChangeTests/MinimumCoinChangeTests.swift b/MinimumCoinChange/Tests/MinimumCoinChangeTests/MinimumCoinChangeTests.swift new file mode 100644 index 000000000..45beff775 --- /dev/null +++ b/MinimumCoinChange/Tests/MinimumCoinChangeTests/MinimumCoinChangeTests.swift @@ -0,0 +1,35 @@ +import XCTest +@testable import MinimumCoinChange + +class MinimumCoinChangeTests: XCTestCase { + 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), + ] +} diff --git a/MinimumCoinChange/eurocoins.gif b/MinimumCoinChange/eurocoins.gif new file mode 100644 index 000000000..2d9e81e55 Binary files /dev/null and b/MinimumCoinChange/eurocoins.gif differ 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 1a0320a65..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]() @@ -49,7 +51,7 @@ public struct OrderedArray { private func findInsertionPoint(_ newElement: T) -> Int { var startIndex = 0 var endIndex = array.count - + while startIndex < endIndex { let midIndex = startIndex + (endIndex - startIndex) / 2 if array[midIndex] == newElement { @@ -70,8 +72,6 @@ extension OrderedArray: CustomStringConvertible { } } - - var a = OrderedArray(array: [5, 1, 3, 9, 7, -1]) a // [-1, 1, 3, 5, 7, 9] 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 Array/README.markdown b/Ordered Array/README.markdown index ca088c325..42c9880a3 100644 --- a/Ordered Array/README.markdown +++ b/Ordered Array/README.markdown @@ -83,7 +83,7 @@ a // [-2, -1, 1, 3, 4, 5, 7, 9, 10] The array's contents will always be sorted from low to high, now matter what. -Unfortunately, the current `findInsertionPoint()` function is a bit slow. In the worst case, it needs to scan through the entire array. We can speed this up by using a [binary search](../Binary Search) to find the insertion point. +Unfortunately, the current `findInsertionPoint()` function is a bit slow. In the worst case, it needs to scan through the entire array. We can speed this up by using a [binary search](../Binary%20Search) to find the insertion point. Here is the new version: @@ -110,4 +110,6 @@ The big difference with a regular binary search is that this doesn't return `nil Note that using binary search doesn't change the worst-case running time complexity of `insert()`. The binary search itself takes only **O(log n)** time, but inserting a new object in the middle of an array still involves shifting all remaining elements in memory. So overall, the time complexity is still **O(n)**. But in practice this new version definitely is a lot faster, especially on large arrays. +A more complete and production ready [SortedArray](https://github.com/ole/SortedArray) is avalible from [Ole Begemann](https://github.com/ole). The [accompanying article](https://oleb.net/blog/2017/02/sorted-array/) explains the advantages and tradeoffs. + *Written for Swift Algorithm Club by Matthijs Hollemans* diff --git a/Ordered Set/OrderedSet.playground/Contents.swift b/Ordered Set/OrderedSet.playground/Contents.swift new file mode 100644 index 000000000..6a2415231 --- /dev/null +++ b/Ordered Set/OrderedSet.playground/Contents.swift @@ -0,0 +1,22 @@ +let s = OrderedSet() + +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 0525a31ed..82d736731 100644 --- a/Ordered Set/README.markdown +++ b/Ordered Set/README.markdown @@ -1,332 +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). +Here is the example about how it works ```swift -public struct OrderedSet { - private var internalSet = [T]() - - // Returns the number of elements in the OrderedSet. - public var count: Int { - return internalSet.count - } - ... -``` +let s = AppleOrderedSet() -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. +s.add(1) +s.add(2) +s.add(-1) +s.add(0) +s.insert(4, at: 3) -```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) - } -``` +print(s.all()) // [1, 2, -1, 4, 0] -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.set(-1, at: 0) // We already have -1 in index: 2, so we will do nothing here -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.remove(-1) -Next up is the `remove()` function: +print(s.all()) // [1, 2, 4, 0] -```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 Search). - -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 ordered set of `Player` objects. Each `Player` has a name and a number of points: +print(s.object(at: 1)) // 2 - [ ("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: - -```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 +print(s.object(at: 2)) // 4 ``` -These loops start at the current `mid` value and then look at the neighboring values until we've found the correct object. +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. -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)**: +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 - // 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] - } +var indexOfKey: [T: Int] +var objects: [T] ``` -## Examples +`indexOfKey` is used to track the index of the element. `objects` is array holding elements. -Below are a few examples that can be found in the playground file. +We will go through some key functions details here. -### Example 1 +### Add -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. - -*Written By Zain Humayun* +*Written By Kai Chen* diff --git a/Palindromes/Palindromes.playground/Contents.swift b/Palindromes/Palindromes.playground/Contents.swift index e8562a367..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 2760137ca..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 90% rename from Palindromes/Test/Palindromes.swift rename to Palindromes/Test/Palindrome.swift index 2760137ca..16af14fdb 100644 --- a/Palindromes/Test/Palindromes.swift +++ b/Palindromes/Test/Palindrome.swift @@ -2,8 +2,8 @@ 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) } @@ -14,13 +14,13 @@ 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/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/README.markdown b/Priority Queue/README.markdown index 8308ec16f..fbbcaa0f3 100644 --- a/Priority Queue/README.markdown +++ b/Priority Queue/README.markdown @@ -12,7 +12,7 @@ Examples of algorithms that can benefit from a priority queue: - Event-driven simulations. Each event is given a timestamp and you want events to be performed in order of their timestamps. The priority queue makes it easy to find the next event that needs to be simulated. - Dijkstra's algorithm for graph searching uses a priority queue to calculate the minimum cost. -- [Huffman coding](../Huffman Coding/) for data compression. This algorithm builds up a compression tree. It repeatedly needs to find the two nodes with the smallest frequencies that do not have a parent node yet. +- [Huffman coding](../Huffman%20Coding/) for data compression. This algorithm builds up a compression tree. It repeatedly needs to find the two nodes with the smallest frequencies that do not have a parent node yet. - A* pathfinding for artificial intelligence. - Lots of other places! @@ -31,8 +31,8 @@ Common operations on a priority queue: There are different ways to implement priority queues: -- As a [sorted array](../Ordered Array/). The most important item is at the end of the array. Downside: inserting new items is slow because they must be inserted in sorted order. -- As a balanced [binary search tree](../Binary Search Tree/). This is great for making a double-ended priority queue because it implements both "find minimum" and "find maximum" efficiently. +- As a [sorted array](../Ordered%20Array/). The most important item is at the end of the array. Downside: inserting new items is slow because they must be inserted in sorted order. +- As a balanced [binary search tree](../Binary%20Search%20Tree/). This is great for making a double-ended priority queue because it implements both "find minimum" and "find maximum" efficiently. - As a [heap](../Heap/). The heap is a natural data structure for a priority queue. In fact, the two terms are often used as synonyms. A heap is more efficient than a sorted array because a heap only has to be partially sorted. All heap operations are **O(log n)**. Here's a Swift priority queue based on a heap: 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 77423c57a..5ddfd574d 100644 --- a/Priority Queue/Tests/Tests.xcodeproj/project.pbxproj +++ b/Priority Queue/Tests/Tests.xcodeproj/project.pbxproj @@ -91,7 +91,7 @@ TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; - LastSwiftMigration = 0800; + LastSwiftMigration = 0820; }; }; }; @@ -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/Images/quadtree.png b/QuadTree/Images/quadtree.png new file mode 100644 index 000000000..53af6fb08 Binary files /dev/null and b/QuadTree/Images/quadtree.png differ diff --git a/QuadTree/QuadTree.playground/Contents.swift b/QuadTree/QuadTree.playground/Contents.swift new file mode 100644 index 000000000..ad8ee31f8 --- /dev/null +++ b/QuadTree/QuadTree.playground/Contents.swift @@ -0,0 +1,13 @@ +import Foundation + +let tree = QuadTree(rect: Rect(origin: Point(0, 0), size: Size(xLength: 10, yLength: 10))) + +for _ in 0..<40 { + let randomX = Double(arc4random_uniform(100)) / 10 + let randomY = Double(arc4random_uniform(100)) / 10 + let point = Point(randomX, randomY) + tree.add(point: point) +} + +print(tree) +print(tree.points(inRect: Rect(origin: Point(1, 1), size: Size(xLength: 5, yLength: 5)))) diff --git a/QuadTree/QuadTree.playground/Sources/QuadTree.swift b/QuadTree/QuadTree.playground/Sources/QuadTree.swift new file mode 100644 index 000000000..7fd459e97 --- /dev/null +++ b/QuadTree/QuadTree.playground/Sources/QuadTree.swift @@ -0,0 +1,289 @@ +public struct Point { + let x: Double + let y: Double + + public init(_ x: Double, _ y: Double) { + self.x = x + self.y = y + } +} + +extension Point: CustomStringConvertible { + public var description: String { + return "Point(\(x), \(y))" + } +} + +public struct Size: CustomStringConvertible { + var xLength: Double + var yLength: Double + + public init(xLength: Double, yLength: Double) { + precondition(xLength >= 0, "xLength can not be negative") + precondition(yLength >= 0, "yLength can not be negative") + self.xLength = xLength + self.yLength = yLength + } + + var half: Size { + return Size(xLength: xLength / 2, yLength: yLength / 2) + } + + public var description: String { + return "Size(\(xLength), \(yLength))" + } +} + +public struct Rect { + // left top vertice + var origin: Point + var size: Size + + public init(origin: Point, size: Size) { + self.origin = origin + self.size = size + } + + var minX: Double { + return origin.x + } + + var minY: Double { + return origin.y + } + + var maxX: Double { + return origin.x + size.xLength + } + + var maxY: Double { + return origin.y + size.yLength + } + + func containts(point: Point) -> Bool { + return (minX <= point.x && point.x <= maxX) && + (minY <= point.y && point.y <= maxY) + } + + var leftTopRect: Rect { + return Rect(origin: origin, size: size.half) + } + + var leftBottomRect: Rect { + return Rect(origin: Point(origin.x, origin.y + size.half.yLength), size: size.half) + } + + var rightTopRect: Rect { + return Rect(origin: Point(origin.x + size.half.xLength, origin.y), size: size.half) + } + + var rightBottomRect: Rect { + return Rect(origin: Point(origin.x + size.half.xLength, origin.y + size.half.yLength), size: size.half) + } + + func intersects(rect: Rect) -> Bool { + + func lineSegmentsIntersect(lStart: Double, lEnd: Double, rStart: Double, rEnd: Double) -> Bool { + return max(lStart, rStart) <= min(lEnd, rEnd) + } + // to intersect, both horizontal and vertical projections need to intersect + // horizontal + if !lineSegmentsIntersect(lStart: minX, lEnd: maxX, rStart: rect.minX, rEnd: rect.maxX) { + return false + } + + // vertical + return lineSegmentsIntersect(lStart: minY, lEnd: maxY, rStart: rect.minY, rEnd: rect.maxY) + } +} + +extension Rect: CustomStringConvertible { + public var description: String { + return "Rect(\(origin), \(size))" + } +} + +protocol PointsContainer { + func add(point: Point) -> Bool + func points(inRect rect: Rect) -> [Point] +} + +class QuadTreeNode { + + enum NodeType { + case leaf + case `internal`(children: Children) + } + + struct Children: Sequence { + let leftTop: QuadTreeNode + let leftBottom: QuadTreeNode + let rightTop: QuadTreeNode + let rightBottom: QuadTreeNode + + init(parentNode: QuadTreeNode) { + leftTop = QuadTreeNode(rect: parentNode.rect.leftTopRect) + leftBottom = QuadTreeNode(rect: parentNode.rect.leftBottomRect) + rightTop = QuadTreeNode(rect: parentNode.rect.rightTopRect) + rightBottom = QuadTreeNode(rect: parentNode.rect.rightBottomRect) + } + + struct ChildrenIterator: IteratorProtocol { + private var index = 0 + private let children: Children + + init(children: Children) { + self.children = children + } + + mutating func next() -> QuadTreeNode? { + + defer { index += 1 } + + switch index { + case 0: return children.leftTop + case 1: return children.leftBottom + case 2: return children.rightTop + case 3: return children.rightBottom + default: return nil + } + } + } + + public func makeIterator() -> ChildrenIterator { + return ChildrenIterator(children: self) + } + } + + var points: [Point] = [] + let rect: Rect + var type: NodeType = .leaf + + static let maxPointCapacity = 3 + + init(rect: Rect) { + self.rect = rect + } + + 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 + } +} + +extension QuadTreeNode: PointsContainer { + + @discardableResult + func add(point: Point) -> Bool { + if !rect.containts(point: point) { + return false + } + + switch type { + case .internal(let children): + // pass the point to one of the children + for child in children { + if child.add(point: point) { + return true + } + } + + fatalError("rect.containts evaluted to true, but none of the children added the point") + case .leaf: + points.append(point) + // if the max capacity was reached, become an internal node + if points.count == QuadTreeNode.maxPointCapacity { + subdivide() + } + } + return true + } + + private func subdivide() { + switch type { + case .leaf: + type = .internal(children: Children(parentNode: self)) + case .internal: + preconditionFailure("Calling subdivide on an internal node") + } + } + + 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) { + result.append(point) + } + } + + switch type { + case .leaf: + break + case .internal(let children): + // recursively add children's points that lie in the rect + for childNode in children { + result.append(contentsOf: childNode.points(inRect: rect)) + } + } + + return result + } +} + +extension QuadTreeNode: CustomStringConvertible { + var description: String { + switch type { + case .leaf: + return "leaf \(rect) Points: \(points)" + case .internal: + return "parent \(rect) Points: \(points)" + } + } +} + +public class QuadTree: PointsContainer { + + let root: QuadTreeNode + + public init(rect: Rect) { + self.root = QuadTreeNode(rect: rect) + } + + @discardableResult + public func add(point: Point) -> Bool { + return root.add(point: point) + } + + public func points(inRect rect: Rect) -> [Point] { + return root.points(inRect: rect) + } +} + +extension QuadTree: CustomStringConvertible { + public var description: String { + return "Quad tree\n" + root.recursiveDescription + } +} diff --git a/QuadTree/QuadTree.playground/contents.xcplayground b/QuadTree/QuadTree.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/QuadTree/QuadTree.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/QuadTree/QuadTree.playground/playground.xcworkspace/contents.xcworkspacedata b/QuadTree/QuadTree.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/QuadTree/QuadTree.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/QuadTree/README.md b/QuadTree/README.md new file mode 100644 index 000000000..ff660f852 --- /dev/null +++ b/QuadTree/README.md @@ -0,0 +1,159 @@ +# QuadTree + +A quadtree is a [tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Tree) in which each internal (not leaf) node has four children. + + + +### 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. + +### A Better Approach + +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. + +```swift +class QuadTreeNode { + + enum NodeType { + case leaf + case `internal`(children: Children) + } + + struct Children { + let leftTop: QuadTreeNode + let leftBottom: QuadTreeNode + let rightTop: QuadTreeNode + let rightBottom: QuadTreeNode + + ... + } + + var points: [Point] = [] + let rect: Rect + var type: NodeType = .leaf + + static let maxPointCapacity = 3 + + init(rect: Rect) { + self.rect = rect + } + + ... +} + +``` +Once the limit in a leaf node is reached, four child nodes are added to the node and they represent `topLeft`, `topRight`, `bottomLeft`, `bottomRight` quadrants of the node's rect; each of the consequent points in the rect will be passed to one of the children. Thus, new points are always added to leaf nodes. + +```swift +extension QuadTreeNode { + + @discardableResult + func add(point: Point) -> Bool { + + if !rect.contains(point: point) { + return false + } + + switch type { + case .internal(let children): + // pass the point to one of the children + for child in children { + if child.add(point: point) { + return true + } + } + return false // should never happen + case .leaf: + points.append(point) + // if the max capacity was reached, become an internal node + if points.count == QuadTreeNode.maxPointCapacity { + subdivide() + } + } + return true + } + + private func subdivide() { + switch type { + case .leaf: + type = .internal(children: Children(parentNode: self)) + case .internal: + preconditionFailure("Calling subdivide on an internal node") + } + } +} + +extension Children { + + init(parentNode: QuadTreeNode) { + leftTop = QuadTreeNode(rect: parentNode.rect.leftTopRect) + leftBottom = QuadTreeNode(rect: parentNode.rect.leftBottomRect) + rightTop = QuadTreeNode(rect: parentNode.rect.rightTopRect) + rightBottom = QuadTreeNode(rect: parentNode.rect.rightBottomRect) + } +} + +``` + +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. + +```swift + +class QuadTree { + + ... + + let root: QuadTreeNode + + public func points(inRect rect: Rect) -> [Point] { + return root.points(inRect: rect) + } +} + +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.contains(point: point) { + result.append(point) + } + } + + switch type { + case .leaf: + break + case .internal(children: let children): + // recursively add children's points that lie in the rect + for childNode in children { + result.append(contentsOf: childNode.points(inRect: rect)) + } + } + + return result + } +} + +``` + +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 + +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 [Wikipedia](https://en.wikipedia.org/wiki/Quadtree) + +*Written for Swift Algorithm Club by Timur Galimov* diff --git a/QuadTree/Tests/Tests.xcodeproj/project.pbxproj b/QuadTree/Tests/Tests.xcodeproj/project.pbxproj new file mode 100644 index 000000000..a972bf534 --- /dev/null +++ b/QuadTree/Tests/Tests.xcodeproj/project.pbxproj @@ -0,0 +1,274 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 82EF3AAE1E50E77800013ECB /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82EF3AAD1E50E77800013ECB /* Tests.swift */; }; + 82EF3AB51E50E82400013ECB /* QuadTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82EF3AB41E50E82400013ECB /* QuadTree.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 82EF3AAA1E50E77800013ECB /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 82EF3AAD1E50E77800013ECB /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; + 82EF3AAF1E50E77800013ECB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 82EF3AB41E50E82400013ECB /* QuadTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QuadTree.swift; path = ../QuadTree.playground/Sources/QuadTree.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 82EF3AA71E50E77800013ECB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 82EF3A9F1E50E74900013ECB = { + isa = PBXGroup; + children = ( + 82EF3AB41E50E82400013ECB /* QuadTree.swift */, + 82EF3AAC1E50E77800013ECB /* Tests */, + 82EF3AAB1E50E77800013ECB /* Products */, + ); + sourceTree = ""; + }; + 82EF3AAB1E50E77800013ECB /* Products */ = { + isa = PBXGroup; + children = ( + 82EF3AAA1E50E77800013ECB /* Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 82EF3AAC1E50E77800013ECB /* Tests */ = { + isa = PBXGroup; + children = ( + 82EF3AAD1E50E77800013ECB /* Tests.swift */, + 82EF3AAF1E50E77800013ECB /* Info.plist */, + ); + path = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 82EF3AA91E50E77800013ECB /* Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 82EF3AB01E50E77800013ECB /* Build configuration list for PBXNativeTarget "Tests" */; + buildPhases = ( + 82EF3AA61E50E77800013ECB /* Sources */, + 82EF3AA71E50E77800013ECB /* Frameworks */, + 82EF3AA81E50E77800013ECB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Tests; + productName = Tests; + productReference = 82EF3AAA1E50E77800013ECB /* Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 82EF3AA01E50E74900013ECB /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + TargetAttributes = { + 82EF3AA91E50E77800013ECB = { + CreatedOnToolsVersion = 8.2.1; + DevelopmentTeam = 34BUG46ZSN; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 82EF3AA31E50E74900013ECB /* Build configuration list for PBXProject "Tests" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 82EF3A9F1E50E74900013ECB; + productRefGroup = 82EF3AAB1E50E77800013ECB /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 82EF3AA91E50E77800013ECB /* Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 82EF3AA81E50E77800013ECB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 82EF3AA61E50E77800013ECB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 82EF3AB51E50E82400013ECB /* QuadTree.swift in Sources */, + 82EF3AAE1E50E77800013ECB /* Tests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 82EF3AA41E50E74900013ECB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + 82EF3AA51E50E74900013ECB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + 82EF3AB11E50E77800013ECB /* 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 = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 34BUG46ZSN; + 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 = com.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 82EF3AB21E50E77800013ECB /* 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 = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 34BUG46ZSN; + 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 = com.Tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 82EF3AA31E50E74900013ECB /* Build configuration list for PBXProject "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 82EF3AA41E50E74900013ECB /* Debug */, + 82EF3AA51E50E74900013ECB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 82EF3AB01E50E77800013ECB /* Build configuration list for PBXNativeTarget "Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 82EF3AB11E50E77800013ECB /* Debug */, + 82EF3AB21E50E77800013ECB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 82EF3AA01E50E74900013ECB /* Project object */; +} diff --git a/QuadTree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/QuadTree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..6c0ea8493 --- /dev/null +++ b/QuadTree/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/QuadTree/Tests/Tests/Info.plist b/QuadTree/Tests/Tests/Info.plist new file mode 100644 index 000000000..6c6c23c43 --- /dev/null +++ b/QuadTree/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/QuadTree/Tests/Tests/Tests.swift b/QuadTree/Tests/Tests/Tests.swift new file mode 100644 index 000000000..9bd8ad28f --- /dev/null +++ b/QuadTree/Tests/Tests/Tests.swift @@ -0,0 +1,85 @@ +// +// Tests.swift +// Tests +// +// Created by Timur Galimov on 12/02/2017. +// +// + +import XCTest + +extension Point: Equatable { + +} + +public func == (lhs: Point, rhs: Point) -> Bool { + return lhs.x == rhs.x && lhs.y == rhs.y +} + +class Tests: XCTestCase { + + func testRectContains() { + let rect = Rect(origin: Point(0, 0), size: Size(xLength: 3, yLength: 3)) + + XCTAssertTrue(rect.containts(point: Point(1, 1))) + XCTAssertTrue(rect.containts(point: Point(0, 0))) + XCTAssertTrue(rect.containts(point: Point(0, 3))) + XCTAssertTrue(rect.containts(point: Point(3, 3))) + + XCTAssertFalse(rect.containts(point: Point(-1, 1))) + XCTAssertFalse(rect.containts(point: Point(-0.1, 0.1))) + XCTAssertFalse(rect.containts(point: Point(0, 3.1))) + XCTAssertFalse(rect.containts(point: Point(-4, 1))) + } + + func testIntersects() { + let rect = Rect(origin: Point(0, 0), size: Size(xLength: 5, yLength: 5)) + let rect2 = Rect(origin: Point(1, 1), size: Size(xLength: 1, yLength: 1)) + XCTAssertTrue(rect.intersects(rect: rect2)) + + let rect3 = Rect(origin: Point(1, 0), size: Size(xLength: 1, yLength: 10)) + let rect4 = Rect(origin: Point(0, 1), size: Size(xLength: 10, yLength: 1)) + XCTAssertTrue(rect3.intersects(rect: rect4)) + + let rect5 = Rect(origin: Point(0, 0), size: Size(xLength: 4, yLength: 4)) + let rect6 = Rect(origin: Point(2, 2), size: Size(xLength: 4, yLength: 4)) + XCTAssertTrue(rect5.intersects(rect: rect6)) + + let rect7 = Rect(origin: Point(0, 0), size: Size(xLength: 4, yLength: 4)) + let rect8 = Rect(origin: Point(4, 4), size: Size(xLength: 1, yLength: 1)) + XCTAssertTrue(rect7.intersects(rect: rect8)) + + let rect9 = Rect(origin: Point(-1, -1), size: Size(xLength: 0.5, yLength: 0.5)) + let rect10 = Rect(origin: Point(0, 0), size: Size(xLength: 1, yLength: 1)) + XCTAssertFalse(rect9.intersects(rect: rect10)) + + let rect11 = Rect(origin: Point(0, 0), size: Size(xLength: 2, yLength: 1)) + let rect12 = Rect(origin: Point(3, 0), size: Size(xLength: 1, yLength: 1)) + XCTAssertFalse(rect11.intersects(rect: rect12)) + } + + func testQuadTree() { + let rect = Rect(origin: Point(0, 0), size: Size(xLength: 5, yLength: 5)) + let qt = QuadTree(rect: rect) + + XCTAssertTrue(qt.points(inRect: rect) == [Point]()) + + XCTAssertFalse(qt.add(point: Point(-0.1, 0.1))) + + XCTAssertTrue(qt.points(inRect: rect) == [Point]()) + + XCTAssertTrue(qt.add(point: Point(1, 1))) + XCTAssertTrue(qt.add(point: Point(3, 3))) + XCTAssertTrue(qt.add(point: Point(4, 4))) + XCTAssertTrue(qt.add(point: Point(0.5, 0.5))) + + XCTAssertFalse(qt.add(point: Point(5.5, 0))) + XCTAssertFalse(qt.add(point: Point(5.5, 1))) + XCTAssertFalse(qt.add(point: Point(5.5, 2))) + + XCTAssertTrue(qt.add(point: Point(1.5, 1.5))) + + let rect2 = Rect(origin: Point(0, 0), size: Size(xLength: 2, yLength: 2)) + XCTAssertTrue(qt.points(inRect: rect2) == [Point(1, 1), Point(0.5, 0.5), Point(1.5, 1.5)]) + } +} 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.. { } } - - - // Create a queue and put some elements on it already. var queueOfNames = Queue(array: ["Carl", "Lisa", "Stephanie", "Jeff", "Wade"]) diff --git a/Queue/Queue.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Queue/Queue.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Queue/Queue.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Queue/README.markdown b/Queue/README.markdown index f8259c31c..ca55f0f28 100644 --- a/Queue/README.markdown +++ b/Queue/README.markdown @@ -1,12 +1,14 @@ # 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 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. +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. -A queue gives you a FIFO or first-in, first-out order. The element you inserted first is also the first one to come out again. It's only fair! (A very similar data structure, the [stack](../Stack/), is LIFO or last-in first-out.) +A queue gives you a FIFO or first-in, first-out order. The element you inserted first is the first one to come out. It is only fair! (A similar data structure, the [stack](../Stack/), is LIFO or last-in first-out.) -For example, let's enqueue a number: +Here is an example to enqueue a number: ```swift queue.enqueue(10) @@ -38,11 +40,11 @@ queue.dequeue() This returns `3`, the next dequeue returns `57`, and so on. If the queue is empty, dequeuing returns `nil` or in some implementations it gives an error message. -> **Note:** A queue is not always the best choice. If the order in which the items are added and removed from the list isn't important, you might as well use a [stack](../Stack/) instead of a queue. Stacks are simpler and faster. +> **Note:** A queue is not always the best choice. If the order in which the items are added and removed from the list is not important, you can use a [stack](../Stack/) instead of a queue. Stacks are simpler and faster. ## The code -Here is a very simplistic implementation of a queue in Swift. It's just a wrapper around an array that lets you enqueue, dequeue, and peek at the front-most item: +Here is a simplistic implementation of a queue in Swift. It is a wrapper around an array to enqueue, dequeue, and peek at the front-most item: ```swift public struct Queue { @@ -74,11 +76,11 @@ public struct Queue { } ``` -This queue works just fine but it is not optimal. +This queue works well, but it is not optimal. -Enqueuing is an **O(1)** operation because adding to the end of an array always takes the same amount of time, regardless of the size of the array. So that's great. +Enqueuing is an **O(1)** operation because adding to the end of an array always takes the same amount of time regardless of the size of the array. -You might be wondering why appending items to an array is **O(1)**, or a constant-time operation. That is so because an array in Swift always has some empty space at the end. If we do the following: +You might be wondering why appending items to an array is **O(1)** or a constant-time operation. That is because an array in Swift always has some empty space at the end. If we do the following: ```swift var queue = Queue() @@ -95,13 +97,13 @@ where `xxx` is memory that is reserved but not filled in yet. Adding a new eleme [ "Ada", "Steve", "Tim", "Grace", xxx, xxx ] -This is simply matter of copying memory from one place to another, a constant-time operation. +This results by copying memory from one place to another which is a constant-time operation. -Of course, there are only a limited number of such unused spots at the end of the array. When the last `xxx` gets used and you want to add another item, the array needs to resize to make more room. +There are only a limited number of unused spots at the end of the array. When the last `xxx` gets used, and you want to add another item, the array needs to resize to make more room. -Resizing includes allocating new memory and copying all the existing data over to the new array. This is an **O(n)** process, so it's relatively slow. But since it happens only every so often, the time for appending a new element to the end of the array is still **O(1)** on average, or **O(1)** "amortized". +Resizing includes allocating new memory and copying all the existing data over to the new array. This is an **O(n)** process which is relatively slow. Since it happens occasionally, the time for appending a new element to the end of the array is still **O(1)** on average or **O(1)** "amortized". -The story for dequeueing is slightly different. To dequeue we remove the element from the *beginning* of the array, not the end. This is always an **O(n)** operation because it requires all remaining array elements to be shifted in memory. +The story for dequeueing is different. To dequeue, we remove the element from the *beginning* of the array. This is always an **O(n)** operation because it requires all remaining array elements to be shifted in memory. In our example, dequeuing the first element `"Ada"` copies `"Steve"` in the place of `"Ada"`, `"Tim"` in the place of `"Steve"`, and `"Grace"` in the place of `"Tim"`: @@ -112,13 +114,13 @@ In our example, dequeuing the first element `"Ada"` copies `"Steve"` in the plac / / / after [ "Steve", "Tim", "Grace", xxx, xxx, xxx ] -Moving all these elements in memory is always an **O(n)** operation. So with our simple implementation of a queue, enqueuing is efficient but dequeueing leaves something to be desired... +Moving all these elements in memory is always an **O(n)** operation. So with our simple implementation of a queue, enqueuing is efficient, but dequeueing leaves something to be desired... ## A more efficient queue -To make dequeuing more efficient, we can use the same trick of reserving some extra free space, but this time do it at the front of the array. We're going to have to write this code ourselves as the built-in Swift array doesn't support this out of the box. +To make dequeuing efficient, we can also reserve some extra free space but this time at the front of the array. We must write this code ourselves because the built-in Swift array does not support it. -The idea is as follows: whenever we dequeue an item, we don't shift the contents of the array to the front (slow) but mark the item's position in the array as empty (fast). After dequeuing `"Ada"`, the array is: +The main idea is whenever we dequeue an item, we do not shift the contents of the array to the front (slow) but mark the item's position in the array as empty (fast). After dequeuing `"Ada"`, the array is: [ xxx, "Steve", "Tim", "Grace", xxx, xxx ] @@ -126,13 +128,13 @@ After dequeuing `"Steve"`, the array is: [ xxx, xxx, "Tim", "Grace", xxx, xxx ] -These empty spots at the front never get reused for anything, so they're wasting space. Every so often you can trim the array by moving the remaining elements to the front again: +Because these empty spots at the front never get reused, you can periodically trim the array by moving the remaining elements to the front: [ "Tim", "Grace", xxx, xxx, xxx, xxx ] -This trimming procedure involves shifting memory so it's an **O(n)** operation. But because it only happens once in a while, dequeuing is now **O(1)** on average. +This trimming procedure involves shifting memory which is an **O(n)** operation. Because this only happens once in a while, dequeuing is **O(1)** on average. -Here is how you could implement this version of `Queue`: +Here is how you can implement this version of `Queue`: ```swift public struct Queue { @@ -176,9 +178,9 @@ public struct Queue { } ``` -The array now stores objects of type `T?` instead of just `T` because we need some way to mark array elements as being empty. The `head` variable is the index in the array of the front-most object. +The array now stores objects of type `T?` instead of just `T` because we need to mark array elements as being empty. The `head` variable is the index in the array of the front-most object. -Most of the new functionality sits in `dequeue()`. When we dequeue an item, we first set `array[head]` to `nil` to remove the object from the array. Then we increment `head` because now the next item has become the front one. +Most of the new functionality sits in `dequeue()`. When we dequeue an item, we first set `array[head]` to `nil` to remove the object from the array. Then, we increment `head` because the next item has become the front one. We go from this: @@ -190,9 +192,9 @@ to this: [ xxx, "Steve", "Tim", "Grace", xxx, xxx ] head -It's like some weird supermarket where the people in the checkout lane don't shuffle forward towards the cash register, but the cash register moves up the queue. +It is like if in a supermarket the people in the checkout lane do not shuffle forward towards the cash register, but the cash register moves up the queue. -Of course, if we never remove those empty spots at the front then the array will keep growing as we enqueue and dequeue elements. To periodically trim down the array, we do the following: +If we never remove those empty spots at the front then the array will keep growing as we enqueue and dequeue elements. To periodically trim down the array, we do the following: ```swift let percentage = Double(head)/Double(array.count) @@ -202,11 +204,11 @@ Of course, if we never remove those empty spots at the front then the array will } ``` -This calculates the percentage of empty spots at the beginning as a ratio of the total array size. If more than 25% of the array is unused, we chop off that wasted space. However, if the array is small we don't want to resize it all the time, so there must be at least 50 elements in the array before we try to trim it. +This calculates the percentage of empty spots at the beginning as a ratio of the total array size. If more than 25% of the array is unused, we chop off that wasted space. However, if the array is small we do not resize it all the time, so there must be at least 50 elements in the array before we try to trim it. > **Note:** I just pulled these numbers out of thin air -- you may need to tweak them based on the behavior of your app in a production environment. -To test this in a playground, do: +To test this in a playground, do the following: ```swift var q = Queue() @@ -251,12 +253,12 @@ q.array // [{Some "Grace"}] q.count // 1 ``` -The `nil` objects at the front have been removed and the array is no longer wasting space. This new version of `Queue` isn't much more complicated than the first one but dequeuing is now also an **O(1)** operation, just because we were a bit smarter about how we used the array. +The `nil` objects at the front have been removed, and the array is no longer wasting space. This new version of `Queue` is not more complicated than the first one but dequeuing is now also an **O(1)** operation, just because we were aware about how we used the array. ## See also -There are many other ways to create a queue. Alternative implementations use a [linked list](../Linked List/), a [circular buffer](../Ring Buffer/), or a [heap](../Heap/). +There are many ways to create a queue. Alternative implementations use a [linked list](../Linked%20List/), a [circular buffer](../Ring%20Buffer/), or a [heap](../Heap/). -Variations on this theme are [deque](../Deque/), a double-ended queue where you can enqueue and dequeue at both ends, and [priority queue](../Priority Queue/), a sorted queue where the "most important" item is always at the front. +Variations on this theme are [deque](../Deque/), a double-ended queue where you can enqueue and dequeue at both ends, and [priority queue](../Priority%20Queue/), a sorted queue where the "most important" item is always at the front. *Written for Swift Algorithm Club by Matthijs Hollemans* 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: [T]) -> [T] { @@ -23,8 +22,6 @@ func quicksort(_ a: [T]) -> [T] { let list1 = [ 10, 0, 3, 9, 2, 14, 8, 27, 1, 5, 8, -1, 26 ] quicksort(list1) - - // *** Using Lomuto partitioning *** /* @@ -40,12 +37,12 @@ func partitionLomuto(_ a: inout [T], low: Int, high: Int) -> Int var i = low for j in low..(_ a: inout [T], low: Int, high: Int) { quicksortLomuto(&list2, low: 0, high: list2.count - 1) - - // *** Hoare partitioning *** /* @@ -85,7 +80,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 } @@ -106,8 +101,6 @@ func quicksortHoare(_ a: inout [T], low: Int, high: Int) { quicksortHoare(&list3, low: 0, high: list3.count - 1) - - // *** Randomized sorting *** /* Returns a random integer in the range min...max, inclusive. */ @@ -131,8 +124,6 @@ var list4 = [ 10, 0, 3, 9, 2, 14, 8, 27, 1, 5, 8, -1, 26 ] quicksortRandom(&list4, low: 0, high: list4.count - 1) list4 - - // *** Dutch national flag partioning *** /* @@ -141,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 6448dd815..a67122723 100644 --- a/Quicksort/Quicksort.swift +++ b/Quicksort/Quicksort.swift @@ -3,7 +3,7 @@ import Foundation /* Easy to understand but not very efficient. */ -func quicksort(a: [T]) -> [T] { +func quicksort(_ a: [T]) -> [T] { guard a.count > 1 else { return a } let pivot = a[a.count/2] @@ -29,7 +29,7 @@ func quicksort(a: [T]) -> [T] { if the pivot value occurs more than once, its duplicates will be found in the left partition. */ -func partitionLomuto(inout a: [T], low: Int, high: Int) -> Int { +func partitionLomuto(_ a: inout [T], low: Int, high: Int) -> Int { // We always use the highest item as the pivot. let pivot = a[high] @@ -56,7 +56,7 @@ func partitionLomuto(inout a: [T], low: Int, high: Int) -> Int { /* Recursive, in-place version that uses Lomuto's partioning scheme. */ -func quicksortLomuto(inout a: [T], low: Int, high: Int) { +func quicksortLomuto(_ a: inout [T], low: Int, high: Int) { if low < high { let p = partitionLomuto(&a, low: low, high: high) quicksortLomuto(&a, low: low, high: p - 1) @@ -80,7 +80,7 @@ func quicksortLomuto(inout a: [T], low: Int, high: Int) { Hoare scheme is more efficient than Lomuto's partition scheme; it performs fewer swaps. */ -func partitionHoare(inout a: [T], low: Int, high: Int) -> Int { +func partitionHoare(_ a: inout [T], low: Int, high: Int) -> Int { let pivot = a[low] var i = low - 1 var j = high + 1 @@ -90,7 +90,7 @@ func partitionHoare(inout a: [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 } @@ -101,7 +101,7 @@ func partitionHoare(inout a: [T], low: Int, high: Int) -> Int { Recursive, in-place version that uses Hoare's partioning scheme. Because of the choice of pivot, this performs badly if the array is already sorted. */ -func quicksortHoare(inout a: [T], low: Int, high: Int) { +func quicksortHoare(_ a: inout [T], low: Int, high: Int) { if low < high { let p = partitionHoare(&a, low: low, high: high) quicksortHoare(&a, low: low, high: p) @@ -112,7 +112,7 @@ func quicksortHoare(inout a: [T], low: Int, high: Int) { // MARK: - Randomized sort /* Returns a random integer in the range min...max, inclusive. */ -public func random(min min: Int, max: Int) -> Int { +public func random(min: Int, max: Int) -> Int { assert(min < max) return min + Int(arc4random_uniform(UInt32(max - min + 1))) } @@ -121,7 +121,7 @@ public func random(min min: Int, max: Int) -> Int { Uses a random pivot index. On average, this results in a well-balanced split of the input array. */ -func quicksortRandom(inout a: [T], low: Int, high: Int) { +func quicksortRandom(_ a: inout [T], low: Int, high: Int) { if low < high { // Create a random pivot index in the range [low...high]. let pivotIndex = random(min: low, max: high) @@ -142,9 +142,9 @@ func quicksortRandom(inout a: [T], low: Int, high: Int) { 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) { +public func swap(_ a: inout [T], _ i: Int, _ j: Int) { if i != j { - swap(&a[i], &a[j]) + a.swapAt(i, j) } } @@ -165,7 +165,7 @@ public func swap(inout a: [T], _ i: Int, _ j: Int) { Time complexity is O(n), space complexity is O(1). */ -func partitionDutchFlag(inout a: [T], low: Int, high: Int, pivotIndex: Int) -> (Int, Int) { +func partitionDutchFlag(_ a: inout [T], low: Int, high: Int, pivotIndex: Int) -> (Int, Int) { let pivot = a[pivotIndex] var smaller = low @@ -195,7 +195,7 @@ func partitionDutchFlag(inout a: [T], low: Int, high: Int, pivotI /* Uses Dutch national flag partitioning and a random pivot index. */ -func quicksortDutchFlag(inout a: [T], low: Int, high: Int) { +func quicksortDutchFlag(_ a: inout [T], low: Int, high: Int) { if low < high { let pivotIndex = random(min: low, max: high) let (p, q) = partitionDutchFlag(&a, low: low, high: high, pivotIndex: pivotIndex) diff --git a/Quicksort/README.markdown b/Quicksort/README.markdown index e3d284333..120ddc346 100644 --- a/Quicksort/README.markdown +++ b/Quicksort/README.markdown @@ -14,7 +14,7 @@ func quicksort(_ a: [T]) -> [T] { let less = a.filter { $0 < pivot } let equal = a.filter { $0 == pivot } let greater = a.filter { $0 > pivot } - + return quicksort(less) + equal + quicksort(greater) } ``` @@ -30,7 +30,7 @@ Here's how it works. When given an array, `quicksort()` splits it up into three All the elements less than the pivot go into a new array called `less`. All the elements equal to the pivot go into the `equal` array. And you guessed it, all elements greater than the pivot go into the third array, `greater`. This is why the generic type `T` must be `Comparable`, so we can compare the elements with `<`, `==`, and `>`. -Once we have these three arrays, `quicksort()` recursively sorts the `less` array and the `right` array, then glues those sorted subarrays back together with the `equal` array to get the final result. +Once we have these three arrays, `quicksort()` recursively sorts the `less` array and the `greater` array, then glues those sorted subarrays back together with the `equal` array to get the final result. ## An example @@ -44,7 +44,7 @@ First, we pick the pivot element. That is `8` because it's in the middle of the equal: [ 8, 8 ] greater: [ 10, 9, 14, 27, 26 ] -This is a good split because `less` and `equal` roughly contain the same number of elements. So we've picked a good pivot that chopped the array right down the middle. +This is a good split because `less` and `greater` roughly contain the same number of elements. So we've picked a good pivot that chopped the array right down the middle. Note that the `less` and `greater` arrays aren't sorted yet, so we call `quicksort()` again to sort those two subarrays. That does the exact same thing: pick a pivot and split the subarray into three even smaller parts. @@ -73,7 +73,7 @@ The `less` array is empty because there was no value smaller than `-1`; the othe That `greater` array was: [ 3, 2, 5 ] - + This works just the same way as before: we pick the middle element `2` as the pivot and fill up the subarrays: less: [ ] @@ -122,7 +122,7 @@ There is no guarantee that partitioning keeps the elements in the same relative [ 3, 0, 5, 2, -1, 1, 8, 8, 14, 26, 10, 27, 9 ] -The only guarantee is that to the left of the pivot are all the smaller elements and to the right are all the larger elements. Because partitioning can change the original order of equal elements, quicksort does not produce a "stable" sort (unlike [merge sort](../Merge Sort/), for example). Most of the time that's not a big deal. +The only guarantee is that to the left of the pivot are all the smaller elements and to the right are all the larger elements. Because partitioning can change the original order of equal elements, quicksort does not produce a "stable" sort (unlike [merge sort](../Merge%20Sort/), for example). Most of the time that's not a big deal. ## Lomuto's partitioning scheme @@ -133,16 +133,16 @@ Here's an implementation of Lomuto's partitioning scheme in Swift: ```swift func partitionLomuto(_ a: inout [T], low: Int, high: Int) -> Int { let pivot = a[high] - + var i = low for j in low..(_ a: inout [T], low: Int, high: Int) -> Int { let pivot = a[low] var i = low - 1 var j = high + 1 - + while true { repeat { j -= 1 } while a[j] > pivot repeat { i += 1 } while a[i] < pivot - + if i < j { - swap(&a[i], &a[j]) + a.swapAt(i, j) } else { return j } @@ -309,9 +309,9 @@ The result is: [ -1, 0, 3, 8, 2, 5, 1, 27, 10, 14, 9, 8, 26 ] -Note that this time the pivot isn't in the middle at all. Unlike with Lomuto's scheme, the return value is not necessarily the index of the pivot element in the new array. +Note that this time the pivot isn't in the middle at all. Unlike with Lomuto's scheme, the return value is not necessarily the index of the pivot element in the new array. -Instead, the array is partitioned into the regions `[low...p]` and `[p+1...high]`. Here, the return value `p` is 6, so the two partitions are `[ -1, 0, 3, 8, 2, 5, 1 ]` and `[ 27, 10, 14, 9, 8, 26 ]`. +Instead, the array is partitioned into the regions `[low...p]` and `[p+1...high]`. Here, the return value `p` is 6, so the two partitions are `[ -1, 0, 3, 8, 2, 5, 1 ]` and `[ 27, 10, 14, 9, 8, 26 ]`. The pivot is placed somewhere inside one of the two partitions, but the algorithm doesn't tell you which one or where. If the pivot value occurs more than once, then some instances may appear in the left partition and others may appear in the right partition. @@ -357,7 +357,7 @@ And again: And so on... -That's no good, because this pretty much reduces quicksort to the much slower insertion sort. For quicksort to be efficient, it needs to split the array into roughly two halves. +That's no good, because this pretty much reduces quicksort to the much slower insertion sort. For quicksort to be efficient, it needs to split the array into roughly two halves. The optimal pivot for this example would have been `4`, so we'd get: diff --git a/Quicksort/Tests/QuicksortTests.swift b/Quicksort/Tests/QuicksortTests.swift index 00135b31f..c8d9a62e6 100644 --- a/Quicksort/Tests/QuicksortTests.swift +++ b/Quicksort/Tests/QuicksortTests.swift @@ -5,12 +5,12 @@ class QuicksortTests: XCTestCase { checkSortAlgorithm(quicksort) } - private typealias QuicksortFunction = (inout [Int], low: Int, high: Int) -> Void + fileprivate typealias QuicksortFunction = (inout [Int], _ low: Int, _ high: Int) -> Void - private func checkQuicksort(function: QuicksortFunction) { + fileprivate func checkQuicksort(_ function: QuicksortFunction) { checkSortAlgorithm { (a: [Int]) -> [Int] in var b = a - function(&b, low: 0, high: b.count - 1) + function(&b, 0, b.count - 1) return b } } 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 82% rename from Quicksort/Tests/Tests.xcodeproj/project.pbxproj rename to Quicksort/Tests/Tests-Quicksort.xcodeproj/project.pbxproj index 8aecb59f8..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,15 +86,16 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; + 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; @@ -107,7 +108,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 7B2BBC7F1C779D720067B71D /* Tests */, + 7B2BBC7F1C779D720067B71D /* Tests-Quicksort */, ); }; /* End PBXProject section */ @@ -144,13 +145,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 = "-"; @@ -177,6 +188,8 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -188,13 +201,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 = "-"; @@ -213,6 +236,9 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -224,6 +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_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -235,13 +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_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 */, @@ -250,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 e2d99e2bc..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,25 +8,21 @@ 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.2** and **Swift 3**. We'll keep this updated with the latest version of Swift. - -This is a work in progress. More algorithms will be added soon. :-) +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: ## Important links -[What are algorithms and data structures?](What are Algorithms.markdown) Pancakes! - -[Why learn algorithms?](Why Algorithms.markdown) Worried this isn't your cup of tea? Then read this. +[What are algorithms and data structures?](What%20are%20Algorithms.markdown) Pancakes! -[Big-O notation](Big-O Notation.markdown). We often say things like, "This algorithm is **O(n)**." If you don't know what that means, read this first. +[Why learn algorithms?](Why%20Algorithms.markdown) Worried this isn't your cup of tea? Then read this. -[Algorithm design techniques](Algorithm Design.markdown). How do you create your own algorithms? +[Big-O notation](Big-O%20Notation.markdown). We often say things like, "This algorithm is **O(n)**." If you don't know what that means, read this first. -[How to contribute](How to Contribute.markdown). Report an issue to leave feedback, or submit a pull request. +[Algorithm design techniques](Algorithm%20Design.markdown). How do you create your own algorithms? -[Under construction](Under Construction.markdown). Algorithms that are under construction. +[How to contribute](https://github.com/raywenderlich/swift-algorithm-club/blob/master/.github/CONTRIBUTING.md). Report an issue to leave feedback, or submit a pull request. ## Where to start? @@ -34,31 +30,31 @@ If you're new to algorithms and data structures, here are a few good ones to sta - [Stack](Stack/) - [Queue](Queue/) -- [Insertion Sort](Insertion Sort/) -- [Binary Search](Binary Search/) and [Binary Search Tree](Binary Search Tree/) -- [Merge Sort](Merge Sort/) -- [Boyer-Moore string search](Boyer-Moore/) +- [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-Horspool/) ## The algorithms ### Searching -- [Linear Search](Linear Search/). Find an element in an array. -- [Binary Search](Binary Search/). Quickly find elements in a sorted array. -- [Count Occurrences](Count Occurrences/). Count how often a value appears in an array. -- [Select Minimum / Maximum](Select Minimum Maximum). Find the minimum/maximum value in an array. -- [k-th Largest Element](Kth Largest Element/). Find the *k*-th largest element in an array, such as the median. -- [Selection Sampling](Selection Sampling/). Randomly choose a bunch of items from a collection. +- [Linear Search](Linear%20Search/). Find an element in an array. +- [Binary Search](Binary%20Search/). Quickly find elements in a sorted array. +- [Count Occurrences](Count%20Occurrences/). Count how often a value appears in an array. +- [Select Minimum / Maximum](Select%20Minimum%20Maximum). Find the minimum/maximum value in an array. +- [k-th Largest Element](Kth%20Largest%20Element/). Find the *k*-th largest element in an array, such as the median. +- [Selection Sampling](Selection%20Sampling/). Randomly choose a bunch of items from a collection. - [Union-Find](Union-Find/). Keeps track of disjoint sets and lets you quickly merge them. ### String Search -- [Brute-Force String Search](Brute-Force String Search/). 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. -- Knuth-Morris-Pratt +- [Brute-Force String Search](Brute-Force%20String%20Search/). A naive method. +- [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 Common Subsequence/). Find the longest sequence of characters that appear in the same order in both strings. +- [Longest Common Subsequence](Longest%20Common%20Subsequence/). Find the longest sequence of characters that appear in the same order in both strings. - [Z-Algorithm](Z-Algorithm/). Finds all instances of a pattern in a String, and returns the indexes of where the pattern starts within the String. ### Sorting @@ -67,54 +63,65 @@ It's fun to see how sorting algorithms work, but in practice you'll almost never Basic sorts: -- [Insertion Sort](Insertion Sort/) -- [Selection Sort](Selection Sort/) -- [Shell Sort](Shell Sort/) +- [Insertion Sort](Insertion%20Sort/) +- [Selection Sort](Selection%20Sort/) +- [Shell Sort](Shell%20Sort/) Fast sorts: - [Quicksort](Quicksort/) -- [Merge Sort](Merge Sort/) -- [Heap Sort](Heap Sort/) +- [Merge Sort](Merge%20Sort/) +- [Heap Sort](Heap%20Sort/) + +Hybrid sorts: + +- [Introsort](Introsort/) Special-purpose sorts: -- [Counting Sort](Counting Sort/) -- Radix Sort -- [Topological Sort](Topological Sort/) +- [Counting Sort](Counting%20Sort/) +- [Radix Sort](Radix%20Sort/) +- [Topological Sort](Topological%20Sort/) Bad sorting algorithms (don't use these!): -- [Bubble Sort](Bubble Sort/) -- [Slow Sort](Slow Sort/) +- [Bubble Sort](Bubble%20Sort/) +- [Slow Sort](Slow%20Sort/) ### Compression -- [Run-Length Encoding (RLE)](Run-Length Encoding/). Store repeated values as a single byte and a count. -- [Huffman Coding](Huffman Coding/). Store more common elements using a smaller number of bits. +- [Run-Length Encoding (RLE)](Run-Length%20Encoding/). Store repeated values as a single byte and a count. +- [Huffman Coding](Huffman%20Coding/). Store more common elements using a smaller number of bits. ### Miscellaneous - [Shuffle](Shuffle/). Randomly rearranges the contents of an array. -- [Comb Sort](Comb Sort/). An improve upon the Bubble Sort algorithm. - +- [Comb Sort](Comb%20Sort/). An improve upon the Bubble Sort algorithm. +- [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. - [Permutations and Combinations](Combinatorics/). Get your combinatorics on! -- [Shunting Yard Algorithm](Shunting Yard/). Convert infix expressions to postfix. -- Statistics -- [Karatsuba Multiplication](Karatsuba Multiplication/). Another take on elementary multiplication. +- [Shunting Yard Algorithm](Shunting%20Yard/). Convert infix expressions to postfix. +- [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 - [k-Means Clustering](K-Means/). Unsupervised classifier that partitions data into *k* clusters. - k-Nearest Neighbors -- [Linear Regression](Linear Regression/) +- [Linear Regression](Linear%20Regression/). A technique for creating a model of the relationship between two (or more) variable quantities. - 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 @@ -129,105 +136,103 @@ Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types ### Variations on arrays - [Array2D](Array2D/). A two-dimensional array with fixed dimensions. Useful for board games. -- [Bit Set](Bit Set/). A fixed-size sequence of *n* bits. -- [Fixed Size Array](Fixed Size Array/). When you know beforehand how large your data will be, it might be more efficient to use an old-fashioned array with a fixed size. -- [Ordered Array](Ordered Array/). An array that is always sorted. -- [Rootish Array Stack](Rootish Array Stack/). A space and time efficient variation on Swift arrays. +- [Bit Set](Bit%20Set/). A fixed-size sequence of *n* bits. +- [Fixed Size Array](Fixed%20Size%20Array/). When you know beforehand how large your data will be, it might be more efficient to use an old-fashioned array with a fixed size. +- [Ordered Array](Ordered%20Array/). An array that is always sorted. +- [Rootish Array Stack](Rootish%20Array%20Stack/). A space and time efficient variation on Swift arrays. ### Queues - [Stack](Stack/). Last-in, first-out! - [Queue](Queue/). First-in, first-out! - [Deque](Deque/). A double-ended queue. -- [Priority Queue](Priority Queue). A queue where the most important element is always at the front. -- [Ring Buffer](Ring Buffer/). Also known as a circular buffer. An array of a certain size that conceptually wraps around back to the beginning. +- [Priority Queue](Priority%20Queue). A queue where the most important element is always at the front. +- [Ring Buffer](Ring%20Buffer/). Also known as a circular buffer. An array of a certain size that conceptually wraps around back to the beginning. ### Lists -- [Linked List](Linked List/). 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. +- [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 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 - [Tree](Tree/). A general-purpose tree structure. -- [Binary Tree](Binary Tree/). A tree where each node has at most two children. -- [Binary Search Tree (BST)](Binary Search Tree/). A binary tree that orders its nodes in a way that allows for fast queries. -- Red-Black Tree -- Splay Tree -- Threaded Binary Tree -- [Segment Tree](Segment Tree/). Can quickly compute a function over a portion of an array. +- [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%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 -- [Hash Table](Hash Table/). Allows you to store and retrieve objects by a key. This is how the dictionary type is usually implemented. +- [Hash Table](Hash%20Table/). Allows you to store and retrieve objects by a key. This is how the dictionary type is usually implemented. - Hash Functions ### Sets -- [Bloom Filter](Bloom Filter/). A constant-memory data structure that probabilistically tests whether an element is in a set. -- [Hash Set](Hash Set/). A set implemented using a hash table. -- Multiset -- [Ordered Set](Ordered Set/). A set where the order of items matters. +- [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/). 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 - [Graph](Graph/) -- [Breadth-First Search (BFS)](Breadth-First Search/) -- [Depth-First Search (DFS)](Depth-First Search/) -- [Shortest Path](Shortest Path %28Unweighted%29/) on an unweighted tree -- [Single-Source Shortest Paths](Single-Source Shortest Paths (Weighted)/) -- [Minimum Spanning Tree](Minimum Spanning Tree %28Unweighted%29/) on an unweighted tree -- [All-Pairs Shortest Paths](All-Pairs Shortest Paths/) +- [Breadth-First Search (BFS)](Breadth-First%20Search/) +- [Depth-First Search (DFS)](Depth-First%20Search/) +- [Shortest Path](Shortest%20Path%20%28Unweighted%29/) on an unweighted tree +- [Single-Source Shortest Paths](Single-Source%20Shortest%20Paths%20(Weighted)/) +- [Minimum Spanning Tree](Minimum%20Spanning%20Tree%20%28Unweighted%29/) on an unweighted tree +- [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 Problem/) -- [Fizz Buzz](Fizz Buzz/) -- [Monty Hall Problem](Monty Hall Problem/) +- [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) and [Kelvin Lau](https://github.com/kelvinlauKL). +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](How to Contribute.markdown)? :] +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 136ede4fd..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] ) { @@ -11,18 +10,16 @@ public func radixSort(_ array: inout [Int] ) { var done = false var index: Int var digit = 1 //Which digit are we on? - - + while !done { //While our sorting is not completed done = true //Assume it is done for now - + var buckets: [[Int]] = [] //Our sorting subroutine is bucket sort, so let us predefine our buckets - + for _ in 1...radix { buckets.append([]) } - - + for number in array { index = number / digit //Which bucket will we access? buckets[index % radix].append(number) @@ -30,9 +27,9 @@ public func radixSort(_ array: inout [Int] ) { done = false } } - + var i = 0 - + for j in 0..=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 05993f016..2108b9055 100644 --- a/Red-Black Tree/README.markdown +++ b/Red-Black Tree/README.markdown @@ -1,120 +1,193 @@ # Red-Black Tree -A Red-Black tree is a special version of a [Binary Search Tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Binary%20Search%20Tree). Binary search trees(BSTs) can become unbalanced after a lot of insertions/deletions. The only difference between a node from a BST and a Red-Black Tree(RBT) is that RBT nodes have a color property added to them which can either be red or black. A RBT rebalances itself by making sure the following properties hold: +A red-black tree (RBT) is a balanced version of a [Binary Search Tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Binary%20Search%20Tree) guaranteeing that the basic operations (search, predecessor, successor, minimum, maximum, insert and delete) have a logarithmic worst case performance. -## Properties -1. A node is either red or black -2. The root is always black -3. All leaves are black -4. If a node is red, both of its children are black -5. Every path to a leaf contains the same number of black nodes (The amount of black nodes met when going down a RBT is called the black-depth of the tree.) - -## Methods -* `insert(_ value: T)` inserts the value into the tree -* `insert(_ values: [T])` inserts an array of values into the tree -* `delete(_ value: T)` deletes the value from the tree -* `find(_ value: T) -> RBTNode` looks for a node in the tree with given value and returns it -* `minimum(n: RBTNode) -> RBTNode` looks for the maximum value of a subtree starting at the given node -* `maximum(n: RBTNode) -> RBTNode` looks for the minimum value of a subtree starting at the given node -* `func verify()` verifies if the tree is still valid. Prints warning messages if this is not the case - -## Rotation +Binary search trees (BSTs) have the disadvantage that they can become unbalanced after some insert or delete operations. In the worst case, this could lead to a tree where the nodes build a linked list as shown in the following example: -In order to rebalance their nodes RBTs use an operation known as rotation. You can either left or right rotate a node and its child. Rotating a node and its child swaps the nodes and interchanges their subtrees. Left rotation swaps the parent node with its right child. while right rotation swaps the parent node with its left child. - -Left rotation: -``` -before left rotating p after left rotating p - p b - / \ / \ - a b -> p n - / \ / \ / \ -n n n n a n - / \ - n n -``` -Right rotation: ``` -before right rotating p after right rotating p - p a - / \ / \ - a b -> n p - / \ / \ / \ -n n n n n b - / \ - n n +a + \ + b + \ + c + \ + d ``` +To prevent this issue, RBTs perform rebalancing operations after an insert or delete and store an additional color property at each node which can either be red or black. After each operation a RBT satisfies the following properties: -## Insertion - -We create a new node with the value to be inserted into the tree. The color of this new node is always red. -We perform a standard BST insert with this node. Now the three might not be a valid RBT anymore. -We now go through several insertion steps in order to make the tree valid again. We call the just inserted node n. - -**Step 1**: We check if n is the rootNode, if so we paint it black and we are done. If not we go to Step 2. - -We now know that n at least has a parent as it is not the rootNode. - -**Step 2**: We check if the parent of n is black if so we are done. If not we go to Step 3. - -We now know the parent is also not the root node as the parent is red. Thus n also has a grandparent and thus also an uncle as every node has two children. This uncle may however be a nullLeaf - -**Step 3**: We check if n's uncle is red. If not we go to Step 4. If n's uncle is indeed red we recolor uncle and parent to black and n's grandparent to red. We now go back to step 1 performing the same logic on n's grandparent. - -From here there are four cases: -- **The left left case.** n's parent is the left child of its parent and n is the left child of its parent. -- **The left right case** n's parent is the left child of its parent and n is the right child of its parent. -- **The right right case** n's parent is the right child of its parent and n is the right child of its parent. -- **The right left case** n's parent is the right child of its parent and n is the left child of its parent. - -**Step 4**: checks if either the **left right** case or the **right left** case applies to the current situation. - - If we find the **left right case**, we left rotate n's parent and go to Step 5 while setting n to n's parent. (This transforms the **left right** case into the **left left** case) - - If we find the **right left case**, we right rotate n's parent and go to Step 5 while setting n to n's parent. (This transforms the **right left** case into the **right right** case) - - If we find neither of these two we proceed to Step 5. +## Properties -n's parent is now red, while n's grandparent is black. +1. Every node is either red or black +2. The root is black +3. Every leaf (nullLeaf) is black +4. If a node is red, then both its children are black +5. For each node, all paths from the node to descendant leaves contain the same number of black nodes -**Step 5**: We swap the colors of n's parent and grandparent. - - We either right rotate n's grandparent in case of the **left left** case - - Or we left rotate n's grandparent in case of the **right right** case +Property 5 includes the definition of the black-height of a node x, bh(x), which is the number of black nodes on a path from this node down to a leaf not counting the node itself. +From [CLRS] -Reaching the end we have successfully made the tree valid again. +## Methods -# Deletion +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 +Tree: +* `search(input:)` Returns the node with the given key value +* `minValue()` Returns the minimum key value of the whole tree +* `maxValue()` Returns the maximum key value of the whole 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. -Deletion is a little more complicated than insertion. In the following we call del the node to be deleted. -First we try and find the node to be deleted using find() -we send the result of find to del. -We now go through the following steps in order to delete the node del. +The rotation, insertion and deletion algorithms are implemented based on the pseudo-code provided in [CLRS] -First we do a few checks: -- Is del the rootNode if so set the root to a nullLeaf and we are done. -- If del has 2 nullLeaf children and is red. We check if del is either a left or a right child and set del's parent left or right to a nullLeaf. -- If del has two non nullLeaf children we look for the maximum value in del's left subtree. We set del's value to this maximum value and continue to instead delete this maximum node. Which we will now call del. +## Implementation Details -Because of these checks we now know that del has at most 1 non nullLeaf child. It has either two nullLeaf children or one nullLeaf and one regular red child. (The child is red otherwise the black-depth wouldn't be the same for every leaf) +For convenience, all nil-pointers to children or the parent (except the parent of the root) of a node are exchanged with a nullLeaf. This is an ordinary node object, like all other nodes in the tree, but with a black color, and in this case a nil value for its children, parent and key. Therefore, an empty tree consists of exactly one nullLeaf at the root. -We now call the non nullLeaf child of del, child. If del has two nullLeaf children child will be a nullLeaf. This means child can either be a nullLeaf or a red node. +## Rotation -We now have three options +Left rotation (around 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] +``` +Right rotation (around 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] +``` +As rotation is a local operation only exchanging pointers it's runtime is O(1). -- If del is red its child is a nullLeaf and we can just delete it as it doesn't change the black-depth of the tree. We are done +## Insertion -- If child is red we recolor it black and child's parent to del's parent. del is now deleted and we are done. +We create a node with the given key and set its color to red. Then we insert it into the the tree by performing a standard insert into a BST. After this, the tree might not be a valid RBT anymore, so we fix the red-black properties by calling the insertFixup algorithm. +The only violation of the red-black properties occurs at the inserted node z and its parent. Either both are red, or the parent does not exist (so there is a violation since the root must be black). +We have three distinct cases: +**Case 1:** The uncle of z is red. If z.parent is left child, z's uncle is z.grandparent's right child. If this is the case, we recolor and move z to z.grandparent, then we check again for this new z. In the following cases, we denote a red node with (x) and a black node with {x}, p = parent, gp = grandparent and u = uncle +``` + | | + {gp} (newZ) + / \ ~> / \ + (p) (u) {p} {u} + / \ / \ / \ / \ + (z) [C] [D] [E] (z) [C] [D] [E] + / \ / \ +[A] [B] [A] [B] -- Both del and child are black we go through +``` +**Case 2a:** The uncle of z is black and z is right child. Here, we move z upwards, so z's parent is the newZ and then we rotate around this newZ. After this, we have Case 2b. +``` + | | + {gp} {gp} + / \ ~> / \ + (p) {u} (z) {u} + / \ / \ / \ / \ + [A] (z) [D] [E] (newZ) [C] [D] [E] + / \ / \ + [B] [C] [A] [B] -If both del and child are black we introduce a new variable sibling. Which is del's sibling. We also replace del with child and recolor it doubleBlack. del is now deleted and child and sibling are siblings. +``` +**Case 2b:** The uncle of z is black and z is left child. In this case, we recolor z.parent to black and z.grandparent to red. Then we rotate around z's grandparent. Afterwards we have valid red-black tree. +``` + | | + {gp} {p} + / \ ~> / \ + (p) {u} (z) (gp) + / \ / \ / \ / \ + (z) [C] [D] [E] [A] [B] [C] {u} + / \ / \ +[A] [B] [D] [E] -We now have to go through a lot of steps in order to remove this doubleBlack color. Which are hard to explain in text without just writing the full code in text. This is due the many possible combination of nodes and colors. The code is commented, but if you don't quite understand please leave me a message. Also part of the deletion is described by the final link in the Also See section. +``` +Running time of this algorithm: +* Only case 1 may repeat, but this only h/2 steps, where h is the height of the tree +* Case 2a -> Case 2b -> red-black tree +* Case 2b -> red-black tree +As we perform case 1 at most O(log n) times and all other cases at most once, we have O(log n) recolorings and at most 2 rotations. +The overall runtime of insert is O(log n). + +## Deletion + +We search for the node with the given key, and if it exists we delete it by performing a standard delete from a BST. If the deleted nodes' color was red everything is fine, otherwise red-black properties may be violated so we call the fixup procedure deleteFixup. +Violations can be that the parent and child of the deleted node x where red, so we now have two adjacent red nodes, or if we deleted the root, the root could now be red, or the black height property is violated. +We have 4 cases: We call deleteFixup on node x +**Case 1:** The sibling of x is red. The sibling is the other child of x's parent, which is not x itself. In this case, we recolor the parent of x and x.sibling then we left rotate around x's parent. In the following cases s = sibling of x, (x) = red, {x} = black, |x| = red/black. +``` + | | + {p} {s} + / \ ~> / \ + {x} (s) (p) [D] + / \ / \ / \ + [A] [B] [C] [D] {x} [C] + / \ + [A] [B] -## Also see +``` +**Case 2:** The sibling of x is black and has two black children. Here, we recolor x.sibling to red, move x upwards to x.parent and check again for this newX. +``` + | | + |p| |newX| + / \ ~> / \ + {x} {s} {x} (s) + / \ / \ / \ / \ + [A] [B] {l} {r} [A] [B] {l} {r} + / \ / \ / \ / \ + [C][D][E][F] [C][D][E][F] -* [Wikipedia](https://en.wikipedia.org/wiki/Red–black_tree) -* [GeeksforGeeks - introduction](http://www.geeksforgeeks.org/red-black-tree-set-1-introduction-2/) -* [GeeksforGeeks - insertion](http://www.geeksforgeeks.org/red-black-tree-set-2-insert/) -* [GeeksforGeeks - deletion](http://www.geeksforgeeks.org/red-black-tree-set-3-delete-2/) +``` +**Case 3:** The sibling of x is black with one black child to the right. In this case, we recolor the sibling to red and sibling.leftChild to black, then we right rotate around the sibling. After this we have case 4. +``` + | | + |p| |p| + / \ ~> / \ + {x} {s} {x} {l} + / \ / \ / \ / \ + [A] [B] (l) {r} [A] [B] [C] (s) + / \ / \ / \ + [C][D][E][F] [D]{e} + / \ + [E] [F] -Important to note is that GeeksforGeeks doesn't mention a few deletion cases that do occur. The code however does implement these. +``` +**Case 4:** The sibling of x is black with one red child to the right. Here, we recolor the sibling to the color of x.parent and x.parent and sibling.rightChild to black. Then we left rotate around x.parent. After this operation we have a valid red-black tree. Here, ||x|| denotes that x can have either color red or black, but that this can be different to |x| color. This is important, as s gets the same color as p. +``` + | | + ||p|| ||s|| + / \ ~> / \ + {x} {s} {p} {r} + / \ / \ / \ / \ + [A] [B] |l| (r) {x} |l| [E] [F] + / \ / \ / \ / \ + [C][D][E][F] [A][B][C][D] -*Written for Swift Algorithm Club by Jaap Wijnen. Updated from Ashwin Raghuraman's contribution.* +``` +Running time of this algorithm: +* Only case 2 can repeat, but this only h many times, 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 +As we perform case 2 at most O(log n) times and all other steps at most once, we have O(log n) recolorings and at most 3 rotations. +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. Swift 4.2 check by Bruno Scheele.* diff --git a/Red-Black Tree/Red-Black Tree 2.playground/Contents.swift b/Red-Black Tree/Red-Black Tree 2.playground/Contents.swift deleted file mode 100644 index d55c153d5..000000000 --- a/Red-Black Tree/Red-Black Tree 2.playground/Contents.swift +++ /dev/null @@ -1,20 +0,0 @@ -//: Playground - noun: a place where people can play -import Foundation - -let tree = RBTree() -let randomMax = Double(0x100000000) -var values = [Double]() -for _ in 0..<1000 { - let value = Double(arc4random()) / randomMax - values.append(value) - tree.insert(value) -} -tree.verify() - -for _ in 0..<1000 { - let rand = arc4random_uniform(UInt32(values.count)) - let index = Int(rand) - let value = values.remove(at: index) - tree.delete(value) -} -tree.verify() diff --git a/Red-Black Tree/Red-Black Tree 2.playground/Sources/RBTree.swift b/Red-Black Tree/Red-Black Tree 2.playground/Sources/RBTree.swift deleted file mode 100644 index 1735798d5..000000000 --- a/Red-Black Tree/Red-Black Tree 2.playground/Sources/RBTree.swift +++ /dev/null @@ -1,521 +0,0 @@ - -private enum RBTColor { - case red - case black - case doubleBlack -} - -public class RBTNode: CustomStringConvertible { - fileprivate var color: RBTColor = .red - public var value: T! = nil - public var right: RBTNode! - public var left: RBTNode! - public var parent: RBTNode! - - public var description: String { - if self.value == nil { - return "null" - } else { - var nodeValue: String - - // If the value is encapsulated by parentheses it is red - // If the value is encapsulated by pipes it is black - // If the value is encapsulated by double pipes it is double black (This should not occur in a verified RBTree) - if self.isRed { - nodeValue = "(\(self.value!))" - } else if self.isBlack{ - nodeValue = "|\(self.value!)|" - } else { - nodeValue = "||\(self.value!)||" - } - - return "(\(self.left.description)<-\(nodeValue)->\(self.right.description))" - } - } - - init(tree: RBTree) { - right = tree.nullLeaf - left = tree.nullLeaf - parent = tree.nullLeaf - } - - init() { - //This method is here to support the creation of a nullLeaf - } - - public var isLeftChild: Bool { - return self.parent.left === self - } - - public var isRightChild: Bool { - return self.parent.right === self - } - - public var grandparent: RBTNode { - return parent.parent - } - - public var sibling: RBTNode { - if isLeftChild { - return self.parent.right - } else { - return self.parent.left - } - } - - public var uncle: RBTNode { - return parent.sibling - } - - fileprivate var isRed: Bool { - return color == .red - } - - fileprivate var isBlack: Bool { - return color == .black - } - - fileprivate var isDoubleBlack: Bool { - return color == .doubleBlack - } -} - -public class RBTree: CustomStringConvertible { - public var root: RBTNode - fileprivate let nullLeaf: RBTNode - - public var description: String { - return root.description - } - - public init() { - nullLeaf = RBTNode() - nullLeaf.color = .black - root = nullLeaf - } - - public convenience init(withValue value: T) { - self.init() - insert(value) - } - - public convenience init(withArray array: [T]) { - self.init() - insert(array) - } - - public func insert(_ value: T) { - let newNode = RBTNode(tree: self) - newNode.value = value - insertNode(n: newNode) - } - - public func insert(_ values: [T]) { - for value in values { - print(value) - insert(value) - } - } - - public func delete(_ value: T) { - let nodeToDelete = find(value) - if nodeToDelete !== nullLeaf { - deleteNode(n: nodeToDelete) - } - } - - public func find(_ value: T) -> RBTNode { - let foundNode = findNode(rootNode: root, value: value) - return foundNode - } - - public func minimum(n: RBTNode) -> RBTNode { - var min = n - if n.left !== nullLeaf { - min = minimum(n: n.left) - } - - return min - } - - public func maximum(n: RBTNode) -> RBTNode { - var max = n - if n.right !== nullLeaf { - max = maximum(n: n.right) - } - - return max - } - - public func verify() { - if self.root === nullLeaf { - print("The tree is empty") - return - } - property1() - property2(n: self.root) - property3() - } - - private func findNode(rootNode: RBTNode, value: T) -> RBTNode { - var nextNode = rootNode - if rootNode !== nullLeaf && value != rootNode.value { - if value < rootNode.value { - nextNode = findNode(rootNode: rootNode.left, value: value) - } else { - nextNode = findNode(rootNode: rootNode.right, value: value) - } - } - - return nextNode - } - - private func insertNode(n: RBTNode) { - BSTInsertNode(n: n, parent: root) - insertCase1(n: n) - } - - private func BSTInsertNode(n: RBTNode, parent: RBTNode) { - if parent === nullLeaf { - self.root = n - } else if n.value < parent.value { - if parent.left !== nullLeaf { - BSTInsertNode(n: n, parent: parent.left) - } else { - parent.left = n - parent.left.parent = parent - } - } else { - if parent.right !== nullLeaf { - BSTInsertNode(n: n, parent: parent.right) - } else { - parent.right = n - parent.right.parent = parent - } - } - } - - // if node is root change color to black, else move on - private func insertCase1(n: RBTNode) { - if n === root { - n.color = .black - } else { - insertCase2(n: n) - } - } - - // if parent of node is not black, and node is not root move on - private func insertCase2(n: RBTNode) { - if !n.parent.isBlack { - insertCase3(n: n) - } - } - - // if uncle is red do stuff otherwise move to 4 - private func insertCase3(n: RBTNode) { - if n.uncle.isRed { // node must have grandparent as children of root have a black parent - // both parent and uncle are red, so grandparent must be black. - n.parent.color = .black - n.uncle.color = .black - n.grandparent.color = .red - // now both parent and uncle are black and grandparent is red. - // we repeat for the grandparent - insertCase1(n: n.grandparent) - } else { - insertCase4(n: n) - } - } - - // parent is red, grandparent is black, uncle is black - // There are 4 cases left: - // - left left - // - left right - // - right right - // - right left - - // the cases "left right" and "right left" can be rotated into the other two - // so if either of the two is detected we apply a rotation and then move on to - // deal with the final two cases, if neither is detected we move on to those cases anyway - private func insertCase4(n: RBTNode) { - if n.parent.isLeftChild && n.isRightChild { // left right case - leftRotate(n: n.parent) - insertCase5(n: n.left) - } else if n.parent.isRightChild && n.isLeftChild { // right left case - rightRotate(n: n.parent) - insertCase5(n: n.right) - } else { - insertCase5(n: n) - } - } - - private func insertCase5(n: RBTNode) { - // swap color of parent and grandparent - // parent is red grandparent is black - n.parent.color = .black - n.grandparent.color = .red - - if n.isLeftChild { // left left case - rightRotate(n: n.grandparent) - } else { // right right case - leftRotate(n: n.grandparent) - } - } - - private func deleteNode(n: RBTNode) { - var toDel = n - - if toDel.left === nullLeaf && toDel.right === nullLeaf && toDel.parent === nullLeaf { - self.root = nullLeaf - return - } - - if toDel.left === nullLeaf && toDel.right === nullLeaf && toDel.isRed { - if toDel.isLeftChild { - toDel.parent.left = nullLeaf - } else { - toDel.parent.right = nullLeaf - } - return - } - - if toDel.left !== nullLeaf && toDel.right !== nullLeaf { - let pred = maximum(n: toDel.left) - toDel.value = pred.value - toDel = pred - } - - // from here toDel has at most 1 non nullLeaf child - - var child: RBTNode - if toDel.left !== nullLeaf { - child = toDel.left - } else { - child = toDel.right - } - - if toDel.isRed || child.isRed { - child.color = .black - - if toDel.isLeftChild { - toDel.parent.left = child - } else { - toDel.parent.right = child - } - - if child !== nullLeaf { - child.parent = toDel.parent - } - } else { // both toDel and child are black - - var sibling = toDel.sibling - - if toDel.isLeftChild { - toDel.parent.left = child - } else { - toDel.parent.right = child - } - if child !== nullLeaf { - child.parent = toDel.parent - } - child.color = .doubleBlack - - while child.isDoubleBlack || (child.parent !== nullLeaf && child.parent != nil) { - if sibling.isBlack { - - var leftRedChild: RBTNode! = nil - if sibling.left.isRed { - leftRedChild = sibling.left - } - var rightRedChild: RBTNode! = nil - if sibling.right.isRed { - rightRedChild = sibling.right - } - - if leftRedChild != nil || rightRedChild != nil { // at least one of sibling's children are red - child.color = .black - if sibling.isLeftChild { - if leftRedChild != nil { // left left case - sibling.left.color = .black - let tempColor = sibling.parent.color - sibling.parent.color = sibling.color - sibling.color = tempColor - rightRotate(n: sibling.parent) - } else { // left right case - if sibling.parent.isRed { - sibling.parent.color = .black - } else { - sibling.right.color = .black - } - leftRotate(n: sibling) - rightRotate(n: sibling.grandparent) - } - } else { - if rightRedChild != nil { // right right case - sibling.right.color = .black - let tempColor = sibling.parent.color - sibling.parent.color = sibling.color - sibling.color = tempColor - leftRotate(n: sibling.parent) - } else { // right left case - if sibling.parent.isRed { - sibling.parent.color = .black - } else { - sibling.left.color = .black - } - rightRotate(n: sibling) - leftRotate(n: sibling.grandparent) - } - } - break - } else { // both sibling's children are black - child.color = .black - sibling.color = .red - if sibling.parent.isRed { - sibling.parent.color = .black - break - } - /* - sibling.parent.color = .doubleBlack - child = sibling.parent - sibling = child.sibling - */ - if sibling.parent.parent === nullLeaf { // parent of child is root - break - } else { - sibling.parent.color = .doubleBlack - child = sibling.parent - sibling = child.sibling // can become nill if child is root as parent is nullLeaf - } - //--------------- - } - } else { // sibling is red - sibling.color = .black - - if sibling.isLeftChild { // left case - rightRotate(n: sibling.parent) - sibling = sibling.right.left - sibling.parent.color = .red - } else { // right case - leftRotate(n: sibling.parent) - sibling = sibling.left.right - sibling.parent.color = .red - } - } - - // sibling check is here for when child is a nullLeaf and thus does not have a parent. - // child is here as sibling can become nil when child is the root - if (sibling.parent === nullLeaf) || (child !== nullLeaf && child.parent === nullLeaf) { - child.color = .black - } - } - } - } - - private func property1() { - - if self.root.isRed { - print("Root is not black") - } - } - - private func property2(n: RBTNode) { - if n === nullLeaf { - return - } - if n.isRed { - if n.left !== nullLeaf && n.left.isRed { - print("Red node: \(n.value), has red left child") - } else if n.right !== nullLeaf && n.right.isRed { - print("Red node: \(n.value), has red right child") - } - } - property2(n: n.left) - property2(n: n.right) - } - - private func property3() { - let bDepth = blackDepth(root: self.root) - - let leaves:[RBTNode] = getLeaves(n: self.root) - - for leaflet in leaves { - var leaf = leaflet - var i = 0 - - while leaf !== nullLeaf { - if leaf.isBlack { - i = i + 1 - } - leaf = leaf.parent - } - - if i != bDepth { - print("black depth: \(bDepth), is not equal (depth: \(i)) for leaf with value: \(leaflet.value)") - } - } - - } - - private func getLeaves(n: RBTNode) -> [RBTNode] { - var leaves = [RBTNode]() - - if n !== nullLeaf { - if n.left === nullLeaf && n.right === nullLeaf { - leaves.append(n) - } else { - let leftLeaves = getLeaves(n: n.left) - let rightLeaves = getLeaves(n: n.right) - - leaves.append(contentsOf: leftLeaves) - leaves.append(contentsOf: rightLeaves) - } - } - - return leaves - } - - private func blackDepth(root: RBTNode) -> Int { - if root === nullLeaf { - return 0 - } else { - let returnValue = root.isBlack ? 1 : 0 - return returnValue + (max(blackDepth(root: root.left), blackDepth(root: root.right))) - } - } - - private func leftRotate(n: RBTNode) { - let newRoot = n.right! - n.right = newRoot.left! - if newRoot.left !== nullLeaf { - newRoot.left.parent = n - } - newRoot.parent = n.parent - if n.parent === nullLeaf { - self.root = newRoot - } else if n.isLeftChild { - n.parent.left = newRoot - } else { - n.parent.right = newRoot - } - newRoot.left = n - n.parent = newRoot - } - - private func rightRotate(n: RBTNode) { - let newRoot = n.left! - n.left = newRoot.right! - if newRoot.right !== nullLeaf { - newRoot.right.parent = n - } - newRoot.parent = n.parent - if n.parent === nullLeaf { - self.root = newRoot - } else if n.isRightChild { - n.parent.right = newRoot - } else { - n.parent.left = newRoot - } - newRoot.right = n - n.parent = newRoot - } -} diff --git a/Red-Black Tree/RedBlackTree.playground/Contents.swift b/Red-Black Tree/RedBlackTree.playground/Contents.swift new file mode 100644 index 000000000..d1133bc98 --- /dev/null +++ b/Red-Black Tree/RedBlackTree.playground/Contents.swift @@ -0,0 +1,30 @@ +//: Playground - noun: a place where people can play +// Test for the RedBlackTree implementation provided in the Source folder of this Playground +import Foundation + +let redBlackTree = RedBlackTree() + +let randomMax = Double(0x10000000) +var values = [Double]() +for i in 0..<1000 { + let value = Double(arc4random()) / randomMax + values.append(value) + redBlackTree.insert(key: value) + + if i % 100 == 0 { + redBlackTree.verify() + } +} +redBlackTree.verify() + +for i in 0..<1000 { + let rand = arc4random_uniform(UInt32(values.count)) + let index = Int(rand) + let value = values.remove(at: index) + redBlackTree.delete(key: value) + + if i % 100 == 0 { + redBlackTree.verify() + } +} +redBlackTree.verify() diff --git a/Red-Black Tree/RedBlackTree.playground/Sources/RedBlackTree.swift b/Red-Black Tree/RedBlackTree.playground/Sources/RedBlackTree.swift new file mode 100644 index 000000000..9b067de55 --- /dev/null +++ b/Red-Black Tree/RedBlackTree.playground/Sources/RedBlackTree.swift @@ -0,0 +1,820 @@ +//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 + } + + public func getKey() -> T? { + return key + } +} + +// 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 and predecessor + +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 + } + + /* + * 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 + } + 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/Red-Black Tree/RedBlackTree.playground/contents.xcplayground b/Red-Black Tree/RedBlackTree.playground/contents.xcplayground new file mode 100644 index 000000000..5da2641c9 --- /dev/null +++ b/Red-Black Tree/RedBlackTree.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Red-Black Tree/RedBlackTree.playground/playground.xcworkspace/contents.xcworkspacedata b/Red-Black Tree/RedBlackTree.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Red-Black Tree/RedBlackTree.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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 68ec0a53e..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 { @@ -177,14 +182,12 @@ list[0] // "Hello" list[1] // "World" //list[2] // crash! - list.memoryDescription // { // [Optional("Hello")] // [Optional("World"), nil] // } - list.insert(element: "Swift", atIndex: 1) list.isEmpty // false list.first // "Hello" diff --git a/Rootish Array Stack/Tests/RootishArrayStackTests.swift b/Rootish Array Stack/Tests/RootishArrayStackTests.swift index f73857d0f..fc045faf4 100755 --- a/Rootish Array Stack/Tests/RootishArrayStackTests.swift +++ b/Rootish Array Stack/Tests/RootishArrayStackTests.swift @@ -1,10 +1,10 @@ import XCTest fileprivate extension RootishArrayStack { - func equal(toArray array: Array) -> Bool{ + func equal(toArray array: Array) -> Bool { for index in 0..=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 62b4ef437..8d6a0a403 100644 --- a/Run-Length Encoding/RLE.playground/Contents.swift +++ b/Run-Length Encoding/RLE.playground/Contents.swift @@ -12,16 +12,16 @@ originalString == restoredString func encodeAndDecode(_ bytes: [UInt8]) -> Bool { var bytes = bytes - + var data1 = Data(bytes: &bytes, count: bytes.count) print("data1 is \(data1.count) bytes") - + var rleData = data1.compressRLE() print("encoded data is \(rleData.count) bytes") - + var data2 = rleData.decompressRLE() print("data2 is \(data2.count) bytes") - + return data1 == data2 } @@ -59,17 +59,17 @@ 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) } func testBufferWithSpans(_ spanSize: Int) -> Bool { print("span size \(spanSize)") - + let length = spanSize * 32 var bytes: [UInt8] = Array(repeating: 0, count: length) - + for t in stride(from: 0, to: length, by: spanSize) { for i in 0.. Bool { for bool in tests { result = result && bool } - + return result } -runTests() \ No newline at end of file +runTests() diff --git a/Run-Length Encoding/RLE.playground/Sources/RLE.swift b/Run-Length Encoding/RLE.playground/Sources/RLE.swift index 5707e3d9d..0bb6fa00d 100644 --- a/Run-Length Encoding/RLE.playground/Sources/RLE.swift +++ b/Run-Length Encoding/RLE.playground/Sources/RLE.swift @@ -13,7 +13,7 @@ extension Data { var count = 0 var byte = ptr.pointee var next = byte - + // Is the next byte the same? Keep reading until we find a different // value, or we reach the end of the data, or the run is 64 bytes. while next == byte && ptr < end && count < 64 { @@ -21,7 +21,7 @@ extension Data { next = ptr.pointee count += 1 } - + if count > 1 || byte >= 192 { // byte run of up to 64 repeats var size = 191 + UInt8(count) data.append(&size, count: 1) @@ -33,7 +33,7 @@ extension Data { } return data } - + /* Converts a run-length encoded NSData back to the original. */ @@ -42,20 +42,20 @@ extension Data { self.withUnsafeBytes { (uPtr: UnsafePointer) in var ptr = uPtr let end = ptr + count - + while ptr < end { // Read the next byte. This is either a single value less than 192, // or the start of a byte run. var byte = ptr.pointee ptr = ptr.advanced(by: 1) - + if byte < 192 { // single value data.append(&byte, count: 1) } else if ptr < end { // byte run // Read the actual data value. var value = ptr.pointee ptr = ptr.advanced(by: 1) - + // And write it out repeatedly. for _ in 0 ..< byte - 191 { data.append(&value, count: 1) 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 15b1a3227..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. @@ -18,7 +20,7 @@ var a = [ 20, 3, -1, 101, 14, 29, 5, 61, 99 ] We want to query this array on the interval from 3 to 7 for the function "sum". That means we do the following: 101 + 14 + 29 + 5 + 61 = 210 - + because `101` is at index 3 in the array and `61` is at index 7. So we pass all the numbers between `101` and `61` to the sum function, which adds them all up. If we had used the "min" function, the result would have been `5` because that's the smallest number in the interval from 3 to 7. Here's naive approach if our array's type is `Int` and **f** is just the sum of two integers: @@ -43,7 +45,7 @@ The main idea of segment trees is simple: we precalculate some segments in our a ## Structure of segment tree -A segment tree is just a [binary tree](../Binary Tree/) where each node is an instance of the `SegmentTree` class: +A segment tree is just a [binary tree](../Binary%20Tree/) where each node is an instance of the `SegmentTree` class: ```swift public class SegmentTree { @@ -116,18 +118,18 @@ Here's the code: 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") } - + // 2 if leftChild.rightBound < leftBound { return rightChild.query(withLeftBound: leftBound, rightBound: rightBound) - + // 3 } else if rightChild.leftBound > rightBound { return leftChild.query(withLeftBound: leftBound, rightBound: rightBound) - + // 4 } else { let leftResult = leftChild.query(withLeftBound: leftBound, rightBound: leftChild.rightBound) @@ -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 5bba56cc0..25116c285 100644 --- a/Segment Tree/SegmentTree.playground/Contents.swift +++ b/Segment Tree/SegmentTree.playground/Contents.swift @@ -1,19 +1,24 @@ //: 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 private var function: (T, T) -> T private var leftBound: Int private var rightBound: Int private var leftChild: SegmentTree? private var rightChild: SegmentTree? - + 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 { @@ -23,19 +28,19 @@ public class SegmentTree { value = function(leftChild!.value, rightChild!.value) } } - + public convenience init(array: [T], function: @escaping (T, T) -> T) { self.init(array: array, leftBound: 0, rightBound: array.count-1, function: function) } - + 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 { @@ -46,7 +51,7 @@ public class SegmentTree { return function(leftResult, rightResult) } } - + public func replaceItem(at index: Int, withItem item: T) { if leftBound == rightBound { value = item @@ -61,9 +66,6 @@ public class SegmentTree { } } - - - let array = [1, 2, 3, 4] let sumSegmentTree = SegmentTree(array: array, function: +) @@ -77,13 +79,12 @@ sumSegmentTree.replaceItem(at: 0, withItem: 2) //our array now is [2, 2, 3, 4] print(sumSegmentTree.query(leftBound: 0, rightBound: 0)) // 2 = 2 print(sumSegmentTree.query(leftBound: 0, rightBound: 1)) // 2 + 2 = 4 - //you can use any associative function (i.e (a+b)+c == a+(b+c)) as function for segment tree 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 @@ -105,7 +106,6 @@ gcdSegmentTree.replaceItem(at: 3, withItem: 10) //gcdArray now is [2, 4, 6, 10, print(gcdSegmentTree.query(leftBound: 3, rightBound: 4)) // gcd(10, 5) = 5 - //example of segment tree which finds minimum on given range let minArray = [2, 4, 1, 5, 3] @@ -118,7 +118,6 @@ minSegmentTree.replaceItem(at: 2, withItem: 10) // minArray now is [2, 4, 10, 5, print(minSegmentTree.query(leftBound: 0, rightBound: 4)) // min(2, 4, 10, 5, 3) = 2 - //type of elements in array can be any type which has some associative function let stringArray = ["a", "b", "c", "A", "B", "C"] diff --git a/Segment Tree/SegmentTree.swift b/Segment Tree/SegmentTree.swift index a3ec8b1bf..6f4edd306 100644 --- a/Segment Tree/SegmentTree.swift +++ b/Segment Tree/SegmentTree.swift @@ -8,19 +8,19 @@ */ public class SegmentTree { - + private var value: T private var function: (T, T) -> T private var leftBound: Int private var rightBound: Int private var leftChild: SegmentTree? private var rightChild: SegmentTree? - + 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 { @@ -30,19 +30,19 @@ public class SegmentTree { value = function(leftChild!.value, rightChild!.value) } } - + public convenience init(array: [T], function: @escaping (T, T) -> T) { self.init(array: array, leftBound: 0, rightBound: array.count-1, function: function) } - + 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 { @@ -53,7 +53,7 @@ public class SegmentTree { return function(leftResult, rightResult) } } - + public func replaceItem(at index: Int, withItem item: T) { if leftBound == rightBound { value = item diff --git a/Select Minimum Maximum/Maximum.swift b/Select Minimum Maximum/Maximum.swift index 991a0544e..82b76fd73 100644 --- a/Select Minimum Maximum/Maximum.swift +++ b/Select Minimum Maximum/Maximum.swift @@ -3,13 +3,9 @@ */ func maximum(_ array: [T]) -> T? { - var array = array - guard !array.isEmpty else { - return nil - } - - var maximum = array.removeFirst() - for element in array { + guard var maximum = array.first else { return nil } + + for element in array.dropFirst() { maximum = element > maximum ? element : maximum } return maximum diff --git a/Select Minimum Maximum/Minimum.swift b/Select Minimum Maximum/Minimum.swift index 90dfbd54b..75ecfc3b1 100644 --- a/Select Minimum Maximum/Minimum.swift +++ b/Select Minimum Maximum/Minimum.swift @@ -3,13 +3,9 @@ */ func minimum(_ array: [T]) -> T? { - var array = array - guard !array.isEmpty else { - return nil - } - - var minimum = array.removeFirst() - for element in array { + guard var minimum = array.first else { return nil } + + for element in array.dropFirst() { minimum = element < minimum ? element : minimum } return minimum diff --git a/Select Minimum Maximum/MinimumMaximumPairs.swift b/Select Minimum Maximum/MinimumMaximumPairs.swift index b7deb570a..a19b037d1 100644 --- a/Select Minimum Maximum/MinimumMaximumPairs.swift +++ b/Select Minimum Maximum/MinimumMaximumPairs.swift @@ -3,21 +3,13 @@ */ func minimumMaximum(_ array: [T]) -> (minimum: T, maximum: T)? { - var array = array - guard !array.isEmpty else { - return nil - } - - var minimum = array.first! - var maximum = array.first! + guard var minimum = array.first else { return nil } + var maximum = minimum - let hasOddNumberOfItems = array.count % 2 != 0 - if hasOddNumberOfItems { - array.removeFirst() - } - - while !array.isEmpty { - let pair = (array.removeFirst(), array.removeFirst()) + // if 'array' has an odd number of items, let 'minimum' or 'maximum' deal with the leftover + let start = array.count % 2 // 1 if odd, skipping the first element + for i in stride(from: start, to: array.count, by: 2) { + let pair = (array[i], array[i+1]) if pair.0 > pair.1 { if pair.0 > maximum { @@ -35,6 +27,6 @@ func minimumMaximum(_ array: [T]) -> (minimum: T, maximum: T)? { } } } - + return (minimum, maximum) } diff --git a/Select Minimum Maximum/README.markdown b/Select Minimum Maximum/README.markdown index 09ca97039..c55272c63 100644 --- a/Select Minimum Maximum/README.markdown +++ b/Select Minimum Maximum/README.markdown @@ -24,26 +24,22 @@ Here is a simple implementation in Swift: ```swift func minimum(_ array: [T]) -> T? { - var array = array - guard !array.isEmpty else { + guard var minimum = array.first else { return nil } - var minimum = array.removeFirst() - for element in array { + for element in array.dropFirst() { minimum = element < minimum ? element : minimum } return minimum } func maximum(_ array: [T]) -> T? { - var array = array - guard !array.isEmpty else { + guard var maximum = array.first else { return nil } - var maximum = array.removeFirst() - for element in array { + for element in array.dropFirst() { maximum = element > maximum ? element : maximum } return maximum @@ -68,6 +64,13 @@ array.minElement() // This will return 3 array.maxElement() // This will return 9 ``` +```swift +let array = [ 8, 3, 9, 4, 6 ] +//swift3 +array.min() // This will return 3 +array.max() // This will return 9 +``` + ## Maximum and minimum To find both the maximum and minimum values contained in array while minimizing the number of comparisons we can compare the items in pairs. @@ -92,21 +95,16 @@ Here is a simple implementation in Swift: ```swift func minimumMaximum(_ array: [T]) -> (minimum: T, maximum: T)? { - var array = array - guard !array.isEmpty else { + guard var minimum = array.first else { return nil } + var maximum = minimum - var minimum = array.first! - var maximum = array.first! - - let hasOddNumberOfItems = array.count % 2 != 0 - if hasOddNumberOfItems { - array.removeFirst() - } + // if 'array' has an odd number of items, let 'minimum' or 'maximum' deal with the leftover + let start = array.count % 2 // 1 if odd, skipping the first element + for i in stride(from: start, to: array.count, by: 2) { + let pair = (array[i], array[i+1]) - while !array.isEmpty { - let pair = (array.removeFirst(), array.removeFirst()) if pair.0 > pair.1 { if pair.0 > maximum { maximum = pair.0 diff --git a/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift b/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift index 182060805..e1101a864 100644 --- a/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift +++ b/Select Minimum Maximum/SelectMinimumMaximum.playground/Contents.swift @@ -1,12 +1,15 @@ +// 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? { - var array = array - guard !array.isEmpty else { + guard var minimum = array.first else { return nil } - var minimum = array.removeFirst() - for element in array { + for element in array.dropFirst() { minimum = element < minimum ? element : minimum } return minimum @@ -14,13 +17,11 @@ func minimum(_ array: [T]) -> T? { // Compare each item to find maximum func maximum(_ array: [T]) -> T? { - var array = array - guard !array.isEmpty else { + guard var maximum = array.first else { return nil } - var maximum = array.removeFirst() - for element in array { + for element in array.dropFirst() { maximum = element > maximum ? element : maximum } return maximum @@ -28,22 +29,18 @@ func maximum(_ array: [T]) -> T? { // Compare in pairs to find minimum and maximum func minimumMaximum(_ array: [T]) -> (minimum: T, maximum: T)? { - var array = array guard !array.isEmpty else { return nil } - + var minimum = array.first! var maximum = array.first! - - let hasOddNumberOfItems = array.count % 2 != 0 - if hasOddNumberOfItems { - array.removeFirst() - } - - while !array.isEmpty { - let pair = (array.removeFirst(), array.removeFirst()) - + + // if 'array' has an odd number of items, let 'minimum' or 'maximum' deal with the leftover + let start = array.count % 2 // 1 if odd, skipping the first element + for i in stride(from: start, to: array.count, by: 2) { + let pair = (array[i], array[i+1]) + if pair.0 > pair.1 { if pair.0 > maximum { maximum = pair.0 @@ -60,7 +57,7 @@ func minimumMaximum(_ array: [T]) -> (minimum: T, maximum: T)? { } } } - + return (minimum, maximum) } diff --git a/Select Minimum Maximum/Tests/MaximumTests.swift b/Select Minimum Maximum/Tests/MaximumTests.swift index cd63ac0a3..4a956dbfb 100644 --- a/Select Minimum Maximum/Tests/MaximumTests.swift +++ b/Select Minimum Maximum/Tests/MaximumTests.swift @@ -49,7 +49,7 @@ class MaximumTests: XCTestCase { let result = maximum(array) - XCTAssertEqual(result, array.maxElement()) + XCTAssertEqual(result, array.max()) } } } diff --git a/Select Minimum Maximum/Tests/MinimumMaximumPairsTests.swift b/Select Minimum Maximum/Tests/MinimumMaximumPairsTests.swift index 6cde21021..4d2162617 100644 --- a/Select Minimum Maximum/Tests/MinimumMaximumPairsTests.swift +++ b/Select Minimum Maximum/Tests/MinimumMaximumPairsTests.swift @@ -71,8 +71,8 @@ class MinimumMaximumPairsTests: XCTestCase { let result = minimumMaximum(array)! - XCTAssertEqual(result.minimum, array.minElement()) - XCTAssertEqual(result.maximum, array.maxElement()) + XCTAssertEqual(result.minimum, array.min()) + XCTAssertEqual(result.maximum, array.max()) } } } diff --git a/Select Minimum Maximum/Tests/MinimumTests.swift b/Select Minimum Maximum/Tests/MinimumTests.swift index ed4fbe1f0..c01d25fac 100644 --- a/Select Minimum Maximum/Tests/MinimumTests.swift +++ b/Select Minimum Maximum/Tests/MinimumTests.swift @@ -49,7 +49,7 @@ class MinimumTests: XCTestCase { let result = minimum(array) - XCTAssertEqual(result, array.minElement()) + XCTAssertEqual(result, array.min()) } } } diff --git a/Select Minimum Maximum/Tests/TestHelper.swift b/Select Minimum Maximum/Tests/TestHelper.swift index cd782d305..062025a2b 100644 --- a/Select Minimum Maximum/Tests/TestHelper.swift +++ b/Select Minimum Maximum/Tests/TestHelper.swift @@ -1,5 +1,5 @@ import Foundation -func createRandomList(numberOfElements: Int) -> [UInt32] { +func createRandomList(_ numberOfElements: Int) -> [UInt32] { return (1...numberOfElements).map {_ in arc4random()} } diff --git a/Select Minimum Maximum/Tests/Tests.xcodeproj/project.pbxproj b/Select Minimum Maximum/Tests/Tests.xcodeproj/project.pbxproj index e2857d2b7..fb70d6970 100644 --- a/Select Minimum Maximum/Tests/Tests.xcodeproj/project.pbxproj +++ b/Select Minimum Maximum/Tests/Tests.xcodeproj/project.pbxproj @@ -98,11 +98,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Swift Algorithm Club"; TargetAttributes = { 7B2BBC7F1C779D720067B71D = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0820; }; }; }; @@ -165,8 +166,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -209,8 +212,10 @@ 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_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; @@ -229,6 +234,7 @@ MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -240,6 +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; }; name = Debug; }; @@ -251,6 +258,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; }; name = Release; }; diff --git a/Select Minimum Maximum/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/Select Minimum Maximum/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 8ef8d8581..dfcf6de42 100644 --- a/Select Minimum Maximum/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Select Minimum Maximum/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ (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 } @@ -151,7 +151,7 @@ The random number is 0.346. The formula gives: Just a tiny bit too high. We skip `"e"`. Only two candidates left... -Note that now literally we're dealing with a toin coss: if the random number is less than 0.5 we select `"f"` and we're done. If it's greater than 0.5, we go on to the final element. Let's say we get 0.583: +Note that now literally we're dealing with a coin toss: if the random number is less than 0.5 we select `"f"` and we're done. If it's greater than 0.5, we go on to the final element. Let's say we get 0.583: 2 * 0.583 = 1.166 @@ -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 df1bb2e32..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,24 +32,23 @@ 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 } - let poem = [ "there", "once", "was", "a", "man", "from", "nantucket", "who", "kept", "all", "of", "his", "cash", "in", "a", "bucket", @@ -57,8 +61,6 @@ let output = select(from: poem, count: 10) print(output) output.count - - // Use this to verify that all input elements have the same probability // of being chosen. The "counts" dictionary should have a roughly equal // count for each input element. 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 c583b47ea..b958da927 100644 --- a/Selection Sort/README.markdown +++ b/Selection Sort/README.markdown @@ -1,29 +1,29 @@ # Selection Sort -Goal: Sort an array from low to high (or high to low). +Goal: To sort an array from low to high (or high to low). -You are given an array of numbers and need to put them in the right order. The selection sort algorithm divides the array into two parts: the beginning of the array is sorted, while the rest of the array consists of the numbers that still remain to be sorted. +You are given an array of numbers and need to put them in the right order. The selection sort algorithm divides the array into two parts: the beginning of the array is sorted, while the rest of the array consists of the numbers that still remain to be sorted. [ ...sorted numbers... | ...unsorted numbers... ] -This is similar to [insertion sort](../Insertion Sort/), but the difference is in how new numbers are added to the sorted portion. +This is similar to [insertion sort](../Insertion%20Sort/), but the difference is in how new numbers are added to the sorted portion. It works as follows: -- Find the lowest number in the array. You start at index 0, loop through all the numbers in the array, and keep track of what the lowest number is. -- Swap the lowest number you've found with the number at index 0. Now the sorted portion consists of just the number at index 0. +- Find the lowest number in the array. You must start at index 0, loop through all the numbers in the array, and keep track of what the lowest number is. +- Swap the lowest number with the number at index 0. Now, the sorted portion consists of just the number at index 0. - Go to index 1. - Find the lowest number in the rest of the array. This time you start looking from index 1. Again you loop until the end of the array and keep track of the lowest number you come across. -- Swap it with the number at index 1. Now the sorted portion contains two numbers and extends from index 0 to index 1. +- Swap the lowest number with the number at index 1. Now, the sorted portion contains two numbers and extends from index 0 to index 1. - Go to index 2. -- 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 so on... until no numbers remain to be sorted. +- 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's 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 -Let's say the numbers to sort are `[ 5, 8, 3, 4, 6 ]`. We also keep track of where the sorted portion of the array ends, denoted by the `|` symbol. +Suppose the numbers to sort are `[ 5, 8, 3, 4, 6 ]`. We also keep track of where the sorted portion of the array ends, denoted by the `|` symbol. Initially, the sorted portion is empty: @@ -43,7 +43,7 @@ Again, we look for the lowest number, starting from the `|` bar. We find `4` and [ 3, 4 | 5, 8, 6 ] * * -With every step, the `|` bar moves one position to the right. We again look through the rest of the array and find `5` as the lowest number. There's no need to swap `5` with itself and we simply move forward: +With every step, the `|` bar moves one position to the right. We again look through the rest of the array and find `5` as the lowest number. There is no need to swap `5` with itself, and we simply move forward: [ 3, 4, 5 | 8, 6 ] * @@ -52,7 +52,7 @@ This process repeats until the array is sorted. Note that everything to the left [ 3, 4, 5, 6, 8 |] -As you can see, selection sort is an *in-place* sort because everything happens in the same array; no additional memory is necessary. You can also implement this as a *stable* sort so that identical elements do not get swapped around relative to each other (note that the version given below isn't stable). +The selection sort is an *in-place* sort because everything happens in the same array without using additional memory. You can also implement this as a *stable* sort so that identical elements do not get swapped around relative to each other (note that the version given below is not stable). ## The code @@ -65,16 +65,16 @@ func selectionSort(_ array: [Int]) -> [Int] { var a = array // 2 for x in 0 ..< a.count - 1 { // 3 - + var lowest = x for y in x + 1 ..< a.count { // 4 if a[y] < a[lowest] { lowest = y } } - + if x != lowest { // 5 - swap(&a[x], &a[lowest]) + a.swapAt(x, lowest) } } return a @@ -90,9 +90,9 @@ selectionSort(list) A step-by-step explanation of how the code works: -1. If the array is empty or only contains a single element, then there's not much point to sorting it. +1. If the array is empty or only contains a single element, then there is no need to sort. -2. Make a copy of the array. This is necessary because we cannot modify the contents of the `array` parameter directly in Swift. Like Swift's own `sort()`, the `selectionSort()` function will return a sorted *copy* of the original array. +2. Make a copy of the array. This is necessary because we cannot modify the contents of the `array` parameter directly in Swift. Like the Swift's `sort()` function, the `selectionSort()` function will return a sorted *copy* of the original array. 3. There are two loops inside this function. The outer loop looks at each of the elements in the array in turn; this is what moves the `|` bar forward. @@ -100,17 +100,17 @@ A step-by-step explanation of how the code works: 5. Swap the lowest number with the current array index. The `if` check is necessary because you can't `swap()` an element with itself in Swift. -In summary: For each element of the array, selection sort swaps positions with the lowest value from the rest of the array. As a result, the array gets sorted from the left to the right. (You can also do it right-to-left, in which case you always look for the largest number in the array. Give that a try!) +In summary: For each element of the array, the selection sort swaps positions with the lowest value from the rest of the array. As a result, the array gets sorted from the left to the right. (You can also do it right-to-left, in which case you always look for the largest number in the array. Give that a try!) -> **Note:** The outer loop ends at index `a.count - 2`. The very last element will automatically always be in the correct position because at that point there are no other smaller elements left. +> **Note:** The outer loop ends at index `a.count - 2`. The very last element will automatically be in the correct position because at that point there are no other smaller elements left. -The source file [SelectionSort.swift](SelectionSort.swift) has a version of this function that uses generics, so you can also use it to sort strings and other types. +The source file [SelectionSort.swift](SelectionSort.swift) has a version of this function that uses generics, so you can also use it to sort strings and other data types. ## Performance -Selection sort is easy to understand but it performs quite badly, **O(n^2)**. It's worse than [insertion sort](../Insertion Sort/) but better than [bubble sort](../Bubble Sort/). The killer is finding the lowest element in the rest of the array. This takes up a lot of time, especially since the inner loop will be performed over and over. +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. -[Heap sort](../Heap Sort/) uses the same principle as selection sort but has a really fast method for finding the minimum value in the rest of the array. Its performance is **O(n log n)**. +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)**. ## See also 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 6cb682035..72375e51b 100644 --- a/Shell Sort/README.markdown +++ b/Shell Sort/README.markdown @@ -39,7 +39,7 @@ As you can see, each sublist contains only every 4th item from the original arra We now call `insertionSort()` once on each sublist. -This particular version of [insertion sort](../Insertion Sort/) sorts from the back to the front. Each item in the sublist is compared against the others. If they're in the wrong order, the value is swapped and travels all the way down until we reach the start of the sublist. +This particular version of [insertion sort](../Insertion%20Sort/) sorts from the back to the front. Each item in the sublist is compared against the others. If they're in the wrong order, the value is swapped and travels all the way down until we reach the start of the sublist. So for sublist 0, we swap `4` with `72`, then swap `4` with `64`. After sorting, this sublist looks like: @@ -111,6 +111,24 @@ This is an old Commodore 64 BASIC version of shell sort that Matthijs used a lon 61300 GOTO 61220 61310 RETURN +## The Code: +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 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/ShellSortExample.swift b/Shell Sort/ShellSortExample.swift new file mode 100644 index 000000000..df0842770 --- /dev/null +++ b/Shell Sort/ShellSortExample.swift @@ -0,0 +1,32 @@ +// +// ShellSortExample.swift +// +// +// Created by Cheer on 2017/2/26. +// +// + +import Foundation + +public func shellSort(_ list : inout [Int]) { + var sublistCount = list.count / 2 + + while sublistCount > 0 { + for var index in 0.. list[index + sublistCount] { + swap(&list[index], &list[index + sublistCount]) + } + + guard sublistCount == 1 && index > 0 else { continue } + + while index > 0 && list[index - 1] > list[index] { + swap(&list[index - 1], &list[index]) + index -= 1 + } + } + sublistCount = sublistCount / 2 + } +} 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)/README.markdown b/Shortest Path (Unweighted)/README.markdown index a16519260..4c3c34d9c 100644 --- a/Shortest Path (Unweighted)/README.markdown +++ b/Shortest Path (Unweighted)/README.markdown @@ -12,11 +12,11 @@ If the [graph is unweighed](../Graph/), then finding the shortest path is easy: ## Unweighted graph: breadth-first search -[Breadth-first search](../Breadth-First Search/) is a method for traversing a tree or graph data structure. It starts at a source node and explores the immediate neighbor nodes first, before moving to the next level neighbors. As a convenient side effect, it automatically computes the shortest path between a source node and each of the other nodes in the tree or graph. +[Breadth-first search](../Breadth-First%20Search/) is a method for traversing a tree or graph data structure. It starts at a source node and explores the immediate neighbor nodes first, before moving to the next level neighbors. As a convenient side effect, it automatically computes the shortest path between a source node and each of the other nodes in the tree or graph. The result of the breadth-first search can be represented with a tree: -![The BFS tree](../Breadth-First Search/Images/TraversalTree.png) +![The BFS tree](../Breadth-First%20Search/Images/TraversalTree.png) The root of the tree is the node you started the breadth-first search from. To find the distance from node `A` to any other node, we simply count the number of edges in the tree. And so we find that the shortest path between `A` and `F` is 2. The tree not only tells you how long that path is, but also how to actually get from `A` to `F` (or any of the other nodes). @@ -57,7 +57,7 @@ queue.enqueue(element: G) G.distance = C.distance + 1 // result: 2 ``` -This continues until the queue is empty and we've visited all the nodes. Each time we discover a new node, it gets the `distance` of its parent plus 1. As you can see, this is exactly what the [breadth-first search](../Breadth-First Search/) algorithm does, except that we now also keep track of the distance travelled. +This continues until the queue is empty and we've visited all the nodes. Each time we discover a new node, it gets the `distance` of its parent plus 1. As you can see, this is exactly what the [breadth-first search](../Breadth-First%20Search/) algorithm does, except that we now also keep track of the distance travelled. Here's the code: @@ -97,6 +97,6 @@ This will output: Node(label: d, distance: 2), Node(label: e, distance: 2), Node(label: f, distance: 2), Node(label: g, distance: 2), Node(label: h, distance: 3) -> **Note:** This version of `breadthFirstSearchShortestPath()` does not actually produce the tree, it only computes the distances. See [minimum spanning tree](../Minimum Spanning Tree (Unweighted)/) on how you can convert the graph into a tree by removing edges. +> **Note:** This version of `breadthFirstSearchShortestPath()` does not actually produce the tree, it only computes the distances. See [minimum spanning tree](../Minimum%20Spanning%20Tree%20(Unweighted)/) on how you can convert the graph into a tree by removing edges. *Written by [Chris Pilcher](https://github.com/chris-pilcher) and Matthijs Hollemans* 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 2380ebb69..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() @@ -19,8 +24,6 @@ func breadthFirstSearchShortestPath(graph: Graph, source: Node) -> Graph { return shortestPathGraph } - - let graph = Graph() let nodeA = graph.addNode(label: "a") diff --git a/Shortest Path (Unweighted)/Tests/Graph.swift b/Shortest Path (Unweighted)/Tests/Graph.swift index 8322ba0a9..cce697344 100644 --- a/Shortest Path (Unweighted)/Tests/Graph.swift +++ b/Shortest Path (Unweighted)/Tests/Graph.swift @@ -2,7 +2,7 @@ open class Edge: Equatable { open var neighbor: Node - + public init(neighbor: Node) { self.neighbor = neighbor } @@ -12,33 +12,32 @@ public func == (lhs: Edge, rhs: Edge) -> Bool { return lhs.neighbor == rhs.neighbor } - // MARK: - Node open class Node: CustomStringConvertible, Equatable { open var neighbors: [Edge] - + open fileprivate(set) var label: String open var distance: Int? open var visited: Bool - + public init(label: String) { self.label = label neighbors = [] visited = false } - + open var description: String { if let distance = distance { return "Node(label: \(label), distance: \(distance))" } return "Node(label: \(label), distance: infinity)" } - + open var hasDistance: Bool { return distance != nil } - + open func remove(_ edge: Edge) { neighbors.remove(at: neighbors.index { $0 === edge }!) } @@ -52,25 +51,25 @@ public func == (lhs: Node, rhs: Node) -> Bool { open class Graph: CustomStringConvertible, Equatable { open fileprivate(set) var nodes: [Node] - + public init() { self.nodes = [] } - + open func addNode(label: String) -> Node { let node = Node(label: label) nodes.append(node) return node } - + open func addEdge(_ source: Node, neighbor: Node) { let edge = Edge(neighbor: neighbor) source.neighbors.append(edge) } - + open var description: String { var description = "" - + for node in nodes { if !node.neighbors.isEmpty { description += "[node: \(node.label) edges: \(node.neighbors.map { $0.neighbor.label})]" @@ -78,18 +77,18 @@ open class Graph: CustomStringConvertible, Equatable { } return description } - + open func findNodeWithLabel(label: String) -> Node { return nodes.filter { $0.label == label }.first! } - + open func duplicate() -> Graph { let duplicated = Graph() - + for node in nodes { duplicated.addNode(label: node.label) } - + for node in nodes { for edge in node.neighbors { let source = duplicated.findNodeWithLabel(label: node.label) @@ -97,7 +96,7 @@ open class Graph: CustomStringConvertible, Equatable { duplicated.addEdge(source, neighbor: neighbour) } } - + return duplicated } } 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.. Bool { public struct Token: CustomStringConvertible { let tokenType: TokenType - + init(tokenType: TokenType) { self.tokenType = tokenType } - + init(operand: Double) { tokenType = .operand(operand) } - + init(operatorType: OperatorType) { tokenType = .Operator(OperatorToken(operatorType: operatorType)) } - + var isOpenBracket: Bool { switch tokenType { case .openBracket: @@ -114,7 +114,7 @@ public struct Token: CustomStringConvertible { return false } } - + var isOperator: Bool { switch tokenType { case .Operator(_): @@ -123,7 +123,7 @@ public struct Token: CustomStringConvertible { return false } } - + var operatorToken: OperatorToken? { switch tokenType { case .Operator(let operatorToken): @@ -132,7 +132,7 @@ public struct Token: CustomStringConvertible { return nil } } - + public var description: String { return tokenType.description } @@ -140,27 +140,27 @@ public struct Token: CustomStringConvertible { public class InfixExpressionBuilder { private var expression = [Token]() - + public func addOperator(_ operatorType: OperatorType) -> InfixExpressionBuilder { expression.append(Token(operatorType: operatorType)) return self } - + public func addOperand(_ operand: Double) -> InfixExpressionBuilder { expression.append(Token(operand: operand)) return self } - + public func addOpenBracket() -> InfixExpressionBuilder { expression.append(Token(tokenType: .openBracket)) return self } - + public func addCloseBracket() -> InfixExpressionBuilder { expression.append(Token(tokenType: .closeBracket)) return self } - + public func build() -> [Token] { // Maybe do some validation here return expression @@ -169,29 +169,29 @@ public class InfixExpressionBuilder { // This returns the result of the shunting yard algorithm public func reversePolishNotation(_ expression: [Token]) -> String { - + var tokenStack = Stack() var reversePolishNotation = [Token]() - + for token in expression { switch token.tokenType { case .operand(_): reversePolishNotation.append(token) - + case .openBracket: tokenStack.push(token) - + case .closeBracket: while tokenStack.count > 0, let tempToken = tokenStack.pop(), !tempToken.isOpenBracket { reversePolishNotation.append(tempToken) } - + case .Operator(let operatorToken): for tempToken in tokenStack.makeIterator() { if !tempToken.isOperator { break } - + if let tempOperatorToken = tempToken.operatorToken { if operatorToken.associativity == .leftAssociative && operatorToken <= tempOperatorToken || operatorToken.associativity == .rightAssociative && operatorToken < tempOperatorToken { @@ -204,15 +204,14 @@ public func reversePolishNotation(_ expression: [Token]) -> String { tokenStack.push(token) } } - + while tokenStack.count > 0 { reversePolishNotation.append(tokenStack.pop()!) } - + return reversePolishNotation.map({token in token.description}).joined(separator: " ") } - // Simple demo let expr = InfixExpressionBuilder() diff --git a/Shunting Yard/ShuntingYard.playground/Sources/Stack.swift b/Shunting Yard/ShuntingYard.playground/Sources/Stack.swift index a67bb9d64..54ffe02ce 100644 --- a/Shunting Yard/ShuntingYard.playground/Sources/Stack.swift +++ b/Shunting Yard/ShuntingYard.playground/Sources/Stack.swift @@ -1,39 +1,36 @@ import Foundation public struct Stack { - fileprivate var array = [T]() + fileprivate var array = [T]() + + public init() { - 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 - } + } + + 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/Shunting Yard/ShuntingYard.swift b/Shunting Yard/ShuntingYard.swift index 97a72249d..201264650 100644 --- a/Shunting Yard/ShuntingYard.swift +++ b/Shunting Yard/ShuntingYard.swift @@ -18,7 +18,7 @@ public enum OperatorType: CustomStringConvertible { case multiply case percent case exponent - + public var description: String { switch self { case .add: @@ -42,7 +42,7 @@ public enum TokenType: CustomStringConvertible { case closeBracket case Operator(OperatorToken) case operand(Double) - + public var description: String { switch self { case .openBracket: @@ -59,11 +59,11 @@ public enum TokenType: CustomStringConvertible { public struct OperatorToken: CustomStringConvertible { let operatorType: OperatorType - + init(operatorType: OperatorType) { self.operatorType = operatorType } - + var precedence: Int { switch operatorType { case .add, .subtract: @@ -74,7 +74,7 @@ public struct OperatorToken: CustomStringConvertible { return 10 } } - + var associativity: OperatorAssociativity { switch operatorType { case .add, .subtract, .divide, .multiply, .percent: @@ -83,7 +83,7 @@ public struct OperatorToken: CustomStringConvertible { return .rightAssociative } } - + public var description: String { return operatorType.description } @@ -99,19 +99,19 @@ func < (left: OperatorToken, right: OperatorToken) -> Bool { public struct Token: CustomStringConvertible { let tokenType: TokenType - + init(tokenType: TokenType) { self.tokenType = tokenType } - + init(operand: Double) { tokenType = .operand(operand) } - + init(operatorType: OperatorType) { tokenType = .Operator(OperatorToken(operatorType: operatorType)) } - + var isOpenBracket: Bool { switch tokenType { case .openBracket: @@ -120,7 +120,7 @@ public struct Token: CustomStringConvertible { return false } } - + var isOperator: Bool { switch tokenType { case .Operator(_): @@ -129,7 +129,7 @@ public struct Token: CustomStringConvertible { return false } } - + var operatorToken: OperatorToken? { switch tokenType { case .Operator(let operatorToken): @@ -138,7 +138,7 @@ public struct Token: CustomStringConvertible { return nil } } - + public var description: String { return tokenType.description } @@ -146,27 +146,27 @@ public struct Token: CustomStringConvertible { public class InfixExpressionBuilder { private var expression = [Token]() - + public func addOperator(_ operatorType: OperatorType) -> InfixExpressionBuilder { expression.append(Token(operatorType: operatorType)) return self } - + public func addOperand(_ operand: Double) -> InfixExpressionBuilder { expression.append(Token(operand: operand)) return self } - + public func addOpenBracket() -> InfixExpressionBuilder { expression.append(Token(tokenType: .openBracket)) return self } - + public func addCloseBracket() -> InfixExpressionBuilder { expression.append(Token(tokenType: .closeBracket)) return self } - + public func build() -> [Token] { // Maybe do some validation here return expression @@ -175,29 +175,29 @@ public class InfixExpressionBuilder { // This returns the result of the shunting yard algorithm public func reversePolishNotation(_ expression: [Token]) -> String { - + var tokenStack = Stack() var reversePolishNotation = [Token]() - + for token in expression { switch token.tokenType { case .operand(_): reversePolishNotation.append(token) - + case .openBracket: tokenStack.push(token) - + case .closeBracket: while tokenStack.count > 0, let tempToken = tokenStack.pop(), !tempToken.isOpenBracket { reversePolishNotation.append(tempToken) } - + case .Operator(let operatorToken): for tempToken in tokenStack.makeIterator() { if !tempToken.isOperator { break } - + if let tempOperatorToken = tempToken.operatorToken { if operatorToken.associativity == .leftAssociative && operatorToken <= tempOperatorToken || operatorToken.associativity == .rightAssociative && operatorToken < tempOperatorToken { @@ -210,10 +210,10 @@ public func reversePolishNotation(_ expression: [Token]) -> String { tokenStack.push(token) } } - + while tokenStack.count > 0 { reversePolishNotation.append(tokenStack.pop()!) } - + return reversePolishNotation.map({token in token.description}).joined(separator: " ") } 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)/SSSP/BellmanFord.swift b/Single-Source Shortest Paths (Weighted)/SSSP/BellmanFord.swift index 5b6670806..41be9759b 100644 --- a/Single-Source Shortest Paths (Weighted)/SSSP/BellmanFord.swift +++ b/Single-Source Shortest Paths (Weighted)/SSSP/BellmanFord.swift @@ -41,7 +41,7 @@ extension BellmanFord: SSSPAlgorithm { for _ in 0 ..< vertices.count - 1 { var weightsUpdated = false - edges.forEach() { edge in + edges.forEach { edge in let weight = edge.weight! let relaxedDistance = weights[edge.from.index] + weight let nextVertexIdx = edge.to.index @@ -116,7 +116,7 @@ extension BellmanFordResult: SSSPResult { return nil } - return path.map() { vertex in + return path.map { vertex in return vertex.data } } 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 5fc61bf8e..8a1959f70 100644 --- a/Skip-List/SkipList.swift +++ b/Skip-List/SkipList.swift @@ -20,10 +20,8 @@ // 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] = [] @@ -52,86 +50,73 @@ 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 { public typealias Node = DataNode - - var data : Payload? - fileprivate var key : Key? - var next : Node? - var down : Node? - + + 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){} - -} + 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 + var currentNode: Node? = head + var isFound: Bool = false while !isFound { if let node = currentNode { - + switch node.next { case .none: - - currentNode = node.down + + currentNode = node.down case .some(let value) where value.key != nil: if value.key == key { isFound = true break - } - else { + } else { if key < value.key! { currentNode = node.down } else { currentNode = node.next - } + } } - + default: continue } - - } else { + + } else { break } } @@ -141,7 +126,7 @@ extension SkipList { } else { return nil } - + } func search(key: Key) -> Payload? { @@ -149,22 +134,20 @@ extension SkipList { return nil } - return node.next!.data - } - -} - + 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) + 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() { @@ -176,9 +159,8 @@ extension SkipList { head = newHead currentTopNode = node } - - } + } private func insertItem(key: Key, data: Payload) { var stack = Stack() @@ -193,13 +175,13 @@ extension SkipList { } else { currentNode = nextNode } - + } else { stack.push(currentNode!) - currentNode = currentNode!.down + currentNode = currentNode!.down } - - } + + } let itemAtLayer = stack.pop() var node = Node(key: key, data: data) @@ -210,32 +192,31 @@ extension SkipList { 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 { + + } else { let nextNode = stack.pop() - + node = Node(key: key, data: data) node.down = currentTopNode node.next = nextNode!.next nextNode!.next = node currentTopNode = node } - } + } } - - 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. - var currentNode = node.next + // replace, in case of key already exists. + var currentNode = node.next while currentNode != nil && currentNode!.key == key { currentNode!.data = data currentNode = currentNode!.down @@ -243,14 +224,13 @@ extension SkipList { } 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. @@ -259,33 +239,33 @@ extension SkipList { 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?{ + + public func get(key: Key) -> Payload? { 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 c21a77fd8..968f6841b 100644 --- a/Slow Sort/SlowSort.swift +++ b/Slow Sort/SlowSort.swift @@ -1,28 +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 75% rename from Quicksort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme rename to Splay Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 8ef8d8581..b3f6d696a 100644 --- a/Quicksort/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/Splay Tree/Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ + 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 974d5a443..b12f713f5 100644 --- a/Stack/Stack.swift +++ b/Stack/Stack.swift @@ -1,38 +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/Strassen Matrix Multiplication/README.markdown b/Strassen Matrix Multiplication/README.markdown new file mode 100644 index 000000000..07ce534b2 --- /dev/null +++ b/Strassen Matrix Multiplication/README.markdown @@ -0,0 +1,468 @@ +# Strassen Matrix Multiplication + +## Goal ++ To quickly perform a matrix multiplication operation on two matricies + +## What is Matrix Multiplication?? + +> Note: If you are already familiar with Linear Algebra/Matrix Multiplication, feel free to skip this section + +Before we begin, you may ask what is matrix multiplication? Great question! It is **NOT** multiplying two matricies term-by-term. Matrix multiplication is a mathematical operation that combines two matricies into a single one. Sounds like multiplying term-by-term huh? It's not... our lives would be much easier if it were. To see how matrix multiplcation works, let's look at an example. + +### Example: Matrix Multiplication + +``` +matrix A = |1 2|, matrix B = |5 6| + |3 4| |7 8| + +A * B = C + +|1 2| * |5 6| = |1*5+2*7 1*6+2*8| = |19 22| +|3 4| |7 8| |3*5+4*7 3*6+4*8| |43 50| +``` + +What's going on here? To start, we're multiplying matricies A & B. Our new matrix, C's, elements `[i, j]` are determined by the dot product of the first matrix's ith row and the second matrix's jth column. See [here](https://www.khanacademy.org/math/linear-algebra/vectors-and-spaces/dot-cross-products/v/vector-dot-product-and-vector-length) for a refresher on the dot product. + +So the upper left element `[i=1, j=1]` of our new matrix is a combination of A's 1st row and B's 1st column. + + A's first row = [1, 2] + B's first column = [5, 7] + + [1, 2] dot [5, 7] = [1*5 + 2*7] = [19] = C[1, 1] + +Now let's try this for `[i=1, j=2]`. Because `i=1` and `j=2`, this will represent the upper right element in our new matrix, C. + + A's first row = [1, 2] + B's second column = [6, 8] + + [1, 2] dot [6, 8] = [1*6 + 2*8] = [22] = C[1, 2] + +If we do this for each row & column of A & B we'll get our result matrix C! + +Here's a great graphic that visually shows you what's going on. + +![](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Matrix_multiplication_principle.svg/1024px-Matrix_multiplication_principle.svg.png) + +[Source](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Matrix_multiplication_principle.svg/1024px-Matrix_multiplication_principle.svg.png) + + +## Matix Multiplication Algorithm + +So how do we implement matrix multiplication in an algoirthm? We'll start with the basic version and from there move on to Strassen's Algorithm. + ++ [Basic Version](#basic-version) ++ [Strassen's Algorithm](#strassens-algorithm) + +### Basic Version + +Remember the method we used to solve matrix multiplication [above](#what-is-matrix-multiplication??)? Let's try to implement that first! We first assert that the two matricies are the right size. + +`assert(A.columns == B.rows, "Two matricies can only be matrix mulitiplied if one has dimensions mxn & the other has dimensions nxp where m, n, p are in R")` + +> **NOTE:** A's # of columns HAS to equal B's # of rows for matrix multiplication to work + +Next, we loop over A's columns and B's rows. Because we know both A's columns & B's rows are the same length, we set that length equal to `n`. + +```swift +for i in 0..) -> Matrix { + let A = self + assert(A.columns == B.rows, "Two matricies can only be matrix mulitiplied if one has dimensions mxn & the other has dimensions nxp where m, n, p are in R") + let n = A.columns + var C = Matrix(rows: A.rows, columns: B.columns) + + for i in 0..) -> Matrix { + let A = self + assert(A.columns == B.rows, "Two matricies can only be matrix mulitiplied if one has dimensions mxn & the other has dimensions nxp where m, n, p are in R") + + let n = max(A.rows, A.columns, B.rows, B.columns) + let m = nextPowerOfTwo(after: n) + + var APrep = Matrix(size: m) + var BPrep = Matrix(size: m) + + A.forEach { (i, j) in + APrep[i,j] = A[i,j] + } + + B.forEach { (i, j) in + BPrep[i,j] = B[i,j] + } + + let CPrep = APrep.strassenR(by: BPrep) + var C = Matrix(rows: A.rows, columns: B.columns) + for i in 0..) -> Matrix { + let A = self + assert(A.isSquare && B.isSquare, "This function requires square matricies!") + guard A.rows > 1 && B.rows > 1 else { return A * B } + + let n = A.rows + let nBy2 = n / 2 + + /* + Assume submatricies are allocated as follows + + matrix A = |a b|, matrix B = |e f| + |c d| |g h| + */ + + var a = Matrix(size: nBy2) + var b = Matrix(size: nBy2) + var c = Matrix(size: nBy2) + var d = Matrix(size: nBy2) + var e = Matrix(size: nBy2) + var f = Matrix(size: nBy2) + var g = Matrix(size: nBy2) + var h = Matrix(size: nBy2) + + for i in 0.. Int { + return Int(pow(2, ceil(log2(Double(n))))) + } +} +``` + +## Appendix + +### Number Protocol + +I use a number protocol to enable by Matrix to be generic. + +The Number protocol ensures three things: + +1. Everything that is a number can be multiplied +2. Everything that is a number can be added/subtracted +3. Everything that is a number has a zero value + +Extending `Int`, `Float`, and `Double` to conform to this protocol is now very straightforward. All you need to do is implement the `static var zero`! + +```swift +public protocol Number: Multipliable, Addable { + static var zero: Self { get } +} + +public protocol Addable { + static func +(lhs: Self, rhs: Self) -> Self + static func -(lhs: Self, rhs: Self) -> Self +} + +public protocol Multipliable { + static func *(lhs: Self, rhs: Self) -> Self +} +``` + +### Dot Product + +I extend `Array` to include a dot product function for when the Array's element conform to the `Number` protocol. + +```swift +extension Array where Element: Number { + public func dot(_ b: Array) -> Element { + let a = self + assert(a.count == b.count, "Can only take the dot product of arrays of the same length!") + let c = a.indices.map{ a[$0] * b[$0] } + return c.reduce(Element.zero, { $0 + $1 }) + } +} +``` + +## Resources + ++ [Intro to Matrix Multiplication | Khan Academy](https://www.khanacademy.org/math/precalculus/precalc-matrices/multiplying-matrices-by-matrices/v/matrix-multiplication-intro) ++ [Matrix Multiplication | Wikipedia](https://en.wikipedia.org/wiki/Matrix_multiplication) ++ [Strassen Algorithm | Wikipedia](https://en.wikipedia.org/wiki/Strassen_algorithm) ++ [Strassen Algorithm | Wolfram MathWorld](http://mathworld.wolfram.com/StrassenFormulas.html) ++ [Strassens Algorithm | Geeks for Geeks](http://www.geeksforgeeks.org/strassens-matrix-multiplication/) + +*Written for Swift Algorithm Club by Richard Ash* \ No newline at end of file diff --git a/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/Contents.swift b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/Contents.swift new file mode 100644 index 000000000..6b9314557 --- /dev/null +++ b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/Contents.swift @@ -0,0 +1,21 @@ +//: Playground - noun: a place where people can play + +import Foundation + +var A = Matrix(rows: 2, columns: 4, initialValue: 0) +A[.row, 0] = [2, 3, -1, 0] +A[.row, 1] = [-7, 2, 1, 10] + +var B = Matrix(rows: 4, columns: 2, initialValue: 0) +print(B) +B[.column, 0] = [3, 2, -1, 2] +B[.column, 1] = [4, 1, 2, 7] + +let C = A.matrixMultiply(by: B) +let D = A.strassenMatrixMultiply(by: B) +let E = B.matrixMultiply(by: A) +let F = B.strassenMatrixMultiply(by: A) +print(C) +print(D) +print(E) +print(F) diff --git a/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/Sources/Matrix.swift b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/Sources/Matrix.swift new file mode 100644 index 000000000..da4b95a78 --- /dev/null +++ b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/Sources/Matrix.swift @@ -0,0 +1,290 @@ +// +// Matrix.swift +// +// +// Created by Richard Ash on 10/28/16. +// +// + +import Foundation + +public struct Matrix { + + // MARK: - Martix Objects + + public enum Index { + case row, column + } + + public struct Size: Equatable { + let rows: Int, columns: Int + + public static func == (lhs: Size, rhs: Size) -> Bool { + return lhs.columns == rhs.columns && lhs.rows == rhs.rows + } + } + + // MARK: - Variables + + let rows: Int, columns: Int + let size: Size + + var grid: [T] + + var isSquare: Bool { + return rows == columns + } + + // MARK: - Init + + public init(rows: Int, columns: Int, initialValue: T = T.zero) { + self.rows = rows + self.columns = columns + self.size = Size(rows: rows, columns: columns) + self.grid = Array(repeating: initialValue, count: rows * columns) + } + + public init(size: Int, initialValue: T = T.zero) { + self.init(rows: size, columns: size, initialValue: initialValue) + } + + // MARK: - Private Functions + + fileprivate func indexIsValid(row: Int, column: Int) -> Bool { + return row >= 0 && row < rows && column >= 0 && column < columns + } + + // MARK: - Subscript + + public subscript(row: Int, column: Int) -> T { + get { + assert(indexIsValid(row: row, column: column), "Index out of range") + return grid[(row * columns) + column] + } set { + assert(indexIsValid(row: row, column: column), "Index out of range") + grid[(row * columns) + column] = newValue + } + } + + public subscript(type: Matrix.Index, value: Int) -> [T] { + get { + switch type { + case .row: + assert(indexIsValid(row: value, column: 0), "Index out of range") + return Array(grid[(value * columns)..<(value * columns) + columns]) + case .column: + assert(indexIsValid(row: 0, column: value), "Index out of range") + let column = (0.. T in + let currentColumnIndex = currentRow * columns + value + return grid[currentColumnIndex] + } + return column + } + } set { + switch type { + case .row: + assert(newValue.count == columns) + for (column, element) in newValue.enumerated() { + grid[(value * columns) + column] = element + } + case .column: + assert(newValue.count == rows) + for (row, element) in newValue.enumerated() { + grid[(row * columns) + value] = element + } + } + } + } + + // MARK: - Public Functions + + public func row(for columnIndex: Int) -> [T] { + assert(indexIsValid(row: columnIndex, column: 0), "Index out of range") + return Array(grid[(columnIndex * columns)..<(columnIndex * columns) + columns]) + } + + public func column(for rowIndex: Int) -> [T] { + assert(indexIsValid(row: 0, column: rowIndex), "Index out of range") + + let column = (0.. T in + let currentColumnIndex = currentRow * columns + rowIndex + return grid[currentColumnIndex] + } + return column + } + + public func forEach(_ body: (Int, Int) throws -> Void) rethrows { + for row in 0..) -> Matrix { + let A = self + assert(A.columns == B.rows, "Two matricies can only be matrix mulitiplied if one has dimensions mxn & the other has dimensions nxp where m, n, p are in R") + + var C = Matrix(rows: A.rows, columns: B.columns) + + for i in 0..) -> Matrix { + let A = self + assert(A.columns == B.rows, "Two matricies can only be matrix mulitiplied if one has dimensions mxn & the other has dimensions nxp where m, n, p are in R") + + let n = max(A.rows, A.columns, B.rows, B.columns) + let m = nextPowerOfTwo(after: n) + + var APrep = Matrix(size: m) + var BPrep = Matrix(size: m) + + A.forEach { (i, j) in + APrep[i, j] = A[i, j] + } + + B.forEach { (i, j) in + BPrep[i, j] = B[i, j] + } + + let CPrep = APrep.strassenR(by: BPrep) + var C = Matrix(rows: A.rows, columns: B.columns) + for i in 0..) -> Matrix { + let A = self + assert(A.isSquare && B.isSquare, "This function requires square matricies!") + guard A.rows > 1 && B.rows > 1 else { return A * B } + + let n = A.rows + let nBy2 = n / 2 + + /* + Assume submatricies are allocated as follows + + matrix A = |a b|, matrix B = |e f| + |c d| |g h| + */ + + var a = Matrix(size: nBy2) + var b = Matrix(size: nBy2) + var c = Matrix(size: nBy2) + var d = Matrix(size: nBy2) + var e = Matrix(size: nBy2) + var f = Matrix(size: nBy2) + var g = Matrix(size: nBy2) + var h = Matrix(size: nBy2) + + for i in 0.. Int { + return Int(pow(2, ceil(log2(Double(n))))) + } +} + +// Term-by-term Matrix Math + +extension Matrix: Addable { + public static func +(lhs: Matrix, rhs: Matrix) -> Matrix { + assert(lhs.size == rhs.size, "To term-by-term add matricies they need to be the same size!") + let rows = lhs.rows + let columns = lhs.columns + + var newMatrix = Matrix(rows: rows, columns: columns) + for row in 0..(lhs: Matrix, rhs: Matrix) -> Matrix { + assert(lhs.size == rhs.size, "To term-by-term subtract matricies they need to be the same size!") + let rows = lhs.rows + let columns = lhs.columns + + var newMatrix = Matrix(rows: rows, columns: columns) + for row in 0..(lhs: Matrix, rhs: Matrix) -> Matrix { + assert(lhs.size == rhs.size, "To term-by-term multiply matricies they need to be the same size!") + let rows = lhs.rows + let columns = lhs.columns + + var newMatrix = Matrix(rows: rows, columns: columns) + for row in 0.. Self + static func - (lhs: Self, rhs: Self) -> Self +} + +public protocol Multipliable { + static func * (lhs: Self, rhs: Self) -> Self +} + +extension Int: Number { + public static var zero: Int { return 0 } +} + +extension Double: Number { + public static var zero: Double { return 0.0 } +} + +extension Float: Number { + public static var zero: Float { return 0.0 } +} + +extension Array where Element: Number { + public func dot(_ b: Array) -> Element { + let a = self + assert(a.count == b.count, "Can only take the dot product of arrays of the same length!") + let c = a.indices.map { a[$0] * b[$0] } + return c.reduce(Element.zero, { $0 + $1 }) + } +} diff --git a/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/contents.xcplayground b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/contents.xcplayground new file mode 100644 index 000000000..9f5f2f40c --- /dev/null +++ b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/playground.xcworkspace/contents.xcworkspacedata b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Strassen Matrix Multiplication/StrassensMatrixMultiplication.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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/TST.playground/Sources/TSTNode.swift b/Ternary Search Tree/TST.playground/Sources/TSTNode.swift index efdfd4299..7fea88d1d 100644 --- a/Ternary Search Tree/TST.playground/Sources/TSTNode.swift +++ b/Ternary Search Tree/TST.playground/Sources/TSTNode.swift @@ -17,7 +17,6 @@ class TSTNode { //Children var left: TSTNode?, right: TSTNode?, middle: TSTNode? - init(key: Character, data: Element?) { self.key = key self.data = data diff --git a/Ternary Search Tree/TST.playground/Sources/TernarySearchTree.swift b/Ternary Search Tree/TST.playground/Sources/TernarySearchTree.swift index fed8f9710..9c4c860d8 100644 --- a/Ternary Search Tree/TST.playground/Sources/TernarySearchTree.swift +++ b/Ternary Search Tree/TST.playground/Sources/TernarySearchTree.swift @@ -8,14 +8,13 @@ import Foundation - public class TernarySearchTree { var root: TSTNode? public init() {} - //MARK: - Insertion + // MARK: - Insertion public func insert(data: Element, withKey key: String) -> Bool { return insert(node: &root, withData: data, andKey: key, atIndex: 0) @@ -58,9 +57,7 @@ public class TernarySearchTree { } } - - //MARK: - Finding - + // MARK: - Finding public func find(key: String) -> Element? { return find(node: root, withKey: key, atIndex: 0) diff --git a/Ternary Search Tree/TST.playground/Sources/Utils.swift b/Ternary Search Tree/TST.playground/Sources/Utils.swift index cabfe1b68..97f09fdd2 100644 --- a/Ternary Search Tree/TST.playground/Sources/Utils.swift +++ b/Ternary Search Tree/TST.playground/Sources/Utils.swift @@ -9,22 +9,22 @@ import Foundation public struct Utils { - + public static let shared = Utils() - + let allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" //Random string generator from: //http://stackoverflow.com/questions/26845307/generate-random-alphanumeric-string-in-swift/26845710 public func randomAlphaNumericString(withLength length: Int) -> String { let allowedCharsCount = UInt32(allowedChars.characters.count) var randomString = "" - + for _ in (0.. { */ public init() {} - //MARK: - Insertion + // MARK: - Insertion /** Public insertion method. @@ -84,8 +84,7 @@ public class TernarySearchTree { } } - - //MARK: - Finding + // MARK: - Finding /** Public find method. diff --git a/Ternary Search Tree/Tests/TernarySearchTreeTests.swift b/Ternary Search Tree/Tests/TernarySearchTreeTests.swift index 40739c888..a595e40a3 100644 --- a/Ternary Search Tree/Tests/TernarySearchTreeTests.swift +++ b/Ternary Search Tree/Tests/TernarySearchTreeTests.swift @@ -9,63 +9,69 @@ import XCTest class TernarySearchTreeTests: XCTestCase { - - let testCount = 30 - - func testCanFindStringInTree() { - var testStrings: [(key: String, data: String)] = [] - let treeOfStrings = TernarySearchTree() + let testCount = 30 - for _ in (1...testCount) { - var randomLength = Int(arc4random_uniform(10)) - - var key = Utils.shared.randomAlphaNumericString(withLength: randomLength) - - 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)") - - if key != "" && data != "" { - testStrings.append((key, data)) - treeOfStrings.insert(data: data, withKey: key) - } - } - - for aTest in testStrings { - let data = treeOfStrings.find(key: aTest.key) - XCTAssertNotNil(data) - XCTAssertEqual(data, aTest.data) - } + func testSwift4() { + // last checked with Xcode 9.0b4 + #if swift(>=4.0) + print("Hello, Swift 4!") + #endif + } + + func testCanFindStringInTree() { + var testStrings: [(key: String, data: String)] = [] + let treeOfStrings = TernarySearchTree() + + for _ in (1...testCount) { + var randomLength = Int(arc4random_uniform(10)) + + var key = Utils.shared.randomAlphaNumericString(withLength: randomLength) + + 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)") + + 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) - } - - 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 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) + } + } + + 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/Ternary Search Tree/Utils.swift b/Ternary Search Tree/Utils.swift index cabfe1b68..97f09fdd2 100644 --- a/Ternary Search Tree/Utils.swift +++ b/Ternary Search Tree/Utils.swift @@ -9,22 +9,22 @@ import Foundation public struct Utils { - + public static let shared = Utils() - + let allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" //Random string generator from: //http://stackoverflow.com/questions/26845307/generate-random-alphanumeric-string-in-swift/26845710 public func randomAlphaNumericString(withLength length: Int) -> String { let allowedCharsCount = UInt32(allowedChars.characters.count) var randomString = "" - + for _ in (0.. For more information about in-order traversals [see here](../Binary Tree/). +> For more information about in-order traversals [see here](../Binary%20Tree/). ## Predecessors and successors @@ -222,7 +222,7 @@ walking through some boring code, it is best to explain this with an example (although you can read through [the implementation](ThreadedBinaryTree.swift) if you want to know the finer details). Please note that this requires knowledge of binary search trees, so make sure you have -[read this first](../Binary Search Tree/). +[read this first](../Binary%20Search%20Tree/). > Note: we do allow duplicate nodes in this implementation of a threaded binary > tree. We break ties by defaulting insertion to the right. @@ -267,9 +267,9 @@ something simple, like removing **7**, which has no children: Before we can just throw **7** away, we have to perform some clean-up. In this case, because **7** is a `right` child and has no children itself, we can simply set the `rightThread` of **7**'s `parent`(**5**) to **7**'s (now -outdated) `rightThread`. Then we can just set **7**'s `parent`, `left`, +outdated) `rightThread`. Then we can just set **7**'s `parent`, `left`, `right`, `leftThread`, and `rightThread` to `nil`, effectively removing it from -the tree. +the tree. We also set the parent's `rightChild` to `nil`, which completes the deletion of this right child. Let's try something a little harder. Say we remove **5** from the tree: @@ -339,7 +339,7 @@ such as `searching()` for a node in the tree, finding the `depth()` or `height()` of a node, etc. You can check [the implementation](ThreadedBinaryTree.swift) for the full technical details. Many of these methods are inherent to binary search trees as well, so you can -find [further documentation here](../Binary Search Tree/). +find [further documentation here](../Binary%20Search%20Tree/). ## See also diff --git a/Threaded Binary Tree/ThreadedBinaryTree.playground/Contents.swift b/Threaded Binary Tree/ThreadedBinaryTree.playground/Contents.swift index e4b527664..ece1966e0 100644 --- a/Threaded Binary Tree/ThreadedBinaryTree.playground/Contents.swift +++ b/Threaded Binary Tree/ThreadedBinaryTree.playground/Contents.swift @@ -1,6 +1,5 @@ //: Playground - noun: a place where people can play - // Simple little debug function to make testing output pretty func check(_ tree: ThreadedBinaryTree?) { if let tree = tree { @@ -28,12 +27,10 @@ func check(_ tree: ThreadedBinaryTree?) { } } - print("\nTree with Single Node") let emptyTree = ThreadedBinaryTree(value: 1) check(emptyTree) - print("\nFull Balanced Binary Tree with 7 Nodes") let fullTree = ThreadedBinaryTree(value: 4) fullTree.insert(2) @@ -44,7 +41,6 @@ fullTree.insert(3) fullTree.insert(7) check(fullTree) - print("\n\nBase Binary Tree with 5 Nodes") let tree = ThreadedBinaryTree(array: [9, 5, 12, 2, 7]) check(tree) @@ -81,5 +77,4 @@ print("\nRemove 12") newRoot?.remove(12) check(newRoot) - print("\n\nDone with Tests!\n") diff --git a/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift b/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift index 6f438c0cf..5c7671688 100644 --- a/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift +++ b/Threaded Binary Tree/ThreadedBinaryTree.playground/Sources/ThreadedBinaryTree.swift @@ -82,7 +82,7 @@ extension ThreadedBinaryTree { public func insert(_ value: T) { insert(value, parent: self) } - + fileprivate func insert(_ value: T, parent: ThreadedBinaryTree) { if value < self.value { if let left = left { @@ -123,13 +123,13 @@ extension ThreadedBinaryTree { @discardableResult public func remove(_ value: T) -> ThreadedBinaryTree? { return search(value)?.remove() } - + /* Deletes "this" node from the tree. */ public func remove() -> ThreadedBinaryTree? { let replacement: ThreadedBinaryTree? - + if let left = left { if let right = right { replacement = removeNodeWithTwoChildren(left, right) @@ -155,33 +155,33 @@ extension ThreadedBinaryTree { parent?.rightThread = rightThread } } - + reconnectParentToNode(replacement) - + // The current node is no longer part of the tree, so clean it up. parent = nil left = nil right = nil leftThread = nil rightThread = nil - + return replacement } - + private func removeNodeWithTwoChildren(_ left: ThreadedBinaryTree, _ right: ThreadedBinaryTree) -> ThreadedBinaryTree { // This node has two children. It must be replaced by the smallest // child that is larger than this node's value, which is the leftmost // descendent of the right child. let successor = right.minimum() - + // If this in-order successor has a right child of its own (it cannot // have a left child by definition), then that must take its place. _ = successor.remove() - + // Connect our left child with the new node. successor.left = left left.parent = successor - + // Connect our right child with the new node. If the right child does // not have any left children of its own, then the in-order successor // *is* the right child. @@ -191,11 +191,11 @@ extension ThreadedBinaryTree { } else { successor.right = nil } - + // And finally, connect the successor node to our parent. return successor } - + private func reconnectParentToNode(_ node: ThreadedBinaryTree?) { if let parent = parent { if isLeftChild { @@ -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 { @@ -228,7 +228,7 @@ extension ThreadedBinaryTree { } return nil } - + /* // Recursive version of search // Educational but undesirable due to the overhead cost of recursion @@ -242,33 +242,33 @@ extension ThreadedBinaryTree { } } */ - + public func contains(value: T) -> Bool { return search(value) != nil } - + /* Returns the leftmost descendent. O(h) time. */ public func minimum() -> ThreadedBinaryTree { var node = self - while case let next? = node.left { + while let next = node.left { node = next } return node } - + /* Returns the rightmost descendent. O(h) time. */ public func maximum() -> ThreadedBinaryTree { var node = self - while case let next? = node.right { + while let next = node.right { node = next } return node } - + /* Calculates the depth of this node, i.e. the distance to the root. Takes O(h) time. @@ -276,13 +276,13 @@ 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 } 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). @@ -294,7 +294,7 @@ extension ThreadedBinaryTree { return 1 + max(left?.height() ?? 0, right?.height() ?? 0) } } - + /* Finds the node whose value precedes our value in sorted order. */ @@ -305,7 +305,7 @@ extension ThreadedBinaryTree { return leftThread } } - + /* Finds the node whose value succeeds our value in sorted order. */ @@ -333,7 +333,7 @@ extension ThreadedBinaryTree { } } } - + public func traverseInOrderBackward(_ visit: (T) -> Void) { var n: ThreadedBinaryTree n = maximum() @@ -346,19 +346,19 @@ extension ThreadedBinaryTree { } } } - + public func traversePreOrder(_ visit: (T) -> Void) { visit(value) left?.traversePreOrder(visit) right?.traversePreOrder(visit) } - + public func traversePostOrder(_ visit: (T) -> Void) { left?.traversePostOrder(visit) right?.traversePostOrder(visit) visit(value) } - + /* Performs an in-order traversal and collects the results in an array. */ @@ -390,7 +390,7 @@ extension ThreadedBinaryTree { let rightBST = right?.isBST(minValue: value, maxValue: maxValue) ?? true return leftBST && rightBST } - + /* Is this binary tree properly threaded? Either left or leftThread (but not both) must be nil (likewise for right). @@ -452,7 +452,7 @@ extension ThreadedBinaryTree: CustomDebugStringConvertible { } return s } - + public func toArray() -> [T] { return map { $0 } } 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/Graph.swift b/Topological Sort/Graph.swift index 1e1be6919..5032ba1d1 100644 --- a/Topological Sort/Graph.swift +++ b/Topological Sort/Graph.swift @@ -4,7 +4,7 @@ public class Graph: CustomStringConvertible { private(set) public var adjacencyLists: [Node : [Node]] public init() { - adjacencyLists = [Node : [Node]]() + adjacencyLists = [Node: [Node]]() } public func addNode(_ value: Node) -> Node { @@ -35,7 +35,7 @@ extension Graph { typealias InDegree = Int func calculateInDegreeOfNodes() -> [Node : InDegree] { - var inDegrees = [Node : InDegree]() + var inDegrees = [Node: InDegree]() for (node, _) in adjacencyLists { inDegrees[node] = 0 diff --git a/Topological Sort/README.markdown b/Topological Sort/README.markdown index 4429a0754..5659dc3fd 100644 --- a/Topological Sort/README.markdown +++ b/Topological Sort/README.markdown @@ -22,7 +22,7 @@ The following is not a valid topological sort for this graph, since **X** and ** Let's consider that you want to learn all the algorithms and data structures from the Swift Algorithm Club. This might seem daunting at first but we can use topological sort to get things organized. -Since you're learning about topological sort, let's take this topic as an example. What else do you need to learn first before you can fully understand topological sort? Well, topological sort uses [depth-first search](../Depth-First Search/) as well as a [stack](../Stack/). But before you can learn about the depth-first search algorithm, you need to know what a [graph](../Graph/) is, and it helps to know what a [tree](../Tree/) is. In turn, graphs and trees use the idea of linking objects together, so you may need to read up on that first. And so on... +Since you're learning about topological sort, let's take this topic as an example. What else do you need to learn first before you can fully understand topological sort? Well, topological sort uses [depth-first search](../Depth-First%20Search/) as well as a [stack](../Stack/). But before you can learn about the depth-first search algorithm, you need to know what a [graph](../Graph/) is, and it helps to know what a [tree](../Tree/) is. In turn, graphs and trees use the idea of linking objects together, so you may need to read up on that first. And so on... If we were to represent these objectives in the form of a graph it would look as follows: diff --git a/Topological Sort/Topological Sort.playground/Contents.swift b/Topological Sort/Topological Sort.playground/Contents.swift index d0ebe550d..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() @@ -23,8 +28,6 @@ graph.addEdge(fromNode: node11, toNode: node9) graph.addEdge(fromNode: node11, toNode: node10) graph.addEdge(fromNode: node8, toNode: node9) - - // using depth-first search graph.topologicalSort() diff --git a/Topological Sort/Topological Sort.playground/Sources/Graph.swift b/Topological Sort/Topological Sort.playground/Sources/Graph.swift index 1e1be6919..5032ba1d1 100644 --- a/Topological Sort/Topological Sort.playground/Sources/Graph.swift +++ b/Topological Sort/Topological Sort.playground/Sources/Graph.swift @@ -4,7 +4,7 @@ public class Graph: CustomStringConvertible { private(set) public var adjacencyLists: [Node : [Node]] public init() { - adjacencyLists = [Node : [Node]]() + adjacencyLists = [Node: [Node]]() } public func addNode(_ value: Node) -> Node { @@ -35,7 +35,7 @@ extension Graph { typealias InDegree = Int func calculateInDegreeOfNodes() -> [Node : InDegree] { - var inDegrees = [Node : InDegree]() + var inDegrees = [Node: InDegree]() for (node, _) in adjacencyLists { inDegrees[node] = 0 diff --git a/Topological Sort/Topological Sort.playground/Sources/TopologicalSort1.swift b/Topological Sort/Topological Sort.playground/Sources/TopologicalSort1.swift index 0f9566f6a..2ad9b51a0 100644 --- a/Topological Sort/Topological Sort.playground/Sources/TopologicalSort1.swift +++ b/Topological Sort/Topological Sort.playground/Sources/TopologicalSort1.swift @@ -19,11 +19,11 @@ extension Graph { let startNodes = calculateInDegreeOfNodes().filter({ _, indegree in return indegree == 0 - }).map({ node, indegree in + }).map({ node, _ in return node }) - var visited = [Node : Bool]() + var visited = [Node: Bool]() for (node, _) in adjacencyLists { visited[node] = false } diff --git a/Topological Sort/Topological Sort.playground/Sources/TopologicalSort2.swift b/Topological Sort/Topological Sort.playground/Sources/TopologicalSort2.swift index f4c1cdd6f..589801d33 100644 --- a/Topological Sort/Topological Sort.playground/Sources/TopologicalSort2.swift +++ b/Topological Sort/Topological Sort.playground/Sources/TopologicalSort2.swift @@ -8,7 +8,7 @@ extension Graph { // topologically sorted list. var leaders = nodes.filter({ _, indegree in return indegree == 0 - }).map({ node, indegree in + }).map({ node, _ in return node }) 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 0f9566f6a..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 } @@ -19,11 +19,11 @@ extension Graph { let startNodes = calculateInDegreeOfNodes().filter({ _, indegree in return indegree == 0 - }).map({ node, indegree in + }).map({ node, _ in return node }) - var visited = [Node : Bool]() + var visited = [Node: Bool]() for (node, _) in adjacencyLists { visited[node] = false } diff --git a/Topological Sort/TopologicalSort2.swift b/Topological Sort/TopologicalSort2.swift index f4c1cdd6f..589801d33 100644 --- a/Topological Sort/TopologicalSort2.swift +++ b/Topological Sort/TopologicalSort2.swift @@ -8,7 +8,7 @@ extension Graph { // topologically sorted list. var leaders = nodes.filter({ _, indegree in return indegree == 0 - }).map({ node, indegree in + }).map({ node, _ in return node }) diff --git a/Treap/Treap.swift b/Treap/Treap.swift index 478f248df..d6cee2e70 100644 --- a/Treap/Treap.swift +++ b/Treap/Treap.swift @@ -27,11 +27,11 @@ import Foundation public indirect enum Treap { case empty case node(key: Key, val: Element, p: Int, left: Treap, right: Treap) - + public init() { self = .empty } - + internal func get(_ key: Key) -> Element? { switch self { case .empty: @@ -46,7 +46,7 @@ public indirect enum Treap { return nil } } - + public func contains(_ key: Key) -> Bool { switch self { case .empty: @@ -61,36 +61,31 @@ public indirect enum Treap { return false } } - + public var depth: Int { - get { - switch self { - case .empty: - return 0 - case let .node(_, _, _, left, .empty): - return 1 + left.depth - case let .node(_, _, _, .empty, right): - return 1 + right.depth - case let .node(_, _, _, left, right): - let leftDepth = left.depth - let rightDepth = right.depth - return 1 + leftDepth > rightDepth ? leftDepth : rightDepth - } - + switch self { + case .empty: + return 0 + case let .node(_, _, _, left, .empty): + return 1 + left.depth + case let .node(_, _, _, .empty, right): + return 1 + right.depth + case let .node(_, _, _, left, right): + let leftDepth = left.depth + let rightDepth = right.depth + return 1 + leftDepth > rightDepth ? leftDepth : rightDepth } } - + public var count: Int { - get { - return Treap.countHelper(self) - } + return Treap.countHelper(self) } - + fileprivate static func countHelper(_ treap: Treap) -> Int { if case let .node(_, _, _, left, right) = treap { return countHelper(left) + 1 + countHelper(right) } - + return 0 } } @@ -126,7 +121,7 @@ public extension Treap { return .empty } } - + fileprivate func insertAndBalance(_ nodeKey: Key, _ nodeVal: Element, _ nodeP: Int, _ left: Treap, _ right: Treap, _ key: Key, _ val: Element, _ p: Int) -> Treap { let newChild: Treap @@ -146,14 +141,14 @@ public extension Treap { newNode = .empty return newNode } - - if case let .node(_, _, newChildP, _, _) = newChild , newChildP < nodeP { + + if case let .node(_, _, newChildP, _, _) = newChild, newChildP < nodeP { return rotate(newNode) } else { return newNode } } - + internal func delete(key: Key) throws -> Treap { switch self { case .empty: 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 f349e6ab8..a5d156ec9 100644 --- a/Treap/Treap/TreapTests/TreapTests.swift +++ b/Treap/Treap/TreapTests/TreapTests.swift @@ -27,17 +27,24 @@ THE SOFTWARE.*/ import XCTest class TreapTests: XCTestCase { - + override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. 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") @@ -51,7 +58,7 @@ class TreapTests: XCTestCase { XCTAssert(!treap.contains(5)) XCTAssert(treap.contains(7)) } - + func testFairlyBalanced() { var treap = Treap.empty for i in 0..<1000 { @@ -60,7 +67,7 @@ class TreapTests: XCTestCase { let depth = treap.depth XCTAssert(depth < 30, "treap.depth was \(depth)") } - + func testFairlyBalancedCollection() { var treap = Treap() for i in 0..<1000 { @@ -69,5 +76,5 @@ class TreapTests: XCTestCase { let depth = treap.depth XCTAssert(depth > 0 && depth < 30) } - + } diff --git a/Treap/TreapCollectionType.swift b/Treap/TreapCollectionType.swift index de40464d1..eeee065fe 100644 --- a/Treap/TreapCollectionType.swift +++ b/Treap/TreapCollectionType.swift @@ -25,52 +25,52 @@ THE SOFTWARE.*/ import Foundation extension Treap: MutableCollection { - + public typealias Index = TreapIndex - + public subscript(index: TreapIndex) -> Element { get { guard let result = self.get(index.keys[index.keyIndex]) else { fatalError("Invalid index!") } - + return result } - + mutating set { let key = index.keys[index.keyIndex] self = self.set(key: key, val: newValue) } } - + public subscript(key: Key) -> Element? { get { return self.get(key) } - + mutating set { guard let value = newValue else { - let _ = try? self.delete(key: key) + _ = try? self.delete(key: key) return } - + self = self.set(key: key, val: value) } } - + public var startIndex: TreapIndex { return TreapIndex(keys: keys, keyIndex: 0) } - + public var endIndex: TreapIndex { let keys = self.keys return TreapIndex(keys: keys, keyIndex: keys.count) } - + public func index(after i: TreapIndex) -> TreapIndex { return i.successor() } - + fileprivate var keys: [Key] { var results: [Key] = [] if case let .node(key, _, _, left, right) = self { @@ -78,28 +78,28 @@ extension Treap: MutableCollection { results.append(key) results.append(contentsOf: right.keys) } - + return results } } public struct TreapIndex: Comparable { - - public static func <(lhs: TreapIndex, rhs: TreapIndex) -> Bool { + + public static func < (lhs: TreapIndex, rhs: TreapIndex) -> Bool { return lhs.keyIndex < rhs.keyIndex } - + fileprivate let keys: [Key] fileprivate let keyIndex: Int - + public func successor() -> TreapIndex { return TreapIndex(keys: keys, keyIndex: keyIndex + 1) } - + public func predecessor() -> TreapIndex { return TreapIndex(keys: keys, keyIndex: keyIndex - 1) } - + fileprivate init(keys: [Key] = [], keyIndex: Int = 0) { self.keys = keys self.keyIndex = keyIndex diff --git a/Treap/TreapMergeSplit.swift b/Treap/TreapMergeSplit.swift index 660bd4932..62d18d16f 100644 --- a/Treap/TreapMergeSplit.swift +++ b/Treap/TreapMergeSplit.swift @@ -37,7 +37,7 @@ public extension Treap { } else { fatalError("No values in treap") } - + switch self { case .node: if case let .node(_, _, _, left, right) = current.set(key: key, val: val, p: -1) { @@ -49,7 +49,7 @@ public extension Treap { return (left: .empty, right: .empty) } } - + internal var leastKey: Key? { switch self { case .empty: @@ -60,7 +60,7 @@ public extension Treap { return left.leastKey } } - + internal var mostKey: Key? { switch self { case .empty: @@ -79,7 +79,7 @@ internal func merge(_ left: Treap, right return right case (_, .empty): return left - + case let (.node(leftKey, leftVal, leftP, leftLeft, leftRight), .node(rightKey, rightVal, rightP, rightLeft, rightRight)): if leftP < rightP { return .node(key: leftKey, val: leftVal, p: leftP, left: leftLeft, right: merge(leftRight, right: right)) @@ -94,20 +94,18 @@ internal func merge(_ left: Treap, right extension Treap: CustomStringConvertible { public var description: String { - get { - return Treap.descHelper(self, indent: 0) - } + return Treap.descHelper(self, indent: 0) } - + fileprivate static func descHelper(_ treap: Treap, indent: Int) -> String { if case let .node(key, value, priority, left, right) = treap { var result = "" let tabs = String(repeating: "\t", count: indent) - + result += descHelper(left, indent: indent + 1) result += "\n" + tabs + "\(key), \(value), \(priority)\n" result += descHelper(right, indent: indent + 1) - + return result } else { return "" diff --git a/Tree/README.markdown b/Tree/README.markdown index 529a68cf1..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) @@ -16,7 +19,7 @@ The pointers in a tree do not form cycles. This is not a tree: ![Not a tree](Images/Cycles.png) -Such a structure is called a [graph](../Graph/). A tree is really a very simple form of a graph. (In a similar vein, a [linked list](../Linked List/) is a simple version of a tree. Think about it!) +Such a structure is called a [graph](../Graph/). A tree is really a very simple form of a graph. (In a similar vein, a [linked list](../Linked%20List/) is a simple version of a tree. Think about it!) This article talks about a general-purpose tree. That's a tree without any kind of restrictions on how many children each node may have, or on the order of the nodes in the tree. @@ -119,7 +122,7 @@ We often use the following definitions when talking about trees: - **Depth of a node.** The number of links between this node and the root node. For example, the depth of `tea` is 2 because it takes two jumps to reach the root. (The root itself has depth 0.) -There are many different ways to construct trees. For example, sometimes you don't need to have a `parent` property at all. Or maybe you only need to give each node a maximum of two children -- such a tree is called a [binary tree](../Binary Tree/). A very common type of tree is the [binary search tree](../Binary Search Tree/) (or BST), a stricter version of a binary tree where the nodes are ordered in a particular way to speed up searches. +There are many different ways to construct trees. For example, sometimes you don't need to have a `parent` property at all. Or maybe you only need to give each node a maximum of two children -- such a tree is called a [binary tree](../Binary%20Tree/). A very common type of tree is the [binary search tree](../Binary%20Search%20Tree/) (or BST), a stricter version of a binary tree where the nodes are ordered in a particular way to speed up searches. The general purpose tree I've shown here is great for describing hierarchical data, but it really depends on your application what kind of extra functionality it needs to have. @@ -153,7 +156,7 @@ tree.search("bubbly") // nil It's also possible to describe a tree using nothing more than an array. The indices in the array then create the links between the different nodes. For example, if we have: - 0 = beverage 5 = cocoa 9 = green + 0 = beverage 5 = cocoa 9 = green 1 = hot 6 = soda 10 = chai 2 = cold 7 = milk 11 = ginger ale 3 = tea 8 = black 12 = bitter lemon diff --git a/Tree/Tree.playground/Contents.swift b/Tree/Tree.playground/Contents.swift index 77e8fd22e..1fad157a0 100644 --- a/Tree/Tree.playground/Contents.swift +++ b/Tree/Tree.playground/Contents.swift @@ -1,34 +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 7caa927a7..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 4da9dc345..48dc24673 100644 --- a/Trie/Trie/Trie/AppDelegate.swift +++ b/Trie/Trie/Trie/AppDelegate.swift @@ -11,16 +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 0e11a9c61..3d5031cf3 100644 --- a/Trie/Trie/Trie/Trie.swift +++ b/Trie/Trie/Trie/Trie.swift @@ -8,7 +8,6 @@ import Foundation - /// A node in the trie class TrieNode { var value: T? @@ -18,8 +17,7 @@ class TrieNode { var isLeaf: Bool { return children.count == 0 } - - + /// Initializes a node. /// /// - Parameters: @@ -29,7 +27,7 @@ class TrieNode { self.value = value self.parentNode = parentNode } - + /// Adds a child node to self. If the child is already present, /// do nothing. /// @@ -45,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 + 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() - } + /// 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. /// @@ -102,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 { @@ -117,42 +114,59 @@ extension Trie { wordCount += 1 currentNode.isTerminating = true } - + /// 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 terminating node of a word. The - /// search will fail if the word is not present. + + /// Attempts to walk to the last node of a word. The + /// search will fail if the word is not present. Doesn't + /// check if the node is terminating /// /// - Parameter word: the word in question /// - Returns: the node where the search ended, nil if the /// search failed. - private func findTerminalNodeOf(word: String) -> Node? { + private func findLastNodeOf(word: String) -> Node? { var currentNode = root - for character in word.lowercased().characters { + for character in word.lowercased() { guard let childNode = currentNode.children[character] else { return nil } currentNode = childNode } - return currentNode.isTerminating ? currentNode : nil + return currentNode } - + + /// Attempts to walk to the terminating node of a word. The + /// search will fail if the word is not present. + /// + /// - Parameter word: the word in question + /// - Returns: the node where the search ended, nil if the + /// search failed. + private func findTerminalNodeOf(word: String) -> Node? { + if let lastNode = findLastNodeOf(word: word) { + return lastNode.isTerminating ? lastNode : nil + } + return nil + } + /// Deletes a word from the trie by starting with the last letter /// and moving back, deleting nodes until either a non-leaf or a /// terminating node is found. @@ -171,7 +185,7 @@ extension Trie { } } } - + /// Removes a word from the trie. If the word is not present or /// it is empty, just ignore it. If the last node is a leaf, /// delete that node and higher nodes that are leaves until a @@ -194,14 +208,14 @@ extension Trie { } wordCount -= 1 } - + /// Returns an array of words in a subtrie of the trie /// /// - Parameters: /// - rootNode: the root node of the subtrie /// - partialWord: the letters collected by traversing to this node /// - Returns: the words in the subtrie - func wordsInSubtrie(rootNode: Node, partialWord: String) -> [String] { + fileprivate func wordsInSubtrie(rootNode: Node, partialWord: String) -> [String] { var subtrieWords = [String]() var previousLetters = partialWord if let value = rootNode.value { @@ -216,4 +230,25 @@ extension Trie { } return subtrieWords } + + /// Returns an array of words in a subtrie of the trie that start + /// with given prefix + /// + /// - Parameters: + /// - 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 + } + } + return words + } } diff --git a/Trie/Trie/Trie/ViewController.swift b/Trie/Trie/Trie/ViewController.swift index b2e9d9105..8ce34f6c7 100644 --- a/Trie/Trie/Trie/ViewController.swift +++ b/Trie/Trie/Trie/ViewController.swift @@ -10,18 +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 f8e934f0d..59da2cd9b 100644 --- a/Trie/Trie/TrieTests/TrieTests.swift +++ b/Trie/Trie/TrieTests/TrieTests.swift @@ -10,162 +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() + 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) } - - /// 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) + 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) } - - /// 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) + } + + /// 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 that a newly created trie has zero words. - func testCreate() { - let trie = Trie() - XCTAssertEqual(trie.count, 0) + 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 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 performance of the contains method. + func testContainsPerformance() { + self.measure { + for word in self.wordArray! { + XCTAssertTrue(self.trie.contains(word: word)) + } } + } - /// 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 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 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) - } - } - XCTAssertGreaterThan(trie.count, 0) - XCTAssertEqual(trie.count, wordArray?.count) + self.measure { + self.insertWordsIntoTrie() + for word in self.wordArray! { + self.trie.remove(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) - } - } + 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 } - - /// 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 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)) - } - } - - /// 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(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 f3da2a67f..380bf3092 100644 --- a/Trie/Trie/TrieUITests/TrieUITests.swift +++ b/Trie/Trie/TrieUITests/TrieUITests.swift @@ -9,28 +9,28 @@ import XCTest class TrieUITests: XCTestCase { - - override func setUp() { - super.setUp() - - // 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’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() - } - - func testExample() { - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - + + override func setUp() { + super.setUp() + + // 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’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() + } + + 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/Under Construction.markdown b/Under Construction.markdown index 0c59d90c1..be53e01b0 100644 --- a/Under Construction.markdown +++ b/Under Construction.markdown @@ -5,27 +5,27 @@ Here you'll find algorithms that are currently under construction. Suggestions a ### Sorting Special-purpose sorts: - - [Radix Sort](Radix Sort/) + - [Radix Sort](Radix%20Sort/) ### Special-purpose sorts: -- [Bucket Sort](Bucket Sort/) +- [Bucket Sort](Bucket%20Sort/) ### Queues -- [Bounded Priority Queue](Bounded Priority Queue). A queue that is bounded to have a limited number of elements. +- [Bounded Priority Queue](Bounded%20Priority%20Queue). A queue that is bounded to have a limited number of elements. ### Trees -- [AVL Tree](AVL Tree/). A binary search tree that balances itself using rotations. -- [Red-Black Tree](Red-Black Tree/) -- [Threaded Binary Tree](Threaded Binary Tree/) -- [Ternary Search Tree](Ternary Search Tree/) +- [AVL Tree](AVL%20Tree/). A binary search tree that balances itself using rotations. +- [Red-Black Tree](Red-Black%20Tree/) +- [Threaded Binary Tree](Threaded%20Binary%20Tree/) +- [Ternary Search Tree](Ternary%20Search%20Tree/) - [Trie](Trie/) -- [Radix Tree](Radix Tree/) +- [Radix Tree](Radix%20Tree/) ### Miscellaneous -- [Minimum Edit Distance](Minimum Edit Distance/). Measure the similarity of two strings by counting the number of operations required to transform one string into the other. +- [Minimum Edit Distance](Minimum%20Edit%20Distance/). Measure the similarity of two strings by counting the number of operations required to transform one string into the other. - [Treap](Treap/) -- [Set Cover (Unweighted)](Set Cover (Unweighted)/) +- [Set Cover (Unweighted)](Set%20Cover%20(Unweighted)/) diff --git a/Union-Find/README.markdown b/Union-Find/README.markdown index 5102f6b89..a268f1d70 100644 --- a/Union-Find/README.markdown +++ b/Union-Find/README.markdown @@ -9,7 +9,7 @@ What do we mean by this? For example, the Union-Find data structure could be kee [ g, d, c ] [ i, j ] -These sets are disjoint because they have no members in common. +These sets are **disjoint** because they have no members in common. Union-Find supports three basic operations: @@ -23,7 +23,8 @@ The most common application of this data structure is keeping track of the conne ## Implementation -Union-Find can be implemented in many ways but we'll look at the most efficient. +Union-Find can be implemented in many ways but we'll look at an efficient and easy to understand implementation: Weighted Quick Union. +> __PS: Multiple implementations of Union-Find has been included in playground.__ ```swift public struct UnionFind { @@ -119,7 +120,7 @@ Here's illustration of what I mean. Let's say the tree looks like this: ![BeforeFind](Images/BeforeFind.png) -We call `setOf(4)`. To find the root node we have to first go to node `2` and then to node `7`. (The indexes of the elements are marked in red.) +We call `setOf(4)`. To find the root node we have to first go to node `2` and then to node `7`. (The indices of the elements are marked in red.) During the call to `setOf(4)`, the tree is reorganized to look like this: @@ -141,24 +142,24 @@ public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool Since this calls `setOf()` it also optimizes the tree. -## Union +## Union (Weighted) The final operation is **Union**, which combines two sets into one larger set. ```swift -public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { - if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { // 1 - if firstSet != secondSet { // 2 - if size[firstSet] < size[secondSet] { // 3 - parent[firstSet] = secondSet // 4 - size[secondSet] += size[firstSet] // 5 - } else { - parent[secondSet] = firstSet - size[firstSet] += size[secondSet] - } + public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { // 1 + if firstSet != secondSet { // 2 + if size[firstSet] < size[secondSet] { // 3 + parent[firstSet] = secondSet // 4 + size[secondSet] += size[firstSet] // 5 + } else { + parent[secondSet] = firstSet + size[firstSet] += size[secondSet] + } + } + } } - } -} ``` Here is how it works: @@ -167,7 +168,7 @@ Here is how it works: 2. Check that the sets are not equal because if they are it makes no sense to union them. -3. This is where the size optimization comes in. We want to keep the trees as shallow as possible so we always attach the smaller tree to the root of the larger tree. To determine which is the smaller tree we compare trees by their sizes. +3. This is where the size optimization comes in (Weighting). We want to keep the trees as shallow as possible so we always attach the smaller tree to the root of the larger tree. To determine which is the smaller tree we compare trees by their sizes. 4. Here we attach the smaller tree to the root of the larger tree. @@ -185,10 +186,40 @@ Note that, because we call `setOf()` at the start of the method, the larger tree Union with optimizations also takes almost **O(1)** time. +## Path Compression +```swift +private mutating func setByIndex(_ index: Int) -> Int { + if index != parent[index] { + // Updating parent index while looking up the index of parent. + parent[index] = setByIndex(parent[index]) + } + return parent[index] +} +``` +Path Compression helps keep trees very flat, thus find operation could take __ALMOST__ in __O(1)__ + +## Complexity Summary + +##### To process N objects +| Data Structure | Union | Find | +|---|---|---| +|Quick Find|N|1| +|Quick Union|Tree height|Tree height| +|Weighted Quick Union|lgN|lgN| +|Weighted Quick Union + Path Compression| very close, but not O(1)| very close, but not O(1) | + +##### To process M union commands on N objects +| Algorithm | Worst-case time| +|---|---| +|Quick Find| M N | +|Quick Union| M N | +|Weighted Quick Union| N + M lgN | +|Weighted Quick Union + Path Compression| (M + N) lgN | + ## See also See the playground for more examples of how to use this handy data structure. [Union-Find at Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) -*Written for Swift Algorithm Club by [Artur Antonov](https://github.com/goingreen)* +*Written for Swift Algorithm Club by [Artur Antonov](https://github.com/goingreen)*, *modified by [Yi Ding](https://github.com/antonio081014).* \ No newline at end of file diff --git a/Union-Find/UnionFind.playground/Contents.swift b/Union-Find/UnionFind.playground/Contents.swift index 09649b938..b1f7fb4e1 100644 --- a/Union-Find/UnionFind.playground/Contents.swift +++ b/Union-Find/UnionFind.playground/Contents.swift @@ -1,60 +1,6 @@ //: Playground - noun: a place where people can play -public struct UnionFind { - private var index = [T: Int]() - private var parent = [Int]() - private var size = [Int]() - - public mutating func addSetWith(_ element: T) { - index[element] = parent.count - parent.append(parent.count) - size.append(1) - } - - private mutating func setByIndex(_ index: Int) -> Int { - if parent[index] == index { - return index - } else { - parent[index] = setByIndex(parent[index]) - return parent[index] - } - } - - public mutating func setOf(_ element: T) -> Int? { - if let indexOfElement = index[element] { - return setByIndex(indexOfElement) - } else { - return nil - } - } - - public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { - if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { - if firstSet != secondSet { - if size[firstSet] < size[secondSet] { - parent[firstSet] = secondSet - size[secondSet] += size[firstSet] - } else { - parent[secondSet] = firstSet - size[firstSet] += size[secondSet] - } - } - } - } - - public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool { - if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { - return firstSet == secondSet - } else { - return false - } - } -} - - - - -var dsu = UnionFind() +var dsu = UnionFindQuickUnion() for i in 1...10 { dsu.addSetWith(i) @@ -76,19 +22,15 @@ print(dsu.inSameSet(4, and: 6)) print(dsu.inSameSet(6, and: 8)) print(dsu.inSameSet(8, and: 10)) - print(dsu.inSameSet(1, and: 3)) print(dsu.inSameSet(3, and: 5)) print(dsu.inSameSet(5, and: 7)) print(dsu.inSameSet(7, and: 9)) - print(dsu.inSameSet(7, and: 4)) print(dsu.inSameSet(3, and: 6)) - - -var dsuForStrings = UnionFind() +var dsuForStrings = UnionFindQuickUnion() let words = ["all", "border", "boy", "afternoon", "amazing", "awesome", "best"] dsuForStrings.addSetWith("a") diff --git a/Union-Find/UnionFind.playground/Sources/UnionFindQuickFind.swift b/Union-Find/UnionFind.playground/Sources/UnionFindQuickFind.swift new file mode 100644 index 000000000..2cd048de6 --- /dev/null +++ b/Union-Find/UnionFind.playground/Sources/UnionFindQuickFind.swift @@ -0,0 +1,50 @@ +import Foundation + +/// Quick-find algorithm may take ~MN steps +/// to process M union commands on N objects +public struct UnionFindQuickFind { + private var index = [T: Int]() + private var parent = [Int]() + private var size = [Int]() + + public init() {} + + public mutating func addSetWith(_ element: T) { + index[element] = parent.count + parent.append(parent.count) + size.append(1) + } + + private mutating func setByIndex(_ index: Int) -> Int { + return parent[index] + } + + public mutating func setOf(_ element: T) -> Int? { + if let indexOfElement = index[element] { + return setByIndex(indexOfElement) + } else { + return nil + } + } + + public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + if firstSet != secondSet { + for index in 0.. Bool { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + return firstSet == secondSet + } else { + return false + } + } +} diff --git a/Union-Find/UnionFind.playground/Sources/UnionFindQuickUnion.swift b/Union-Find/UnionFind.playground/Sources/UnionFindQuickUnion.swift new file mode 100644 index 000000000..2be8875eb --- /dev/null +++ b/Union-Find/UnionFind.playground/Sources/UnionFindQuickUnion.swift @@ -0,0 +1,51 @@ +import Foundation + +/// Quick-Union algorithm may take ~MN steps +/// to process M union commands on N objects +public struct UnionFindQuickUnion { + private var index = [T: Int]() + private var parent = [Int]() + private var size = [Int]() + + public init() {} + + public mutating func addSetWith(_ element: T) { + index[element] = parent.count + parent.append(parent.count) + size.append(1) + } + + private mutating func setByIndex(_ index: Int) -> Int { + if parent[index] == index { + return index + } else { + parent[index] = setByIndex(parent[index]) + return parent[index] + } + } + + public mutating func setOf(_ element: T) -> Int? { + if let indexOfElement = index[element] { + return setByIndex(indexOfElement) + } else { + return nil + } + } + + public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + if firstSet != secondSet { + parent[firstSet] = secondSet + size[secondSet] += size[firstSet] + } + } + } + + public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + return firstSet == secondSet + } else { + return false + } + } +} diff --git a/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickFind.swift b/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickFind.swift new file mode 100644 index 000000000..49a526fe3 --- /dev/null +++ b/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickFind.swift @@ -0,0 +1,59 @@ +import Foundation + +/// Quick-find algorithm may take ~MN steps +/// to process M union commands on N objects +public struct UnionFindWeightedQuickFind { + private var index = [T: Int]() + private var parent = [Int]() + private var size = [Int]() + + public init() {} + + public mutating func addSetWith(_ element: T) { + index[element] = parent.count + parent.append(parent.count) + size.append(1) + } + + private mutating func setByIndex(_ index: Int) -> Int { + return parent[index] + } + + public mutating func setOf(_ element: T) -> Int? { + if let indexOfElement = index[element] { + return setByIndex(indexOfElement) + } else { + return nil + } + } + + public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + if firstSet != secondSet { + if size[firstSet] < size[secondSet] { + for index in 0.. Bool { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + return firstSet == secondSet + } else { + return false + } + } +} diff --git a/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickUnion.swift b/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickUnion.swift new file mode 100644 index 000000000..70217422f --- /dev/null +++ b/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickUnion.swift @@ -0,0 +1,56 @@ +import Foundation + +public struct UnionFindWeightedQuickUnion { + private var index = [T: Int]() + private var parent = [Int]() + private var size = [Int]() + + public init() {} + + public mutating func addSetWith(_ element: T) { + index[element] = parent.count + parent.append(parent.count) + size.append(1) + } + + private mutating func setByIndex(_ index: Int) -> Int { + if parent[index] == index { + return index + } else { + parent[index] = setByIndex(parent[index]) + return parent[index] + } + } + + public mutating func setOf(_ element: T) -> Int? { + if let indexOfElement = index[element] { + return setByIndex(indexOfElement) + } else { + return nil + } + } + + /// Weighted, by comparing set size. + /// Merge small set into the large one. + public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + if firstSet != secondSet { + if size[firstSet] < size[secondSet] { + parent[firstSet] = secondSet + size[secondSet] += size[firstSet] + } else { + parent[secondSet] = firstSet + size[firstSet] += size[secondSet] + } + } + } + } + + public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + return firstSet == secondSet + } else { + return false + } + } +} diff --git a/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickUnionPathCompression.swift b/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickUnionPathCompression.swift new file mode 100644 index 000000000..1c0bacd82 --- /dev/null +++ b/Union-Find/UnionFind.playground/Sources/UnionFindWeightedQuickUnionPathCompression.swift @@ -0,0 +1,53 @@ +import Foundation + +public struct UnionFindWeightedQuickUnionPathCompression { + private var index = [T: Int]() + private var parent = [Int]() + private var size = [Int]() + + public init() {} + + public mutating func addSetWith(_ element: T) { + index[element] = parent.count + parent.append(parent.count) + size.append(1) + } + + /// Path Compression. + private mutating func setByIndex(_ index: Int) -> Int { + if index != parent[index] { + parent[index] = setByIndex(parent[index]) + } + return parent[index] + } + + public mutating func setOf(_ element: T) -> Int? { + if let indexOfElement = index[element] { + return setByIndex(indexOfElement) + } else { + return nil + } + } + + public mutating func unionSetsContaining(_ firstElement: T, and secondElement: T) { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + if firstSet != secondSet { + if size[firstSet] < size[secondSet] { + parent[firstSet] = secondSet + size[secondSet] += size[firstSet] + } else { + parent[secondSet] = firstSet + size[firstSet] += size[secondSet] + } + } + } + } + + public mutating func inSameSet(_ firstElement: T, and secondElement: T) -> Bool { + if let firstSet = setOf(firstElement), let secondSet = setOf(secondElement) { + return firstSet == secondSet + } else { + return false + } + } +} 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/Union-Find/UnionFind.playground/timeline.xctimeline b/Union-Find/UnionFind.playground/timeline.xctimeline deleted file mode 100644 index bf468afec..000000000 --- a/Union-Find/UnionFind.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - 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 9bef69687..65acaabd1 100644 --- a/Z-Algorithm/ZAlgorithm.swift +++ b/Z-Algorithm/ZAlgorithm.swift @@ -9,34 +9,33 @@ import Foundation func ZetaAlgorithm(ptrn: String) -> [Int]? { - - let pattern = Array(ptrn.characters) + let pattern = Array(ptrn) let patternLength = pattern.count - + guard patternLength > 0 else { return nil } - + var zeta = [Int](repeating: 0, count: patternLength) - + var left = 0 var right = 0 var k_1 = 0 var betaLength = 0 var textIndex = 0 var patternIndex = 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 @@ -44,18 +43,18 @@ func ZetaAlgorithm(ptrn: String) -> [Int]? { } 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 diff --git a/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift b/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift index 395122af0..56aa92170 100644 --- a/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift +++ b/Z-Algorithm/ZetaAlgorithm.playground/Contents.swift @@ -1,35 +1,33 @@ //: 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 { return nil } - + var zeta = [Int](repeating: 0, count: patternLength) - + var left = 0 var right = 0 var k_1 = 0 var betaLength = 0 var textIndex = 0 var patternIndex = 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 @@ -37,18 +35,18 @@ func ZetaAlgorithm(ptrn: String) -> [Int]? { } 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 @@ -58,30 +56,29 @@ func ZetaAlgorithm(ptrn: String) -> [Int]? { return zeta } - 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 { return nil } - + var indexes: [Int] = [] - + /* Scan the zeta array to find matched patterns */ for i in 0 ..< zeta!.count { if zeta![i] == patternLength { indexes.append(i - patternLength - 1) } } - + guard !indexes.isEmpty else { return nil } - + return indexes } } diff --git a/Z-Algorithm/ZetaAlgorithm.swift b/Z-Algorithm/ZetaAlgorithm.swift index c35703272..f2c453364 100644 --- a/Z-Algorithm/ZetaAlgorithm.swift +++ b/Z-Algorithm/ZetaAlgorithm.swift @@ -9,28 +9,28 @@ 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 { return nil } - + var indexes: [Int] = [Int]() - + /* Scan the zeta array to find matched patterns */ for i in 0 ..< zeta!.count { if zeta![i] == patternLength { indexes.append(i - patternLength - 1) } } - + guard !indexes.isEmpty else { return nil } - + return indexes } } 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 b2ffce836..000000000 --- a/swift-algorithm-club.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,2189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -