nimdeque/src/private/queues/linked.nim

340 lines
9.7 KiB
Nim

# Copyright 2022 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## A deque implemented on top of a doubly linked list
import strformat
type
DequeNode[T] = ref object
## A doubly linked list node
next: DequeNode[T]
prev: DequeNode[T]
val: T
LinkedDeque*[T] = ref object
## A collection type
## optimized for ~O(1)
## access at both ends
## based on a doubly
## 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] =
## Internal proc to
## initialize an element
## for the LinkedQueue
## object
new(result)
result.val = val
proc newLinkedDeque*[T](maxSize: int = 0): LinkedDeque[T] {.raises: [ValueError].} =
## Initializes a new, empty
## LinkedDeque object with an
## optional size limit. A maxSize
## of 0 indicates no size limit
## for the queue
new(result)
result.size = 0
if maxSize < 0:
raise newException(ValueError, "maxSize cannot be less than zero")
result.maxSize = maxSize
proc len*[T](self: LinkedDeque[T]): int =
## Returns the length of the deque.
## This operation takes constant time
result = self.size
proc high*[T](self: LinkedDeque[T]): int =
## Returns the index of the last
## 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] {.raises: [IndexDefect, ValueError].} =
## Low level method for indexing and getting
## a node object back
when defined(boundChecks):
if self.high() == -1:
raise newException(IndexDefect, "LinkedDeque is empty")
elif i > self.high() or i < 0:
raise newException(IndexDefect, &"{i} notin 0..{self.high()}")
var pos = 0
if i < self.high() div 2:
# If we're taking an element
# in the first half of the
# queue, we start from the
# beginning
result = self.head
while pos < i:
result = result.next
inc(pos)
else:
# Otherwise, we start from
# the end and go backwards
# at the chosen position
result = self.tail
pos = self.high()
while pos > i:
result = result.prev
dec(pos)
proc `[]`*[T](self: LinkedDeque[T], i: int): T {.raises: [IndexDefect, ValueError].} =
## Implements indexing into the queue
result = self.getNode(i).val
proc `[]=`*[T](self: LinkedDeque[T], i: int, val: T) {.raises: [IndexDefect, ValueError].} =
## Sets element at position i
## to the given value
self[i].val = val
proc `[]`*[T](self: LinkedDeque[T], i: BackwardsIndex): T {.raises: [IndexDefect, ValueError].} =
## Implements indexing into the queue
## with backwards indeces
result = self[self.size - int(i)]
proc `[]=`*[T](self: LinkedDeque[T], i: BackwardsIndex, val: T) {.raises: [IndexDefect, ValueError].} =
## Sets element at backwards
## position i to the given value
self[self.size - int(i)] = val
proc pop*[T](self: LinkedDeque[T], pos: int = 0): T {.raises: [IndexDefect, ValueError].} =
## Pops an element off the deque
## at the given index (default 0).
## The queue is optimized for popping
## and appending in roughly constant time
## at both ends, so this is quite fast
## if the operation is near the ends of
## the iterable. The popped item is returned
var node = self.getNode(pos)
if pos == 0:
self.head = self.head.next
elif pos == self.high():
var prev = self.tail.prev
prev.next = nil
self.tail = prev
else:
var prev = node.prev
var next = node.next
prev.next = node.next
next.prev = prev
result = node.val
dec(self.size)
proc pop*[T](self: LinkedDeque[T], pos: BackwardsIndex): T {.raises: [IndexDefect, ValueError].} =
## Same as self.pop but for backwards indeces
result = self.pop(self.size - int(pos))
proc clear*[T](self: LinkedDeque[T]) =
## Clears the deque in constant time
## (relies on the GC to clean up!)
self.head = nil
self.tail = nil
self.size = 0
proc clearPop*[T](self: LinkedDeque[T]) =
## Clears the deque by repeatedly
## calling self.pop() in O(n) time,
## slower than clear()
while self.len() > 0:
discard self.pop()
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
if self.maxSize > 0 and 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.maxSize > 0 and self.size == self.maxSize:
discard self.pop(self.high())
self.head = node
self.head.next = head
if head != nil:
head.prev = node
inc(self.size)
proc insert*[T](self: LinkedDeque[T], pos: int, val: T) {.raises: [IndexDefect, ValueError].} =
## Inserts the given value at the given
## position. When pos equals 0, this proc is equivalent
## to addLeft. In all other cases, all items are
## "shifted" by 1 (this is not actually what happens,
## but the result is analogous). The operation takes
## constant time at the ends, but the complexity grows
## to 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
when not defined(noQueueSizeCheck):
if self.maxSize > 0 and self.size == self.maxSize:
raise newException(IndexDefect, &"LinkedDeque has reached its maximum size ({self.maxSize})")
if pos == 0:
self.addLeft(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 =
## Implements the iteration protocol
## for the queue
var node = self.head
while node != nil:
yield node.val
node = node.next
iterator pairs*[T](self: LinkedDeque[T]): auto =
## Implements pairwise iteration with
## the index and the element at that
## index
var i = 0
for element in self:
yield (i, element)
inc(i)
iterator reversed*[T](self: LinkedDeque[T]): T =
## Same as self.items(), but starts at
## the end and yields the contents of
## the queue backwards
var node = self.tail
while node != nil:
yield node.val
node = node.prev
iterator reversedPairs*[T](self: LinkedDeque[T]): auto =
## Implements pairwise reversed iteration
var i = 0
for e in self.reversed():
yield (i, e)
inc(i)
proc `==`*[T](self: LinkedDeque[T], other: LinkedDeque[T]): bool =
## Compares two LinkedDeque objects
if self.high() != other.high():
return false
for i, item in self:
if item != other[i]:
return false
return true
proc contains*[T](self: LinkedDeque[T], val: T): bool =
## Returns if the given element is in
## the deque
for element in self:
if element == val:
return true
return false
proc extend*[T](self: LinkedDeque[T], other: LinkedDeque[T]) =
## Extends self with the items from other
for item in other:
self.add(item)
proc extend*[T](self: LinkedDeque[T], other: seq[T]) =
## Extends self with the items from other
for item in other:
self.add(item)
proc extendLeft*[T](self: LinkedDeque[T], other: LinkedDeque[T]) =
## Same as self.extend(), but extends from
## the head instead of the tail
for item in other:
self.addLeft(item)
proc extendLeft*[T](self: LinkedDeque[T], other: seq[T]) =
## Same as self.extend(), but extends from
## the head instead of the tail
for item in other:
self.addLeft(item)
proc find*[T](self: LinkedDeque[T], val: T): int =
## Returns the first occurrence
## of val in the queue. Returns -1
## if the item isn't found
for i, item in self:
if item == val:
return i
return -1
proc `$`*[T](self: LinkedDeque[T]): string =
## Returns a string representation
## of the deque
result = "deque(["
for i, item in self:
result &= $item
if i < self.high():
result &= ", "
result &= "]"
if self.maxSize > 0:
result &= &", maxSize={self.maxSize}"
result &= ")"