# 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 &= ")"