Skip to content

Commit a83afba

Browse files
committed
Adds bounded priority queue
1 parent 1911744 commit a83afba

File tree

6 files changed

+257
-0
lines changed

6 files changed

+257
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
struct Message: Comparable, CustomStringConvertible {
2+
let name: String
3+
let priority: Int
4+
5+
var description: String {
6+
return "\(name):\(priority)"
7+
}
8+
}
9+
10+
func == (m1: Message, m2: Message) -> Bool {
11+
return m1.priority == m2.priority
12+
}
13+
14+
func < (m1: Message, m2: Message) -> Bool {
15+
return m1.priority < m2.priority
16+
}
17+
18+
let queue = BoundedPriorityQueue<Message>(maxElements: 5)
19+
queue.count
20+
21+
queue.enqueue(Message(name: "hello", priority: 100))
22+
queue.count
23+
queue.peek()
24+
print(queue)
25+
26+
queue.enqueue(Message(name: "there", priority: 99))
27+
queue.count
28+
queue.peek()
29+
print(queue)
30+
31+
queue.enqueue(Message(name: "world", priority: 150))
32+
queue.count
33+
queue.peek()
34+
print(queue)
35+
36+
queue.enqueue(Message(name: "swift", priority: 110))
37+
queue.count
38+
queue.peek()
39+
print(queue)
40+
41+
queue.enqueue(Message(name: "is", priority: 30))
42+
queue.count
43+
queue.peek()
44+
print(queue)
45+
46+
// At this point, the queue is:
47+
// <world:150, swift:110, hello:100, there:99, is:30, >
48+
49+
// Try to insert an item with a really low priority. This should not get added.
50+
queue.enqueue(Message(name: "very", priority: -1))
51+
queue.count // 5
52+
queue.peek()
53+
print(queue) // still same as before
54+
55+
// Try to insert an item with medium priority. This gets added and the lowest
56+
// priority item is removed.
57+
queue.enqueue(Message(name: "cool", priority: 120))
58+
queue.count
59+
queue.peek()
60+
print(queue)
61+
62+
// Try to insert an item with very high priority. This gets added and the
63+
// lowest priority item is removed.
64+
queue.enqueue(Message(name: "!!!", priority: 500))
65+
queue.count
66+
queue.peek()
67+
print(queue)
68+
69+
// Test dequeuing
70+
queue.dequeue()
71+
queue.count
72+
queue.peek()
73+
print(queue)
74+
75+
queue.dequeue()
76+
queue.count
77+
queue.peek()
78+
print(queue)
79+
80+
queue.dequeue()
81+
queue.count
82+
queue.peek()
83+
print(queue)
84+
85+
queue.dequeue()
86+
queue.count
87+
queue.peek()
88+
print(queue)
89+
90+
queue.dequeue()
91+
queue.count
92+
queue.peek()
93+
print(queue)
94+
95+
queue.dequeue()
96+
queue.count
97+
queue.peek()
98+
print(queue)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
public class LinkedListNode<T: Comparable> {
2+
var value: T
3+
var next: LinkedListNode?
4+
var previous: LinkedListNode?
5+
6+
public init(value: T) {
7+
self.value = value
8+
}
9+
}
10+
11+
public class BoundedPriorityQueue<T: Comparable> {
12+
fileprivate typealias Node = LinkedListNode<T>
13+
14+
private(set) public var count = 0
15+
fileprivate var head: Node?
16+
private var tail: Node?
17+
private var maxElements: Int
18+
19+
public init(maxElements: Int) {
20+
self.maxElements = maxElements
21+
}
22+
23+
public var isEmpty: Bool {
24+
return count == 0
25+
}
26+
27+
public func peek() -> T? {
28+
return head?.value
29+
}
30+
31+
public func enqueue(_ value: T) {
32+
if let node = insert(value, after: findInsertionPoint(value)) {
33+
// If the newly inserted node is the last one in the list, then update
34+
// the tail pointer.
35+
if node.next == nil {
36+
tail = node
37+
}
38+
39+
// If the queue is full, then remove an element from the back.
40+
count += 1
41+
if count > maxElements {
42+
removeLeastImportantElement()
43+
}
44+
}
45+
}
46+
47+
private func insert(_ value: T, after: Node?) -> Node? {
48+
if let previous = after {
49+
50+
// If the queue is full and we have to insert at the end of the list,
51+
// then there's no reason to insert the new value.
52+
if count == maxElements && previous.next == nil {
53+
print("Queue is full and priority of new object is too small")
54+
return nil
55+
}
56+
57+
// Put the new node in between previous and previous.next (if exists).
58+
let node = Node(value: value)
59+
node.next = previous.next
60+
previous.next?.previous = node
61+
previous.next = node
62+
node.previous = previous
63+
return node
64+
65+
} else if let first = head {
66+
// Have to insert at the head, so shift the existing head up once place.
67+
head = Node(value: value)
68+
head!.next = first
69+
first.previous = head
70+
return head
71+
72+
} else {
73+
// This is the very first item in the queue.
74+
head = Node(value: value)
75+
return head
76+
}
77+
}
78+
79+
/* Find the node after which to insert the new value. If this returns nil,
80+
the new value should be inserted at the head of the list. */
81+
private func findInsertionPoint(_ value: T) -> Node? {
82+
var node = head
83+
var prev: Node?
84+
85+
while let current = node, value < current.value {
86+
prev = node
87+
node = current.next
88+
}
89+
return prev
90+
}
91+
92+
private func removeLeastImportantElement() {
93+
if let last = tail {
94+
tail = last.previous
95+
tail?.next = nil
96+
count -= 1
97+
}
98+
99+
// Note: Instead of using a tail pointer, we could just scan from the new
100+
// node until the end. Then nodes also don't need a previous pointer. But
101+
// this is much slower on large lists.
102+
}
103+
104+
public func dequeue() -> T? {
105+
if let first = head {
106+
count -= 1
107+
if count == 0 {
108+
head = nil
109+
tail = nil
110+
} else {
111+
head = first.next
112+
head!.previous = nil
113+
}
114+
return first.value
115+
} else {
116+
return nil
117+
}
118+
}
119+
}
120+
121+
extension LinkedListNode: CustomStringConvertible {
122+
public var description: String {
123+
return "\(value)"
124+
}
125+
}
126+
127+
extension BoundedPriorityQueue: CustomStringConvertible {
128+
public var description: String {
129+
var s = "<"
130+
var node = head
131+
while let current = node {
132+
s += "\(current), "
133+
node = current.next
134+
}
135+
return s + ">"
136+
}
137+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='osx' last-migration='1000'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>

SwiftAlgorithmClub/BoundedPriorityQueue.playground/playground.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

SwiftAlgorithmClub/SwiftAlgorithmClub.xcworkspace/contents.xcworkspacedata

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)