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:
parent
e3eb0b0ff9
commit
35956f1464
56
README.md
56
README.md
|
@ -1,6 +1,60 @@
|
||||||
# nimdeque
|
# 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
|
__Note__: This is mostly a toy, there are no performance guarantees nor particular optimizations other than very obvious ones. With
|
||||||
|
|
|
@ -28,10 +28,15 @@ type
|
||||||
## optimized for ~O(1)
|
## optimized for ~O(1)
|
||||||
## access at both ends
|
## access at both ends
|
||||||
## based on a doubly
|
## 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]
|
head: DequeNode[T]
|
||||||
tail: DequeNode[T]
|
tail: DequeNode[T]
|
||||||
size: int
|
size: int
|
||||||
|
maxSize: int
|
||||||
|
|
||||||
|
|
||||||
proc newDequeNode[T](val: T): DequeNode[T] =
|
proc newDequeNode[T](val: T): DequeNode[T] =
|
||||||
|
@ -43,37 +48,19 @@ proc newDequeNode[T](val: T): DequeNode[T] =
|
||||||
result.val = val
|
result.val = val
|
||||||
|
|
||||||
|
|
||||||
proc newLinkedDeque*[T]: LinkedDeque[T] =
|
proc newLinkedDeque*[T](maxSize: int = 0): LinkedDeque[T] =
|
||||||
## Initializes a new, empty
|
## 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)
|
new(result)
|
||||||
result.size = 0
|
result.size = 0
|
||||||
|
if maxSize < 0:
|
||||||
|
raise newException(ValueError, "maxSize cannot be less than zero")
|
||||||
proc add*[T](self: LinkedDeque[T], val: T) =
|
result.maxSize = maxSize
|
||||||
## Appends an element at the end
|
if result.maxSize == 0:
|
||||||
## of the queue
|
result.maxSize = int.high()
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
proc len*[T](self: LinkedDeque[T]): int =
|
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 =
|
proc high*[T](self: LinkedDeque[T]): int =
|
||||||
## Returns the index of the last
|
## Returns the index of the last
|
||||||
## element in the deque and -1 if
|
## element in the deque or -1 if
|
||||||
## the deque is empty
|
## the deque is empty
|
||||||
result = self.len() - 1
|
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] =
|
proc getNode[T](self: LinkedDeque[T], i: int): DequeNode[T] =
|
||||||
## Low level method for indexing and getting
|
## Low level method for indexing and getting
|
||||||
## a node object back
|
## a node object back
|
||||||
|
@ -117,31 +110,6 @@ proc getNode[T](self: LinkedDeque[T], i: int): DequeNode[T] =
|
||||||
dec(pos)
|
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 =
|
proc `[]`*[T](self: LinkedDeque[T], i: int): T =
|
||||||
## Implements indexing into the queue
|
## Implements indexing into the queue
|
||||||
result = self.getNode(i).val
|
result = self.getNode(i).val
|
||||||
|
@ -189,9 +157,68 @@ proc pop*[T](self: LinkedDeque[T], pos: int = 0): T =
|
||||||
dec(self.size)
|
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
|
## 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 =
|
iterator items*[T](self: LinkedDeque[T]): T =
|
||||||
|
@ -219,7 +246,7 @@ iterator reversed*[T](self: LinkedDeque[T]): T =
|
||||||
## the queue backwards
|
## the queue backwards
|
||||||
var node = self.tail
|
var node = self.tail
|
||||||
while node != nil:
|
while node != nil:
|
||||||
yield node
|
yield node.val
|
||||||
node = node.prev
|
node = node.prev
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,4 +267,7 @@ proc `$`*[T](self: LinkedDeque[T]): string =
|
||||||
result &= $item
|
result &= $item
|
||||||
if i < self.high():
|
if i < self.high():
|
||||||
result &= ", "
|
result &= ", "
|
||||||
result &= "])"
|
result &= "]"
|
||||||
|
if self.maxSize > 0:
|
||||||
|
result &= &", maxSize={self.maxSize}"
|
||||||
|
result &= ")"
|
|
@ -25,8 +25,17 @@ when isMainModule:
|
||||||
echo "\t- Checking length"
|
echo "\t- Checking length"
|
||||||
doAssert deque.len() == size
|
doAssert deque.len() == size
|
||||||
echo "Checking iteration"
|
echo "Checking iteration"
|
||||||
|
echo "\t- Checking indeces"
|
||||||
for i in countup(0, size - 1, 1):
|
for i in countup(0, size - 1, 1):
|
||||||
doAssert deque[i] == i
|
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"
|
echo "Checking contains"
|
||||||
for i in countup(0, size - 1, 1):
|
for i in countup(0, size - 1, 1):
|
||||||
doAssert i in deque
|
doAssert i in deque
|
||||||
|
@ -34,13 +43,13 @@ when isMainModule:
|
||||||
doAssert deque.pop() == 0
|
doAssert deque.pop() == 0
|
||||||
echo "\t- Checking length"
|
echo "\t- Checking length"
|
||||||
doAssert deque.len() == size - 1
|
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
|
doAssert deque.pop(deque.high()) == size - 1
|
||||||
echo "\t- Checking length"
|
echo "\t- Checking length"
|
||||||
doAssert deque.len() == size - 2
|
doAssert deque.len() == size - 2
|
||||||
echo "Checking new head"
|
echo "\t- Checking new tail"
|
||||||
doAssert deque[0] == 1
|
|
||||||
echo "Checking new tail"
|
|
||||||
doAssert deque[deque.high()] == size - 2
|
doAssert deque[deque.high()] == size - 2
|
||||||
echo "Re-checking values"
|
echo "Re-checking values"
|
||||||
for i in countup(0, size - 3, 1):
|
for i in countup(0, size - 3, 1):
|
||||||
|
@ -95,4 +104,30 @@ when isMainModule:
|
||||||
echo "Checking backwards indeces"
|
echo "Checking backwards indeces"
|
||||||
for i in countdown(deque.high(), 1):
|
for i in countdown(deque.high(), 1):
|
||||||
doAssert deque[^i] == deque[deque.len() - i]
|
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!"
|
echo "All tests passed!"
|
||||||
|
|
Loading…
Reference in New Issue