Skip to content

Update doubly_linked_list.py #2573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
293 changes: 209 additions & 84 deletions data_structures/linked_list/doubly_linked_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,21 @@
- 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"""
Delete operation is more efficient
"""


class LinkedList:
"""
Copy link
Member

@cclauss cclauss Oct 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove all these doctests?!?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cclauss I had removed the previous doc test as I had completely changed the implementation. Apologies for that
I have updated the doc test with a set of new test cases and added in much more cases
Kindly review the new changes submitted for the Dubly linked List

Some functions are intentionally made of O(N) time complexity to provide a more user-friendly output on the console.
besides most functions are working in O(1) time complexity

Kindly update in case of furthermore changes are required

>>> 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
"""
class Node:
def __init__(self, data, previous=None, next_node=None):
self.data = data
self.previous = previous
self.next = next_node

def __str__(self):
return f"{self.data}"


class LinkedList:
def __init__(self):
self.head = None # First node in list
self.tail = None # Last node in list
Expand All @@ -32,89 +29,217 @@ def __str__(self):
current = self.head
nodes = []
while current is not None:
nodes.append(current)
nodes.append(current.data)
current = current.next
return " ".join(str(node) for node in nodes)
return "<-->".join(str(node) for node in nodes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the "<—>" notation to be a lot of visual clutter. Why not just add an .__iter__() method and then return str(list(self))?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi sir,
I have added in a linked list iterator class for iterating over the linked list
and removed the <--> representation.

However, I have left the str(self) same and just removed the <--> from the string as
this would keep visualizing the linked list if someone wants to visualize the data in the Linked list while debugging as opposed to the memory address that it spits out

I had previously used the <--> representation as I thought it would make the links between nodes more clear

@cclauss I have made the changes


def insert_at_head(self, data):
new_node = Node(data)
if self.is_empty:
self.tail = new_node
self.head = new_node
def __contains__(self, value):
current = self.head
while current:
if current.data == value:
return True
current = current.next
return False

def get_head_data(self):
if self.head:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.head:
return self.head.data if self.head else None

Same with get_tail_data()

return self.head.data
return None

def get_tail_data(self):
if self.tail:
return self.tail.data
return None

def set_head(self, node: Node) -> None:

if self.head is None:
self.head = node
self.tail = node
else:
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
self.insert_before_node(self.head, node)

def set_tail(self, node: Node) -> None:
if self.head is None:
self.set_head(node)
else:
self.tail.next = new_node
new_node.previous = self.tail
self.tail = new_node
self.insert_after_node(self.tail, node)

def delete_tail(self) -> str:
if self.is_empty:
return "List is empty"
def insert(self, value):
node = Node(value)
if self.head is None:
self.set_head(node)
else:
self.set_tail(node)

tail_data = self.tail.data
if self.tail.previous:
self.tail = self.tail.previous
self.tail.next = None
else: # if there is no previous node
self.head = None
self.tail = None
def insert_before_node(self, node: Node, node_to_insert: Node) -> None:
node_to_insert.next = node
node_to_insert.previous = node.previous

return tail_data
if node.previous is None:
self.head = node_to_insert
else:
node.previous.next = node_to_insert

def delete(self, data) -> str:
current = self.head
node.previous = node_to_insert

while current.data != data: # Find the position to delete
if current.next:
current = current.next
else: # We have reached the end an no value matches
return "No data matching given value"
def insert_after_node(self, node: Node, node_to_insert: Node) -> None:
node_to_insert.previous = node
node_to_insert.next = node.next

if node.next is None:
self.tail = node_to_insert
else:
node.next.previous = node_to_insert

node.next = node_to_insert

def insert_at_position(self, position: int, value: int) -> None:
current_position = 1
new_node = Node(value)
node = self.head
while node:
if current_position == position:
self.insert_before_node(node, new_node)
return None
current_position += 1
node = node.next
self.insert_after_node(self.tail, new_node)

def get_node(self, item):
node = self.head
while node:
if node.data == item:
return node
node = node.next
return None

def delete_value(self, value):
node = self.get_node(value)

if node is not None:
if node == self.head:
self.head = self.head.next

if node == self.tail:
self.tail = self.tail.previous

self.remove_node_pointers(node)
else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature does not say that this function returns a string. My sense is that we should raise an exception.

return 'Node not found'

if current == self.head:
self.delete_head()
@staticmethod
def remove_node_pointers(node: Node) -> None:
if node.next:
node.next.previous = node.previous

elif current == self.tail:
self.delete_tail()
if node.previous:
node.previous.next = node.next

else: # Before: 1 <--> 2(current) <--> 3
current.previous.next = current.next # 1 --> 3
current.next.previous = current.previous # 1 <--> 3
return data
node.next = None
node.previous = None

@property
def is_empty(self): # return True if the list is empty
def is_empty(self):
return self.head is None


class Node:
def __init__(self, data):
self.data = data
self.previous = None
self.next = None
def create_linked_list():
"""
>>> new_linked_list = LinkedList()
>>> new_linked_list.get_head_data() is None
True

def __str__(self):
return f"{self.data}"
>>> new_linked_list.get_tail_data() is None
True

>>> new_linked_list.is_empty()
True

>>> new_linked_list.insert(10)
>>> new_linked_list.get_head_data()
10

>>> new_linked_list.get_tail_data()
10

>>> new_linked_list.insert_at_position(position=3, value=20)
>>> new_linked_list.get_head_data()
10

>>> new_linked_list.get_tail_data()
20

>>> new_linked_list.set_head(Node(1000))
>>> new_linked_list.get_head_data()
1000

>>> new_linked_list.get_tail_data()
20

>>> new_linked_list.set_tail(Node(2000))
>>> new_linked_list.get_head_data()
1000
>>> new_linked_list.get_tail_data()
2000

>>> new_linked_list.is_empty()
False

>>> 10 in new_linked_list
True

>>> new_linked_list.delete_value(value=10)
>>> 10 in new_linked_list
False

>>> new_linked_list.delete_value(value=5000)
'Node not found'

>>> new_linked_list.delete_value(value=2000)
>>> new_linked_list.get_tail_data()
20

>>> new_linked_list.delete_value(value=1000)
>>> new_linked_list.get_tail_data()
20
>>> new_linked_list.get_head_data()
20
"""
linked_list = LinkedList()
for i in range(10):
linked_list.insert(value=i)

print(linked_list)
# 0<-->1<-->2<-->3<-->4<-->5<-->6<-->7<-->8<-->9
print(linked_list.head)
# 0
linked_list.delete_value(value=0)
print(linked_list.head)
# 1
print(linked_list)
# 1<-->2<-->3<-->4<-->5<-->6<-->7<-->8<-->9
linked_list.insert_at_position(position=1, value=100)
# 100<-->1<-->2<-->3<-->4<-->5<-->6<-->7<-->8<-->9
print(linked_list)
linked_list.delete_value(value=5)
print(linked_list)
# 100<-->1<-->2<-->3<-->4<-->6<-->7<-->8<-->9
print(linked_list.is_empty())
# False
linked_list.insert_at_position(position=12, value=200)

for i in range(5):
print(linked_list)
linked_list.delete_value(linked_list.tail.data)
# for each iterations
# 100 < -->1 < -->2 < -->3 < -->4 < -->6 < -->7 < -->8 < -->9 < -->200
# 100 < -->1 < -->2 < -->3 < -->4 < -->6 < -->7 < -->8 < -->9
# 100 < -->1 < -->2 < -->3 < -->4 < -->6 < -->7 < -->8
# 100 < -->1 < -->2 < -->3 < -->4 < -->6 < -->7
# 100 < -->1 < -->2 < -->3 < -->4 < -->6


if __name__ == '__main__':
import doctest

doctest.testmod()