diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queue/double_ended_queue.py index dd003b7c9..36106d8bc 100644 --- a/data_structures/queue/double_ended_queue.py +++ b/data_structures/queue/double_ended_queue.py @@ -1,57 +1,470 @@ -# Python code to demonstrate working of -# extend(), extendleft(), rotate(), reverse() +""" +Implementation of double ended queue. +""" +from dataclasses import dataclass +from typing import Any, Iterable -# importing "collections" for deque operations -import collections -# initializing deque -de = collections.deque([1, 2, 3]) +class Deque: + """ + Deque data structure. -# using extend() to add numbers to right end -# adds 4,5,6 to right end -de.extend([4, 5, 6]) + Operations + ---------- + append(val: Any) -> None -# printing modified deque -print("The deque after extending deque at end is : ") -print(de) + appendleft(val: Any) -> None -# using extendleft() to add numbers to left end -# adds 7,8,9 to right end -de.extendleft([7, 8, 9]) + extend(iter: Iterable) -> None -# printing modified deque -print("The deque after extending deque at beginning is : ") -print(de) + extendleft(iter: Iterable) -> None -# using rotate() to rotate the deque -# rotates by 3 to left -de.rotate(-3) + pop() -> Any -# printing modified deque -print("The deque after rotating deque is : ") -print(de) + popleft() -> Any -# using reverse() to reverse the deque -de.reverse() -# printing modified deque -print("The deque after reversing deque is : ") -print(de) + Observers + --------- + is_empty() -> bool -# get right-end value and eliminate -startValue = de.pop() -print("The deque after popping value at end is : ") -print(de) + Attributes + ---------- + _front: _Node + front of the deque a.k.a. the first element -# get left-end value and eliminate -endValue = de.popleft() + _back: _Node + back of the element a.k.a. the last element -print("The deque after popping value at start is : ") -print(de) + _len: int + the number of nodes + """ -# eliminate element searched by value -de.remove(5) + __slots__ = ["_front", "_back", "_len"] -print("The deque after eliminating element searched by value : ") -print(de) + @dataclass + class _Node: + """ + Representation of a node. + Contains a value and a pointer to the next node as well as to the previous one. + """ + + val: Any = None + next: "Deque._Node" = None + prev: "Deque._Node" = None + + class _Iterator: + """ + Helper class for iteration. Will be used to implement iteration. + + Attributes + ---------- + _cur: _Node + the current node of the iteration. + """ + + __slots__ = ["_cur"] + + def __init__(self, cur: "Deque._Node") -> None: + self._cur = cur + + def __iter__(self) -> "Deque._Iterator": + """ + >>> our_deque = Deque([1, 2, 3]) + >>> iterator = iter(our_deque) + """ + return self + + def __next__(self) -> Any: + """ + >>> our_deque = Deque([1, 2, 3]) + >>> iterator = iter(our_deque) + >>> next(iterator) + 1 + >>> next(iterator) + 2 + >>> next(iterator) + 3 + """ + if self._cur is None: + # finished iterating + raise StopIteration + val = self._cur.val + self._cur = self._cur.next + + return val + + def __init__(self, iterable: Iterable = None) -> None: + self._front = self._back = None + self._len = 0 + + if iterable is not None: + # append every value to the deque + for val in iterable: + self.append(val) + + def append(self, val: Any) -> None: + """ + Adds val to the end of the deque. + Time complexity: O(1) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_1.append(4) + >>> our_deque_1 + [1, 2, 3, 4] + >>> our_deque_2 = Deque('ab') + >>> our_deque_2.append('c') + >>> our_deque_2 + ['a', 'b', 'c'] + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_1.append(4) + >>> deque_collections_1 + deque([1, 2, 3, 4]) + >>> deque_collections_2 = deque('ab') + >>> deque_collections_2.append('c') + >>> deque_collections_2 + deque(['a', 'b', 'c']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + node = self._Node(val, None, None) + if self.is_empty(): + # front = back + self._front = self._back = node + self._len = 1 + else: + # connect nodes + self._back.next = node + node.prev = self._back + self._back = node # assign new back to the new node + + self._len += 1 + + # make sure there were no errors + assert not self.is_empty(), "Error on appending value." + + def appendleft(self, val: Any) -> None: + """ + Adds val to the beginning of the deque. + Time complexity: O(1) + + >>> our_deque_1 = Deque([2, 3]) + >>> our_deque_1.appendleft(1) + >>> our_deque_1 + [1, 2, 3] + >>> our_deque_2 = Deque('bc') + >>> our_deque_2.appendleft('a') + >>> our_deque_2 + ['a', 'b', 'c'] + + >>> from collections import deque + >>> deque_collections_1 = deque([2, 3]) + >>> deque_collections_1.appendleft(1) + >>> deque_collections_1 + deque([1, 2, 3]) + >>> deque_collections_2 = deque('bc') + >>> deque_collections_2.appendleft('a') + >>> deque_collections_2 + deque(['a', 'b', 'c']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + node = self._Node(val, None, None) + if self.is_empty(): + # front = back + self._front = self._back = node + self._len = 1 + else: + # connect nodes + node.next = self._front + self._front.prev = node + self._front = node # assign new front to the new node + + self._len += 1 + + # make sure there were no errors + assert not self.is_empty(), "Error on appending value." + + def extend(self, iter: Iterable) -> None: + """ + Appends every value of iter to the end of the deque. + Time complexity: O(n) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_1.extend([4, 5]) + >>> our_deque_1 + [1, 2, 3, 4, 5] + >>> our_deque_2 = Deque('ab') + >>> our_deque_2.extend('cd') + >>> our_deque_2 + ['a', 'b', 'c', 'd'] + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_1.extend([4, 5]) + >>> deque_collections_1 + deque([1, 2, 3, 4, 5]) + >>> deque_collections_2 = deque('ab') + >>> deque_collections_2.extend('cd') + >>> deque_collections_2 + deque(['a', 'b', 'c', 'd']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + for val in iter: + self.append(val) + + def extendleft(self, iter: Iterable) -> None: + """ + Appends every value of iter to the beginning of the deque. + Time complexity: O(n) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_1.extendleft([0, -1]) + >>> our_deque_1 + [-1, 0, 1, 2, 3] + >>> our_deque_2 = Deque('cd') + >>> our_deque_2.extendleft('ba') + >>> our_deque_2 + ['a', 'b', 'c', 'd'] + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_1.extendleft([0, -1]) + >>> deque_collections_1 + deque([-1, 0, 1, 2, 3]) + >>> deque_collections_2 = deque('cd') + >>> deque_collections_2.extendleft('ba') + >>> deque_collections_2 + deque(['a', 'b', 'c', 'd']) + + >>> list(our_deque_1) == list(deque_collections_1) + True + >>> list(our_deque_2) == list(deque_collections_2) + True + """ + for val in iter: + self.appendleft(val) + + def pop(self) -> Any: + """ + Removes the last element of the deque and returns it. + Time complexity: O(1) + + @returns topop.val: the value of the node to pop. + + >>> our_deque = Deque([1, 2, 3, 15182]) + >>> our_popped = our_deque.pop() + >>> our_popped + 15182 + >>> our_deque + [1, 2, 3] + + >>> from collections import deque + >>> deque_collections = deque([1, 2, 3, 15182]) + >>> collections_popped = deque_collections.pop() + >>> collections_popped + 15182 + >>> deque_collections + deque([1, 2, 3]) + + >>> list(our_deque) == list(deque_collections) + True + >>> our_popped == collections_popped + True + """ + # make sure the deque has elements to pop + assert not self.is_empty(), "Deque is empty." + + topop = self._back + self._back = self._back.prev # set new back + self._back.next = ( + None # drop the last node - python will deallocate memory automatically + ) + + self._len -= 1 + + return topop.val + + def popleft(self) -> Any: + """ + Removes the first element of the deque and returns it. + Time complexity: O(1) + + @returns topop.val: the value of the node to pop. + + >>> our_deque = Deque([15182, 1, 2, 3]) + >>> our_popped = our_deque.popleft() + >>> our_popped + 15182 + >>> our_deque + [1, 2, 3] + + >>> from collections import deque + >>> deque_collections = deque([15182, 1, 2, 3]) + >>> collections_popped = deque_collections.popleft() + >>> collections_popped + 15182 + >>> deque_collections + deque([1, 2, 3]) + + >>> list(our_deque) == list(deque_collections) + True + >>> our_popped == collections_popped + True + """ + # make sure the deque has elements to pop + assert not self.is_empty(), "Deque is empty." + + topop = self._front + self._front = self._front.next # set new front and drop the first node + self._front.prev = None + + self._len -= 1 + + return topop.val + + def is_empty(self) -> bool: + """ + Checks if the deque is empty. + Time complexity: O(1) + + >>> our_deque = Deque([1, 2, 3]) + >>> our_deque.is_empty() + False + >>> our_empty_deque = Deque() + >>> our_empty_deque.is_empty() + True + + >>> from collections import deque + >>> empty_deque_collections = deque() + >>> list(our_empty_deque) == list(empty_deque_collections) + True + """ + return self._front is None + + def __len__(self) -> int: + """ + Implements len() function. Returns the length of the deque. + Time complexity: O(1) + + >>> our_deque = Deque([1, 2, 3]) + >>> len(our_deque) + 3 + >>> our_empty_deque = Deque() + >>> len(our_empty_deque) + 0 + + >>> from collections import deque + >>> deque_collections = deque([1, 2, 3]) + >>> len(deque_collections) + 3 + >>> empty_deque_collections = deque() + >>> len(empty_deque_collections) + 0 + >>> len(our_empty_deque) == len(empty_deque_collections) + True + """ + return self._len + + def __eq__(self, other: "Deque") -> bool: + """ + Implements "==" operator. Returns if *self* is equal to *other*. + Time complexity: O(n) + + >>> our_deque_1 = Deque([1, 2, 3]) + >>> our_deque_2 = Deque([1, 2, 3]) + >>> our_deque_1 == our_deque_2 + True + >>> our_deque_3 = Deque([1, 2]) + >>> our_deque_1 == our_deque_3 + False + + >>> from collections import deque + >>> deque_collections_1 = deque([1, 2, 3]) + >>> deque_collections_2 = deque([1, 2, 3]) + >>> deque_collections_1 == deque_collections_2 + True + >>> deque_collections_3 = deque([1, 2]) + >>> deque_collections_1 == deque_collections_3 + False + + >>> (our_deque_1 == our_deque_2) == (deque_collections_1 == deque_collections_2) + True + >>> (our_deque_1 == our_deque_3) == (deque_collections_1 == deque_collections_3) + True + """ + me = self._front + oth = other._front + + # if the length of the deques are not the same, they are not equal + if len(self) != len(other): + return False + + while me is not None and oth is not None: + # compare every value + if me.val != oth.val: + return False + me = me.next + oth = oth.next + + return True + + def __iter__(self) -> "_Iterator": + """ + Implements iteration. + Time complexity: O(1) + + >>> our_deque = Deque([1, 2, 3]) + >>> for v in our_deque: + ... print(v) + 1 + 2 + 3 + + >>> from collections import deque + >>> deque_collections = deque([1, 2, 3]) + >>> for v in deque_collections: + ... print(v) + 1 + 2 + 3 + """ + return Deque._Iterator(self._front) + + def __repr__(self) -> str: + """ + Implements representation of the deque. + Represents it as a list, with its values between '[' and ']'. + Time complexity: O(n) + + >>> our_deque = Deque([1, 2, 3]) + >>> our_deque + [1, 2, 3] + """ + values_list = [] + aux = self._front + while aux is not None: + # append the values in a list to display + values_list.append(aux.val) + aux = aux.next + + return "[" + ", ".join(repr(val) for val in values_list) + "]" + + +if __name__ == "__main__": + import doctest + + doctest.testmod()