Added support for a max queue size, fixed a bug in pop with backward index and updated tests. Added examples in README

This commit is contained in:
Nocturn9x 2022-03-15 12:26:09 +01:00
parent e3eb0b0ff9
commit 35956f1464
3 changed files with 183 additions and 64 deletions

View File

@ -1,6 +1,60 @@
# nimdeque
Various deque implementations in pure nim. See the tests directory for usage examples
Various deque implementations in pure nim. A deque (short for "double-ended queue") is a data type
that is optimized for access towards its ends. A deque's most interesting feauture is the ~O(1)
time that it takes to pop/append at either ends (as opposed to regular lists where appending at the
beginning is an O(n) operation).
------------------------
## Examples
### LinkedDeque
A `LinkedDeque` is a deque based on a doubly linked list
```nim
import nimdeque
queue = newLinkedDeque[int]()
# Appends at the end
queue.add(1)
queue.add(2)
queue.add(3)
# Prepends at the beginning
queue.addLeft(0)
queue.addLeft(-1)
queue.addLeft(-2)
# Pops the first element in O(1) time
queue.pop(0)
# Pops the last element in O(1) time
queue.pop(queue.high())
# Supports iteration
for i, e in queue:
echo i, " ", e
# Reversed iteration too!
for e in queue.reversed():
echo e
echo queue.len()
echo 5 in queue # false
echo 0 in queue # true
# Item accessing works just like regular sequence types in Nim.
# Note that the further the item is from either end of the
# queue, the higher the time it takes to retrieve it. For
# fast random access, seqs should be used instead
assert queue[0] == -1
assert queue[^1] == queue[queue.high()]
```
__Note__: This is mostly a toy, there are no performance guarantees nor particular optimizations other than very obvious ones. With

View File

@ -28,10 +28,15 @@ type
## optimized for ~O(1)
## access at both ends
## based on a doubly
## linked list
## linked list. If
## maxSize is specified
## and the queue is full,
## older items are discarded
## to make room for newer ones
head: DequeNode[T]
tail: DequeNode[T]
size: int
maxSize: int
proc newDequeNode[T](val: T): DequeNode[T] =
@ -43,37 +48,19 @@ proc newDequeNode[T](val: T): DequeNode[T] =
result.val = val
proc newLinkedDeque*[T]: LinkedDeque[T] =
proc newLinkedDeque*[T](maxSize: int = 0): LinkedDeque[T] =
## Initializes a new, empty
## LinkedDeque object
## LinkedDeque object with an
## optional size limit. A maxSize
## of 0 indicates no size limit
## for the queue
new(result)
result.size = 0
proc add*[T](self: LinkedDeque[T], val: T) =
## Appends an element at the end
## of the queue
var newNode = newDequeNode(val)
if self.head == nil:
self.head = newNode
else:
newNode.prev = self.tail
self.tail.next = newNode
self.tail = newNode
inc(self.size)
proc addLeft*[T](self: LinkedDeque[T], val: T) =
## Behaves like add(), but inserts a
## value at the beginning instead
## of at the end. This operation
## takes constant time
var node = newDequeNode(val)
var head = self.head
self.head = node
self.head.next = head
head.prev = node
inc(self.size)
if maxSize < 0:
raise newException(ValueError, "maxSize cannot be less than zero")
result.maxSize = maxSize
if result.maxSize == 0:
result.maxSize = int.high()
proc len*[T](self: LinkedDeque[T]): int =
@ -84,11 +71,17 @@ proc len*[T](self: LinkedDeque[T]): int =
proc high*[T](self: LinkedDeque[T]): int =
## Returns the index of the last
## element in the deque and -1 if
## element in the deque or -1 if
## the deque is empty
result = self.len() - 1
proc maxSize*[T](self: LinkedDeque[T]): int =
## Returns the maximum size of the
## queue (0 = no limit)
result = self.maxSize
proc getNode[T](self: LinkedDeque[T], i: int): DequeNode[T] =
## Low level method for indexing and getting
## a node object back
@ -117,31 +110,6 @@ proc getNode[T](self: LinkedDeque[T], i: int): DequeNode[T] =
dec(pos)
proc insert*[T](self: LinkedDeque[T], pos: int, val: T) =
## Inserts the given value at the given
## position. When pos equals 0 or self.high(),
## this proc is equivalent to addLeft and add
## respectively. In all other cases, all items
## are "shifted" by 1 (shifted is in quotes because
## no shifting actually occurs, but the result is
## the same). The operation takes roughly constant
## time and the complexity becomes O(n) the closer
## the index gets to the middle of the deque
if pos == 0:
self.addLeft(val)
elif pos == self.high():
self.add(val)
else:
var node = self.getNode(pos)
var prev: DequeNode[T] = node.prev
var newNode = newDequeNode(val)
node.prev = newNode
prev.next = newNode
newNode.next = node
newNode.prev = prev
inc(self.size)
proc `[]`*[T](self: LinkedDeque[T], i: int): T =
## Implements indexing into the queue
result = self.getNode(i).val
@ -189,9 +157,68 @@ proc pop*[T](self: LinkedDeque[T], pos: int = 0): T =
dec(self.size)
proc pop*[T](self: LinkedDeque[T], i: BackwardsIndex): T =
proc pop*[T](self: LinkedDeque[T], pos: BackwardsIndex): T =
## Same as self.pop but for backwards indeces
result = self.pop(self.size - int(i) - 1)
result = self.pop(self.size - int(pos))
proc add*[T](self: LinkedDeque[T], val: T) =
## Appends an element at the end
## of the queue
var newNode = newDequeNode(val)
newNode.prev = self.tail
if self.tail != nil:
self.tail.next = newNode
self.tail = newNode
if self.head == nil:
self.head = newNode
elif self.size == self.maxSize:
discard self.pop()
inc(self.size)
proc addLeft*[T](self: LinkedDeque[T], val: T) =
## Behaves like add(), but inserts a
## value at the beginning instead
## of at the end. This operation
## takes constant time
var node = newDequeNode(val)
var head = self.head
if self.size == self.maxSize:
discard self.pop(self.high())
self.head = node
self.head.next = head
head.prev = node
inc(self.size)
proc insert*[T](self: LinkedDeque[T], pos: int, val: T) =
## Inserts the given value at the given
## position. When pos equals 0 or self.high(),
## this proc is equivalent to addLeft and add
## respectively. In all other cases, all items
## are "shifted" by 1 (shifted is in quotes because
## no shifting actually occurs, but the result is
## the same). The operation takes roughly constant
## time and the complexity becomes O(n) the closer
## the index gets to the middle of the deque. This
## proc raises an IndexDefect if the queue's max
## size is reached
if self.size == self.maxSize:
raise newException(IndexDefect, &"LinkedDeque has reached its maximum size ({self.maxSize})")
if pos == 0:
self.addLeft(val)
elif pos == self.high():
self.add(val)
else:
var node = self.getNode(pos)
var prev: DequeNode[T] = node.prev
var newNode = newDequeNode(val)
node.prev = newNode
prev.next = newNode
newNode.next = node
newNode.prev = prev
inc(self.size)
iterator items*[T](self: LinkedDeque[T]): T =
@ -219,7 +246,7 @@ iterator reversed*[T](self: LinkedDeque[T]): T =
## the queue backwards
var node = self.tail
while node != nil:
yield node
yield node.val
node = node.prev
@ -240,4 +267,7 @@ proc `$`*[T](self: LinkedDeque[T]): string =
result &= $item
if i < self.high():
result &= ", "
result &= "])"
result &= "]"
if self.maxSize > 0:
result &= &", maxSize={self.maxSize}"
result &= ")"

