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
|
||||
|
||||
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
|
||||
|
|
|
@ -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 &= ")"
|
|
@ -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!"
|
||||
|
|
Loading…
Reference in New Issue