From 5e642607c8958aa1e4277f75399442b2048a4725 Mon Sep 17 00:00:00 2001 From: Du Yuanchao Date: Wed, 21 Oct 2020 22:31:09 +0800 Subject: [PATCH] Update doubly linked list (#3619) * update doubly linked list * reformat code add more test * add test to iter * updating DIRECTORY.md Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- .../linked_list/doubly_linked_list.py | 262 ++++++++++++------ 1 file changed, 184 insertions(+), 78 deletions(-) diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 1b4005f59..0eb3cf101 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -1,89 +1,156 @@ """ -- A linked list is similar to an array, it holds values. However, links in a linked - list do not have indexes. -- This is an example of a double ended, doubly linked list. -- Each link references the next link and the previous one. -- A Doubly Linked List (DLL) contains an extra pointer, typically called previous - pointer, together with next pointer and data which are there in singly linked list. - - Advantages over SLL - It can be traversed in both forward and backward direction. - Delete operation is more efficient""" +https://en.wikipedia.org/wiki/Doubly_linked_list +""" -class LinkedList: - """ - >>> linked_list = LinkedList() - >>> linked_list.insert_at_head("a") - >>> linked_list.insert_at_tail("b") - >>> linked_list.delete_tail() - 'b' - >>> linked_list.is_empty - False - >>> linked_list.delete_head() - 'a' - >>> linked_list.is_empty - True - """ - - def __init__(self): - self.head = None # First node in list - self.tail = None # Last node in list +class Node: + def __init__(self, data): + self.data = data + self.previous = None + self.next = None def __str__(self): - current = self.head - nodes = [] - while current is not None: - nodes.append(current) - current = current.next - return " ".join(str(node) for node in nodes) + return f"{self.data}" + + +class DoublyLinkedList: + def __init__(self): + self.head = None + self.tail = None + + def __iter__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_head('b') + >>> linked_list.insert_at_head('a') + >>> linked_list.insert_at_tail('c') + >>> tuple(linked_list) + ('a', 'b', 'c') + """ + node = self.head + while node: + yield node.data + node = node.next + + def __str__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_tail('a') + >>> linked_list.insert_at_tail('b') + >>> linked_list.insert_at_tail('c') + >>> str(linked_list) + 'a->b->c' + """ + return "->".join([str(item) for item in self]) + + def __len__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> for i in range(0, 5): + ... linked_list.insert_at_nth(i, i + 1) + >>> len(linked_list) == 5 + True + """ + return len(tuple(iter(self))) def insert_at_head(self, data): + self.insert_at_nth(0, data) + + def insert_at_tail(self, data): + self.insert_at_nth(len(self), data) + + def insert_at_nth(self, index: int, data): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_nth(-1, 666) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> linked_list.insert_at_nth(1, 666) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> linked_list.insert_at_nth(0, 2) + >>> linked_list.insert_at_nth(0, 1) + >>> linked_list.insert_at_nth(2, 4) + >>> linked_list.insert_at_nth(2, 3) + >>> str(linked_list) + '1->2->3->4' + >>> linked_list.insert_at_nth(5, 5) + Traceback (most recent call last): + .... + IndexError: list index out of range + """ + if not 0 <= index <= len(self): + raise IndexError("list index out of range") new_node = Node(data) - if self.is_empty: - self.tail = new_node - self.head = new_node - else: + if self.head is None: + self.head = self.tail = new_node + elif index == 0: self.head.previous = new_node new_node.next = self.head self.head = new_node - - def delete_head(self) -> str: - if self.is_empty: - return "List is empty" - - head_data = self.head.data - if self.head.next: - self.head = self.head.next - self.head.previous = None - - else: # If there is no next previous node - self.head = None - self.tail = None - - return head_data - - def insert_at_tail(self, data): - new_node = Node(data) - if self.is_empty: - self.tail = new_node - self.head = new_node - else: + elif index == len(self): self.tail.next = new_node new_node.previous = self.tail self.tail = new_node + else: + temp = self.head + for i in range(0, index): + temp = temp.next + temp.previous.next = new_node + new_node.previous = temp.previous + new_node.next = temp + temp.previous = new_node - def delete_tail(self) -> str: - if self.is_empty: - return "List is empty" + def delete_head(self): + return self.delete_at_nth(0) - tail_data = self.tail.data - if self.tail.previous: + def delete_tail(self): + return self.delete_at_nth(len(self) - 1) + + def delete_at_nth(self, index: int): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.delete_at_nth(0) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> for i in range(0, 5): + ... linked_list.insert_at_nth(i, i + 1) + >>> linked_list.delete_at_nth(0) == 1 + True + >>> linked_list.delete_at_nth(3) == 5 + True + >>> linked_list.delete_at_nth(1) == 3 + True + >>> str(linked_list) + '2->4' + >>> linked_list.delete_at_nth(2) + Traceback (most recent call last): + .... + IndexError: list index out of range + """ + if not 0 <= index <= len(self) - 1: + raise IndexError("list index out of range") + delete_node = self.head # default first node + if len(self) == 1: + self.head = self.tail = None + elif index == 0: + self.head = self.head.next + self.head.previous = None + elif index == len(self) - 1: + delete_node = self.tail self.tail = self.tail.previous self.tail.next = None - else: # if there is no previous node - self.head = None - self.tail = None - - return tail_data + else: + temp = self.head + for i in range(0, index): + temp = temp.next + delete_node = temp + temp.next.previous = temp.previous + temp.previous.next = temp.next + return delete_node.data def delete(self, data) -> str: current = self.head @@ -105,16 +172,55 @@ class LinkedList: current.next.previous = current.previous # 1 <--> 3 return data - @property - def is_empty(self): # return True if the list is empty - return self.head is None + def is_empty(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.is_empty() + True + >>> linked_list.insert_at_tail(1) + >>> linked_list.is_empty() + False + """ + return len(self) == 0 -class Node: - def __init__(self, data): - self.data = data - self.previous = None - self.next = None +def test_doubly_linked_list() -> None: + """ + >>> test_doubly_linked_list() + """ + linked_list = DoublyLinkedList() + assert linked_list.is_empty() is True + assert str(linked_list) == "" - def __str__(self): - return f"{self.data}" + try: + linked_list.delete_head() + assert False # This should not happen. + except IndexError: + assert True # This should happen. + + try: + linked_list.delete_tail() + assert False # This should not happen. + except IndexError: + assert True # This should happen. + + for i in range(10): + assert len(linked_list) == i + linked_list.insert_at_nth(i, i + 1) + assert str(linked_list) == "->".join(str(i) for i in range(1, 11)) + + linked_list.insert_at_head(0) + linked_list.insert_at_tail(11) + assert str(linked_list) == "->".join(str(i) for i in range(0, 12)) + + assert linked_list.delete_head() == 0 + assert linked_list.delete_at_nth(9) == 10 + assert linked_list.delete_tail() == 11 + assert len(linked_list) == 9 + assert str(linked_list) == "->".join(str(i) for i in range(1, 10)) + + +if __name__ == "__main__": + from doctest import testmod + + testmod()