|
1 | 1 | # Ordered Set
|
| 2 | +An Ordered Set is a collection of unique items in sorted order. Items are usually sorted from least to greatest. The Ordered Set data type is a representation of a [Set in Mathematics](https://en.wikipedia.org/wiki/Set_(mathematics)). It's important to keep in mind that two items can have the same *value* but still may not be equal. |
| 3 | +For example, we could define "a" and "z" to have the same value (their lengths), but clearly "a" != "z". |
| 4 | + |
| 5 | +### Examples of Ordered Sets |
| 6 | +``` |
| 7 | +[1, 2, 3, 6, 8, 10, 1000] |
| 8 | +Where each item (Integers) has it's normal definition of value and equality |
| 9 | +``` |
| 10 | +``` |
| 11 | +["a", "is", "set", "this"] |
| 12 | +Where each item (String) has it's value equal to it's length |
| 13 | +``` |
| 14 | + |
| 15 | +### These are not Ordered Sets |
| 16 | +``` |
| 17 | +[1, 1, 2, 3, 5, 8] |
| 18 | +This Set violates the property of uniqueness |
| 19 | +``` |
| 20 | +``` |
| 21 | +[1, 11, 2, 3] |
| 22 | +This Set violates the sorted property |
| 23 | +``` |
| 24 | + |
| 25 | +## The Code |
| 26 | +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 our set sorted, we need to compare the individual elemants. Thus, any type must conform to the [Comparable Protocol](https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Comparable_Protocol/index.html). |
| 27 | + |
| 28 | +``` swift |
| 29 | +public struct OrderedSet<T: Comparable> { |
| 30 | + private var internalSet: [T]! = nil |
| 31 | + |
| 32 | + // returns size of Set |
| 33 | + public var count: Int { |
| 34 | + return internalSet!.count |
| 35 | + } |
| 36 | + |
| 37 | + public init(){ |
| 38 | + internalSet = [T]() // create the internal array on init |
| 39 | + } |
| 40 | + ... |
| 41 | +``` |
| 42 | + |
| 43 | +Lets take a look at the insert function first. The insert function first checks if the item already exists, and if so returns and does not insert the item. Otherwise, it will insert the item through straight forward iteration. It starts from the first item, and checks to see if this item is larger than the item we want to insert. Once we find such an item, we insert the given item into it's place, and shift the array over to the right by 1. |
| 44 | + |
| 45 | +``` swift |
| 46 | + // inserts an item |
| 47 | + public mutating func insert(item: T){ |
| 48 | + if exists(item) { |
| 49 | + return // don't add an item if it already exists |
| 50 | + } |
| 51 | + // if the set is initially empty, we need to simply append the item to internalSet |
| 52 | + if count == 0 { |
| 53 | + internalSet.append(item) |
| 54 | + return |
| 55 | + } |
| 56 | + |
| 57 | + for i in 0..<count { |
| 58 | + if internalSet[i] > item { |
| 59 | + internalSet.insert(item, atIndex: i) |
| 60 | + return |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + // if an item is larger than any item in the current set, append it to the back. |
| 65 | + internalSet.append(item) |
| 66 | + } |
| 67 | +``` |
| 68 | +The first part of the function checks if the item is already in the set.As we'll see later on, this 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. The second part iterates through the interal array so that it can find a spot for our given item. This is at worse **O(n)**. The insert function for arrays has an efficiency of **O(log(n))**, thus making the insert function for our Ordered Set **O(log(n) + k)**. |
| 69 | + |
| 70 | + |
| 71 | +Next we have the `remove` function. First check if the item exists. If not, then return and no nothing. If it does exist, remove it. |
| 72 | + |
| 73 | +``` swift |
| 74 | + // removes an item if it exists |
| 75 | + public mutating func remove(item: T) { |
| 76 | + if !exists(item) { |
| 77 | + return |
| 78 | + } |
| 79 | + |
| 80 | + internalSet.removeAtIndex(findIndex(item)) |
| 81 | + } |
| 82 | +``` |
| 83 | +Again, because of the `exists` function, the efficiency for remove is **O(log(n) + k)** |
| 84 | + |
| 85 | +The next function is the `findIndex` function which takes in an item of type `T` and returns the index of the item if it is in the set, otherwise returns -1. |
| 86 | + |
| 87 | +``` swift |
| 88 | +// returns the index of an item if it exists, otherwise returns -1. |
| 89 | + public func findIndex(item: T) -> Int { |
| 90 | + var leftBound = 0 |
| 91 | + var rightBound = count - 1 |
| 92 | + |
| 93 | + while leftBound <= rightBound { |
| 94 | + let mid = leftBound + ((rightBound - leftBound) / 2) |
| 95 | + |
| 96 | + if internalSet[mid] > item { |
| 97 | + rightBound = mid - 1 |
| 98 | + } else if internalSet[mid] < item { |
| 99 | + leftBound = mid + 1 |
| 100 | + } else { |
| 101 | + // check the mid value to see if it is the item we are looking for |
| 102 | + if internalSet[mid] == item { |
| 103 | + return mid |
| 104 | + } |
| 105 | + |
| 106 | + var j = mid |
| 107 | + |
| 108 | + // check right side of mid |
| 109 | + while j < internalSet.count - 1 && !(internalSet[j] < internalSet[j + 1]) { |
| 110 | + if internalSet[j + 1] == item { |
| 111 | + return j + 1 |
| 112 | + } |
| 113 | + |
| 114 | + j += 1 |
| 115 | + } |
| 116 | + |
| 117 | + j = mid |
| 118 | + |
| 119 | + // check left side of mid |
| 120 | + while j > 0 && !(internalSet[j] < internalSet[j - 1]) { |
| 121 | + if internalSet[j - 1] == item { |
| 122 | + return j - 1 |
| 123 | + } |
| 124 | + |
| 125 | + j -= 1 |
| 126 | + } |
| 127 | + return -1 |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + return -1 |
| 132 | + } |
| 133 | +``` |
| 134 | +Since our set is sorted, we can use a binary search to quickly search for the item. If you are not familiar with the concept of binary search, we have an article all about it [here](../Binary\ Search). |
| 135 | + |
| 136 | +Since a set can contain multiple items with the same *value*, it is important to check to see if we have the correct item. |
| 137 | + |
| 138 | +For example, consider this Ordered Set |
| 139 | +``` |
| 140 | +["a", "b", "c", "longer string", "even longer string"] |
| 141 | +Where the value of each String is equal to it's length. |
| 142 | +``` |
| 143 | +The call `findIndex("a")` with the traditional implementation of Binary Search would give us the value of 2, however we know that "a" is located at index 0. Thus, we need to check the items with the same *value* to the right and left of the mid value. |
| 144 | + |
| 145 | +The code to check the left and right side are similar so we will only look at the code that checks the left side. |
| 146 | +``` swift |
| 147 | + j = mid |
| 148 | + |
| 149 | + // check left side of mid |
| 150 | + while j > 0 && !(internalSet[j] < internalSet[j - 1]) { |
| 151 | + if internalSet[j - 1] == item { |
| 152 | + return j - 1 |
| 153 | + } |
| 154 | + |
| 155 | + j -= 1 |
| 156 | + } |
| 157 | + return -1 |
| 158 | +``` |
| 159 | +First, `j` starts at the mid value. Above, we've already checked to see that the item at index `j` is not equal to the item we are looking for. Then, we keep looping until we either reach the end of the array, or hit an element which has a lower value than the current item at index `j`. If the item at value `j - 1` is equal to the one we are looking for, we return that index, otherwise we keep decreasing `j`. Once the loop terminates, we were unable to find the item and so we return -1. |
| 160 | + |
| 161 | +The combined runtime for this function is **O(log(n) + k)** where `n` is the length of the set, and `k` is the number of |
| 162 | +items with the same *value* as the one that is being searched for. |
| 163 | + |
2 | 164 |
|
3 |
| -Under Construction |
4 | 165 |
|
5 | 166 | *Written By Zain Humayun*
|
0 commit comments