From f81f344d4571992c333528880804a0cd17994883 Mon Sep 17 00:00:00 2001 From: Nocturn9x Date: Mon, 14 Mar 2022 10:33:51 +0100 Subject: [PATCH] Added LinkedDeque implementation and tests --- example.nim | 32 +++++++++ src/nimdeque.nim | 183 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 example.nim create mode 100644 src/nimdeque.nim diff --git a/example.nim b/example.nim new file mode 100644 index 0000000..da921ed --- /dev/null +++ b/example.nim @@ -0,0 +1,32 @@ +import strformat +import src/nimdeque + + +when isMainModule: + const size = 10000 + var deque = newLinkedDeque[int]() + echo &"Generating {size} values" + for i in countup(0, size, 1): + deque.add(i) + echo "Checking values" + for i in countup(0, size, 1): + doAssert deque[i] == i + echo "Popping off the head" + doAssert deque.pop() == 0 + echo "Popping off the tail" + doAssert deque.pop(deque.high()) == size + echo "Checking new head" + doAssert deque[0] == 1 + echo "Checking new tail" + doAssert deque[deque.high()] == size - 1 + echo "Re-checking values" + for i in countup(0, size - 2, 1): + doAssert deque[i] == i + 1 + echo "Appending value at the beginning" + deque.addLeft(0) + echo "Re-checking head" + doAssert deque[0] == 0 + echo "Re-checking values" + for i in countup(0, size - 1, 1): + doAssert deque[i] == i + echo "All tests passed!" diff --git a/src/nimdeque.nim b/src/nimdeque.nim new file mode 100644 index 0000000..e66830d --- /dev/null +++ b/src/nimdeque.nim @@ -0,0 +1,183 @@ +# 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. +import strformat + + +type + DequeNode*[T] = ref object + 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 + head: DequeNode[T] + tail: DequeNode[T] + size: 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]: LinkedDeque[T] = + ## Initializes a new, empty + ## LinkedDeque object + 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) + + +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 and -1 if + ## the deque is empty + result = self.len() - 1 + + +proc getNode[T](self: LinkedDeque[T], i: int): DequeNode[T] = + ## 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(): + 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 = + ## Implements indexing into the queue + result = self.getNode(i).val + + +proc `[]=`*[T](self: LinkedDeque[T], i: int, val: T) = + ## Sets element at position i + ## to the given value + self[i].val = val + + +proc pop*[T](self: LinkedDeque[T], pos: int = 0): T = + ## Pops an element off the queue + ## 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 nears 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(): + self.tail = self.tail.prev + else: + node.prev.next = node.next + result = node.val + dec(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 + node = node.prev + + +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 &= "])"