View File

@ -25,8 +25,17 @@ when isMainModule:
echo "\t- Checking length"
doAssert deque.len() == size
echo "Checking iteration"
echo "\t- Checking indeces"
for i in countup(0, size - 1, 1):
doAssert deque[i] == i
echo "\t- Checking pairs"
for i, e in deque:
assert i == e
echo "\t- Checking reversed iteration"
var j = 0
for e in deque.reversed():
assert size - j - 1 == e
inc(j)
echo "Checking contains"
for i in countup(0, size - 1, 1):
doAssert i in deque
@ -34,13 +43,13 @@ when isMainModule:
doAssert deque.pop() == 0
echo "\t- Checking length"
doAssert deque.len() == size - 1
echo "\t- Popping off the tail"
echo "\t- Checking new head"
doAssert deque[0] == 1
echo "Popping off the tail"
doAssert deque.pop(deque.high()) == size - 1
echo "\t- Checking length"
doAssert deque.len() == size - 2
echo "Checking new head"
doAssert deque[0] == 1
echo "Checking new tail"
echo "\t- Checking new tail"
doAssert deque[deque.high()] == size - 2
echo "Re-checking values"
for i in countup(0, size - 3, 1):
@ -95,4 +104,30 @@ when isMainModule:
echo "Checking backwards indeces"
for i in countdown(deque.high(), 1):
doAssert deque[^i] == deque[deque.len() - i]
deque.add(deque.pop(^1))
doAssert deque[deque.high()] == deque[^1]
echo &"Checking maxSize ({size div 2})"
var queue = newLinkedDeque[int](size div 2)
echo &"\t- Generating {size div 2} values"
for i in countup(0, (size div 2) - 1):
queue.add(i)
echo "\t- Checking length"
doAssert queue.len() == size div 2
var temp = queue[0]
echo "\t- Testing append at the end"
queue.add((size div 2) + 1)
echo "\t- Checking length"
doAssert queue.len() == size div 2
echo "\t- Checking item"
doAssert queue[^1] == (size div 2) + 1
echo "\t- Checking displacement"
doAssert queue[0] == temp + 1
echo "Testing prepend at the beginning"
queue.addLeft(0)
echo "\t- Checking length"
doAssert queue.len() == size div 2
echo "\t- Checking item"
doAssert queue[0] == 0
echo "\t- Checking displacement"
doAssert queue[^1] == (size div 2) - 1
echo "All tests passed!"