Queue implementation in Swift language
Aside from the bugs, there are a couple of things about your implementation that you probably want to change to make it more Swift-like. One is it looks like you're replicating the Java names like poll
and offer
– these names are (IMHO) a little strange, and partly related to needing to have two functions, an exception-throwing version and a non-exception version. Since Swift doesn't have exceptions, you can probably just name them using the conventional names other Swift collections use, like append.
The other issue is that your implementation incorporates traversing the queue into the queue itself. It's better to do this kind of traversal outside the collection than mix the two. Swift collections do this with indexes.
Here's a possible Swift-like queue implementation. First, the node and base queue definition:
// singly rather than doubly linked list implementation
// private, as users of Queue never use this directly
private final class QueueNode<T> {
// note, not optional – every node has a value
var value: T
// but the last node doesn't have a next
var next: QueueNode<T>? = nil
init(value: T) { self.value = value }
}
// Ideally, Queue would be a struct with value semantics but
// I'll leave that for now
public final class Queue<T> {
// note, these are both optionals, to handle
// an empty queue
private var head: QueueNode<T>? = nil
private var tail: QueueNode<T>? = nil
public init() { }
}
Then, extend with an append
and dequeue
method:
extension Queue {
// append is the standard name in Swift for this operation
public func append(newElement: T) {
let oldTail = tail
self.tail = QueueNode(value: newElement)
if head == nil { head = tail }
else { oldTail?.next = self.tail }
}
public func dequeue() -> T? {
if let head = self.head {
self.head = head.next
if head.next == nil { tail = nil }
return head.value
}
else {
return nil
}
}
}
At this point, you're almost done if all you want to do is add and remove. To add traversal, first create an index type, which is a simple wrapper on the node type:
public struct QueueIndex<T>: ForwardIndexType {
private let node: QueueNode<T>?
public func successor() -> QueueIndex<T> {
return QueueIndex(node: node?.next)
}
}
public func ==<T>(lhs: QueueIndex<T>, rhs: QueueIndex<T>) -> Bool {
return lhs.node === rhs.node
}
Then, use this index to conform to MutableCollectionType
:
extension Queue: MutableCollectionType {
public typealias Index = QueueIndex<T>
public var startIndex: Index { return Index(node: head) }
public var endIndex: Index { return Index(node: nil) }
public subscript(idx: Index) -> T {
get {
precondition(idx.node != nil, "Attempt to subscript out of bounds")
return idx.node!.value
}
set(newValue) {
precondition(idx.node != nil, "Attempt to subscript out of bounds")
idx.node!.value = newValue
}
}
typealias Generator = IndexingGenerator<Queue>
public func generate() -> Generator {
return Generator(self)
}
}
From conforming to collection type, you get a whole load of stuff for free:
var q = Queue<String>()
q.append("one")
q.append("two")
for x in q {
println(x)
}
isEmpty(q) // returns false
first(q) // returns Optional("one")
count(q) // returns 2
",".join(q) // returns "one,two"
let x = find(q, "two") // returns index of second entry
let counts = map(q) { count($0) } // returns [3,3]
Finally, there's 3 more protocols that are good to conform to: ExtensibleCollectionType
, Printable
and ArrayLiteralConvertible
:
// init() and append() requirements are already covered
extension Queue: ExtensibleCollectionType {
public func reserveCapacity(n: Index.Distance) {
// do nothing
}
public func extend<S : SequenceType where S.Generator.Element == T>
(newElements: S) {
for x in newElements {
self.append(x)
}
}
}
extension Queue: ArrayLiteralConvertible {
public convenience init(arrayLiteral elements: T...) {
self.init()
// conformance to ExtensibleCollectionType makes this easy
self.extend(elements)
}
}
extension Queue: Printable {
// pretty easy given conformance to CollectionType
public var description: String {
return "[" + ", ".join(map(self,toString)) + "]"
}
}
These mean you can now create queues as easily arrays or sets:
var q: Queue = [1,2,3]
println(q) // prints [1, 2, 3]
There are a lot of little issues regarding the internal consistency of your model:
When you first instantiate a new
Queue
, you are initializinghead
,tail
andcurrent
to three differentNode
objects (even though nothing's been queued yet!). That doesn't make sense. Personally, I'd be inclined to make those three properties optional and leave them asnil
until you start enqueuing stuff.By the way, when you start using optionals for these properties, many of the other methods are simplified.
It looks like you're trying to implement a doubly linked list. So, when you dequeue, you need to not only update the
Queue
properties, but you also need to update thenext
pointer for the next item that will be dequeued (because it still will be pointing to that item you already dequeued). You don't want your linked list maintaining references to objects that have been dequeued and should be removed.When you dequeue the last item, you really should be clearing out
head
andtail
references.You're implementing a doubly linked list, without regard to the object ownership model. Thus, as soon as you have more than one item in your list, you've got a strong reference cycle between nodes and if not remedied, this will leak if there are still objects in the queue when the queue, itself, is deallocated. Consider making one of the references
weak
orunowned
.I'd suggest keeping this simple (just enqueue and dequeue). The concept of
poll
andoffer
may make sense in terms of an arbitrary linked list, but not in the context of a queue. The implementations ofpoll
andoffer
are also incorrect (e.g.poll
callsdeQueue
which removes the tail, but the object you return is thehead
!), but I presume you'd just remove these functions altogether. Likewise, I do not understand the intent ofcurrent
in the context of a queue.I'd suggest you make
Queue
andNode
conform toPrintable
. It will simplify your debugging process.
The following is code of a playground consisting of a queue implemented with an array and a queue implemented with nodes. There are substantial performance differences between the two but if you going to be iterating through a queue you might want to use one with an array.
import UIKit // for NSDate() used in testing)
// QUEUE WITH ARRAY IMPLEMENTATION (For ease of adaptibility, slow enque, faster deque):
struct QueueArray<T> {
private var items = [T]()
mutating func enQueue(item: T) {
items.append(item)
}
mutating func deQueue() -> T? {
return items.removeFirst()
}
func isEmpty() -> Bool {
return items.isEmpty
}
func peek() -> T? {
return items.first
}
}
// QUEUE WITH NODE IMPLEMENTATION (For performance, if all you need is a queue this is it):
class QNode<T> {
var value: T
var next: QNode?
init(item:T) {
value = item
}
}
struct Queue<T> {
private var top: QNode<T>!
private var bottom: QNode<T>!
init() {
top = nil
bottom = nil
}
mutating func enQueue(item: T) {
let newNode:QNode<T> = QNode(item: item)
if top == nil {
top = newNode
bottom = top
return
}
bottom.next = newNode
bottom = newNode
}
mutating func deQueue() -> T? {
let topItem: T? = top?.value
if topItem == nil {
return nil
}
if let nextItem = top.next {
top = nextItem
} else {
top = nil
bottom = nil
}
return topItem
}
func isEmpty() -> Bool {
return top == nil ? true : false
}
func peek() -> T? {
return top?.value
}
}
// QUEUE NODES TEST
let testAmount = 100
var queueNodes = Queue<Int>()
let queueNodesEnqueStart = NSDate()
for i in 0...testAmount {
queueNodes.enQueue(i)
}
let queueNodesEnqueEnd = NSDate()
while !queueNodes.isEmpty() {
queueNodes.deQueue()
}
let queueNodesDequeEnd = NSDate()
// QUEUE ARRAY TEST
var queueArray = QueueArray<Int>()
let queueArrayEnqueStart = NSDate()
for i in 0...testAmount {
queueArray.enQueue(i)
}
let queueArrayEnqueEnd = NSDate()
while !queueArray.isEmpty() {
queueArray.deQueue()
}
let queueArrayDequeEnd = NSDate()
// QUEUE NODES RESULT:
print("queueEnqueDuration: \(queueNodesEnqueEnd.timeIntervalSinceDate(queueNodesEnqueStart)), Deque: \(queueNodesDequeEnd.timeIntervalSinceDate(queueNodesEnqueEnd))")
// QUEUE ARRAY RESULT:
print("queueArrayEnqueDuration: \(queueArrayEnqueEnd.timeIntervalSinceDate(queueArrayEnqueStart)), Deque: \(queueArrayDequeEnd.timeIntervalSinceDate(queueArrayEnqueEnd))")