You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: Queue/README.markdown
+181-6Lines changed: 181 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -40,20 +40,22 @@ This returns `3`, the next dequeue returns `57`, and so on. If the queue is empt
40
40
41
41
> **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.
42
42
43
+
## The code
44
+
43
45
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:
44
46
45
47
```swift
46
48
publicstructQueue<T> {
47
-
var array = [T]()
48
-
49
+
privatevar array = [T]()
50
+
49
51
publicvar isEmpty: Bool {
50
52
return array.isEmpty
51
53
}
52
54
53
55
publicvar count: Int {
54
56
return array.count
55
57
}
56
-
58
+
57
59
publicmutatingfuncenqueue(element: T) {
58
60
array.append(element)
59
61
}
@@ -76,10 +78,183 @@ This queue works just fine but it is not optimal.
76
78
77
79
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.
78
80
79
-
However, to dequeue we remove the element from the beginning of the array. This is an **O(n)** operation because it requires all remaining array elements to be shifted in memory.
81
+
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:
82
+
83
+
```swift
84
+
var queue = Queue<String>()
85
+
queue.enqueue("Ada")
86
+
queue.enqueue("Steve")
87
+
queue.enqueue("Tim")
88
+
```
89
+
90
+
then the array might actually look like this:
91
+
92
+
[ "Ada", "Steve", "Tim", xxx, xxx, xxx ]
93
+
94
+
where `xxx` is memory that is reserved but not filled in yet. Adding a new element to the array overwrites the next unused spot:
95
+
96
+
[ "Ada", "Steve", "Tim", "Grace", xxx, xxx ]
97
+
98
+
This is simply matter of copying memory from one place to another, a constant-time operation.
99
+
100
+
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. Resizing includes allocating new memory and copying all the existing data over to the new array. This is an **O(n)** process, so 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".
101
+
102
+
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.
103
+
104
+
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"`:
105
+
106
+
before [ "Ada", "Steve", "Tim", "Grace", xxx, xxx ]
107
+
/ / /
108
+
/ / /
109
+
/ / /
110
+
/ / /
111
+
after [ "Steve", "Tim", "Grace", xxx, xxx, xxx ]
112
+
113
+
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...
114
+
115
+
## A more efficient queue
116
+
117
+
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.
118
+
119
+
The idea is this: 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:
120
+
121
+
[ xxx, "Steve", "Tim", "Grace", xxx, xxx ]
122
+
123
+
After dequeuing `"Steve"`, the array is:
124
+
125
+
[ xxx, xxx, "Tim", "Grace", xxx, xxx ]
126
+
127
+
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:
128
+
129
+
[ "Tim", "Grace", xxx, xxx, xxx, xxx ]
130
+
131
+
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.
132
+
133
+
Here is how you could implement this version of `Queue`:
134
+
135
+
```swift
136
+
publicstructQueue<T> {
137
+
privatevar array = [T?]()
138
+
privatevar head =0
139
+
140
+
publicvar isEmpty: Bool {
141
+
return count ==0
142
+
}
143
+
144
+
publicvar count: Int {
145
+
return array.count- head
146
+
}
147
+
148
+
publicmutatingfuncenqueue(element: T) {
149
+
array.append(element)
150
+
}
151
+
152
+
publicmutatingfuncdequeue() -> T? {
153
+
guard head < array.count, let element = array[head] else { returnnil }
154
+
155
+
array[head] =nil
156
+
head +=1
157
+
158
+
let percentage =Double(head)/Double(array.count)
159
+
if head >20&& percentage >0.25 {
160
+
array.removeFirst(head)
161
+
head =0
162
+
}
163
+
164
+
return element
165
+
}
166
+
167
+
publicfuncpeek() -> T? {
168
+
if isEmpty {
169
+
returnnil
170
+
} else {
171
+
return array[head]
172
+
}
173
+
}
174
+
}
175
+
```
176
+
177
+
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.
178
+
179
+
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.
180
+
181
+
We go from this:
182
+
183
+
[ "Ada", "Steve", "Tim", "Grace", xxx, xxx ]
184
+
head
185
+
186
+
to this:
187
+
188
+
[ xxx, "Steve", "Tim", "Grace", xxx, xxx ]
189
+
head
190
+
191
+
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.
192
+
193
+
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:
194
+
195
+
```swift
196
+
let percentage =Double(head)/Double(array.count)
197
+
if array.count>50&& percentage >0.25 {
198
+
array.removeFirst(head)
199
+
head =0
200
+
}
201
+
```
202
+
203
+
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.
204
+
205
+
> **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.
Now if you dequeue another object, the array will look as follows:
245
+
246
+
```swift
247
+
q.dequeue() // "Tim"
248
+
q.array// [{Some "Grace"}]
249
+
q.count// 1
250
+
```
251
+
252
+
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.
253
+
254
+
## See also
80
255
81
-
More efficient implementations would use a linked list, a [circular buffer](../Ring Buffer/), or a [heap](../Heap/). (I might add an example of this later.)
256
+
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/).
82
257
83
258
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.
84
259
85
-
*Written by Matthijs Hollemans*
260
+
*Written for Swift Algorithm Club by Matthijs Hollemans*
0 commit comments