338 lines
9.6 KiB
Nim
338 lines
9.6 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
|
|
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(1) 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
|
|
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 &= ")" |