diff --git a/AUTHORS b/AUTHORS index 25dd5c009..151ac7c21 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,4 @@ Gagandeep Singh Kartikei Mittal Umesh<23umesh.here@gmail.com> - +Rohan Singh diff --git a/pydatastructs/linear_data_structures/__init__.py b/pydatastructs/linear_data_structures/__init__.py index c13973d94..0fe59d913 100644 --- a/pydatastructs/linear_data_structures/__init__.py +++ b/pydatastructs/linear_data_structures/__init__.py @@ -2,6 +2,7 @@ from . import ( arrays, + linked_lists ) from .arrays import ( @@ -9,3 +10,8 @@ DynamicOneDimensionalArray ) __all__.extend(arrays.__all__) + +from .linked_lists import ( + DoublyLinkedList +) +__all__.extend(linked_lists.__all__) diff --git a/pydatastructs/linear_data_structures/linked_lists.py b/pydatastructs/linear_data_structures/linked_lists.py new file mode 100644 index 000000000..35dbc9f4f --- /dev/null +++ b/pydatastructs/linear_data_structures/linked_lists.py @@ -0,0 +1,284 @@ +from __future__ import print_function, division +from pydatastructs.utils.misc_util import _check_type, LinkedListNode + +__all__ = [ + 'DoublyLinkedList' +] + +class LinkedList(object): + """ + Abstract class for Linked List. + """ + __slots__ = ['head', 'size'] + + def __len__(self): + return self.size + + @property + def is_empty(self): + return self.size == 0 + + def __str__(self): + """ + For printing the linked list. + """ + elements = [] + current_node = self.head + while current_node is not None: + elements.append(current_node.data) + current_node = current_node.next + return str(elements) + +class DoublyLinkedList(LinkedList): + """ + Represents Doubly Linked List + + Examples + ======== + + >>> from pydatastructs import DoublyLinkedList + >>> dll = DoublyLinkedList() + >>> dll.append(6) + >>> dll[0].data + 6 + >>> dll.head.data + 6 + >>> dll.append(5) + >>> dll.append_left(2) + >>> print(dll) + [2, 6, 5] + >>> dll[0].data = 7.2 + >>> dll.extract(1).data + 6 + >>> print(dll) + [7.2, 5] + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Doubly_linked_list + + """ + __slots__ = ['head', 'tail', 'size'] + + def __new__(cls): + obj = object.__new__(cls) + obj.head = None + obj.tail = None + obj.size = 0 + return obj + + def append_left(self, data): + """ + Pushes a new node at the start i.e., + the left of the list. + + Parameters + ========== + + data + Any valid data to be stored in the node. + """ + self.insert_at(0, data) + + def append(self, data): + """ + Appends a new node at the end of the list. + + Parameters + ========== + + data + Any valid data to be stored in the node. + """ + self.insert_at(self.size, data) + + def insert_after(self, prev_node, data): + """ + Inserts a new node after the prev_node. + + Parameters + ========== + + prev_node: LinkedListNode + The node after which the + new node is to be inserted. + + data + Any valid data to be stored in the node. + """ + self.size += 1 + new_node = LinkedListNode(data, + links=['next', 'prev'], + addrs=[None, None]) + new_node.next = prev_node.next + prev_node.next = new_node + new_node.prev = prev_node + + if new_node.next is None: + self.tail = new_node + + def insert_before(self, next_node, data): + """ + Inserts a new node before the new_node. + + Parameters + ========== + + next_node: LinkedListNode + The node before which the + new node is to be inserted. + + data + Any valid data to be stored in the node. + """ + self.size += 1 + new_node = LinkedListNode(data, + links=['next', 'prev'], + addrs=[None, None]) + new_node.prev = next_node.prev + next_node.prev = new_node + new_node.next = next_node + + if new_node.prev is None: + self.head = new_node + + def insert_at(self, index, data): + """ + Inserts a new node at the input index. + + Parameters + ========== + + index: int + An integer satisfying python indexing properties. + + data + Any valid data to be stored in the node. + """ + if self.size == 0 and (index in (0, -1)): + index = 0 + + if index < 0: + index = self.size + index + + if index > self.size: + raise IndexError('%d index is out of range.'%(index)) + + self.size += 1 + new_node = LinkedListNode(data, + links=['next', 'prev'], + addrs=[None, None]) + if self.size == 1: + self.head, self.tail = \ + new_node, new_node + else: + counter = 0 + current_node = self.head + prev_node = None + while counter != index: + prev_node = current_node + current_node = current_node.next + counter += 1 + new_node.prev = prev_node + new_node.next = current_node + if prev_node is not None: + prev_node.next = new_node + if current_node is not None: + current_node.prev = new_node + if new_node.next is None: + self.tail = new_node + if new_node.prev is None: + self.head = new_node + + def pop_left(self): + """ + Extracts the Node from the left + i.e. start of the list. + + Returns + ======= + + old_head: LinkedListNode + The leftmost element of linked + list. + """ + self.extract(0) + + def pop_right(self): + """ + Extracts the node from the right + of the linked list. + + Returns + ======= + + old_tail: LinkedListNode + The leftmost element of linked + list. + """ + self.extract(-1) + + def extract(self, index): + """ + Extracts the node at the index of the list. + + Parameters + ========== + + index: int + An integer satisfying python indexing properties. + + Returns + ======= + + current_node: LinkedListNode + The node at index i. + """ + if self.is_empty: + raise ValueError("The list is empty.") + + if index < 0: + index = self.size + index + + if index >= self.size: + raise IndexError('%d is out of range.'%(index)) + + self.size -= 1 + counter = 0 + current_node = self.head + prev_node = None + while counter != index: + prev_node = current_node + current_node = current_node.next + counter += 1 + if prev_node is not None: + prev_node.next = current_node.next + if current_node.next is not None: + current_node.next.prev = prev_node + if index == 0: + self.head = current_node.next + if index == self.size: + self.tail = current_node.prev + return current_node + + def __getitem__(self, index): + """ + Returns + ======= + + current_node: LinkedListNode + The node at given index. + """ + if index < 0: + index = self.size + index + + if index >= self.size: + raise IndexError('%d index is out of range.'%(index)) + + counter = 0 + current_node = self.head + while counter != index: + current_node = current_node.next + counter += 1 + return current_node diff --git a/pydatastructs/linear_data_structures/tests/test_linked_lists.py b/pydatastructs/linear_data_structures/tests/test_linked_lists.py new file mode 100644 index 000000000..4709fd65f --- /dev/null +++ b/pydatastructs/linear_data_structures/tests/test_linked_lists.py @@ -0,0 +1,37 @@ +from pydatastructs.linear_data_structures import DoublyLinkedList +from pydatastructs.utils.raises_util import raises +import copy, random + +def test_DoublyLinkedList(): + random.seed(1000) + dll = DoublyLinkedList() + assert raises(IndexError, lambda: dll[2]) + dll.append_left(5) + dll.append(1) + dll.append_left(2) + dll.append(3) + dll.insert_after(dll[1], 4) + dll.insert_after(dll[-1], 6) + dll.insert_before(dll[0], 1) + dll.insert_at(0, 2) + dll.insert_at(-1, 9) + dll.extract(2) + dll.extract(0) + dll.extract(-1) + dll[-2].data = 0 + assert str(dll) == "[1, 5, 4, 1, 0, 9]" + assert len(dll) == 6 + assert raises(IndexError, lambda: dll.insert_at(7, None)) + assert raises(IndexError, lambda: dll.extract(20)) + dll_copy = copy.deepcopy(dll) + for i in range(len(dll)): + if i%2 == 0: + dll.pop_left() + else: + dll.pop_right() + assert str(dll) == "[]" + for _ in range(len(dll_copy)): + index = random.randint(0, len(dll_copy) - 1) + dll_copy.extract(index) + assert str(dll_copy) == "[]" + assert raises(ValueError, lambda: dll_copy.extract(1)) diff --git a/pydatastructs/utils/__init__.py b/pydatastructs/utils/__init__.py index 64f0e982e..0923b7ddd 100644 --- a/pydatastructs/utils/__init__.py +++ b/pydatastructs/utils/__init__.py @@ -2,6 +2,7 @@ from . import misc_util from .misc_util import ( - TreeNode + TreeNode, + LinkedListNode ) __all__.extend(misc_util.__all__) diff --git a/pydatastructs/utils/misc_util.py b/pydatastructs/utils/misc_util.py index 9e7e7bf7d..b55dde764 100644 --- a/pydatastructs/utils/misc_util.py +++ b/pydatastructs/utils/misc_util.py @@ -1,7 +1,8 @@ from __future__ import print_function, division __all__ = [ - 'TreeNode' + 'TreeNode', + 'LinkedListNode' ] _check_type = lambda a, t: isinstance(a, t) @@ -40,3 +41,27 @@ def __str__(self): Used for printing. """ return str((self.left, self.key, self.data, self.right)) + +class LinkedListNode(object): + """ + Represents node in linked lists. + + Parameters + ========== + + data + Any valid data to be stored in the node. + """ + + # __slots__ = ['data'] + + def __new__(cls, data=None, links=['next'], addrs=[None]): + obj = object.__new__(cls) + obj.data = data + for link, addr in zip(links, addrs): + obj.__setattr__(link, addr) + obj.__slots__ = ['data'] + links + return obj + + def __str__(self): + return str(self.data) diff --git a/pydatastructs/utils/raises_util.py b/pydatastructs/utils/raises_util.py index 3d55b5552..3a324d38d 100644 --- a/pydatastructs/utils/raises_util.py +++ b/pydatastructs/utils/raises_util.py @@ -14,3 +14,4 @@ def raises(exception, code): """ with pytest.raises(exception): code() + return True