Skip to content

Updated circular_linked_list #2483

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

Merged
merged 10 commits into from
Sep 26, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,8 @@
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_67/sol1.py)
* Problem 76
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_76/sol1.py)
* Problem 97
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_97/sol1.py)
* Problem 99
* [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_99/sol1.py)

Expand Down
290 changes: 113 additions & 177 deletions data_structures/linked_list/circular_linked_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,201 +2,137 @@


class Node:
"""
Class to represent a single node.

Each node has following attributes
* data
* next_ptr
"""

def __init__(self, data: Any):
self.data = data
self.next_ptr = None
self.next = None


class CircularLinkedList:
"""
Class to represent the CircularLinkedList.

CircularLinkedList has following attributes.
* head
* length
"""

def __init__(self):
self.head = None
self.length = 0
self.tail = None

def __len__(self) -> int:
"""
Dunder method to return length of the CircularLinkedList
>>> cll = CircularLinkedList()
>>> len(cll)
0
>>> cll.append(1)
>>> len(cll)
1
>>> cll.prepend(0)
>>> len(cll)
2
>>> cll.delete_front()
>>> len(cll)
1
>>> cll.delete_rear()
>>> len(cll)
0
"""
return self.length

def __str__(self) -> str:
"""
Dunder method to represent the string representation of the CircularLinkedList
>>> cll = CircularLinkedList()
>>> print(cll)
Empty linked list
>>> cll.append(1)
>>> cll.append(2)
>>> print(cll)
<Node data=1> => <Node data=2>
"""
current_node = self.head
if not current_node:
return "Empty linked list"

results = [current_node.data]
current_node = current_node.next_ptr

while current_node != self.head:
results.append(current_node.data)
current_node = current_node.next_ptr

return " => ".join(f"<Node data={result}>" for result in results)

def append(self, data: Any) -> None:
"""
Adds a node with given data to the end of the CircularLinkedList
>>> cll = CircularLinkedList()
>>> cll.append(1)
>>> print(f"{len(cll)}: {cll}")
1: <Node data=1>
>>> cll.append(2)
>>> print(f"{len(cll)}: {cll}")
2: <Node data=1> => <Node data=2>
"""
current_node = self.head
def __iter__(self):
node = self.head
while self.head:
yield node.data
node = node.next
if node == self.head:
break

new_node = Node(data)
new_node.next_ptr = new_node

if current_node:
while current_node.next_ptr != self.head:
current_node = current_node.next_ptr
def __len__(self) -> int:
return len(tuple(iter(self)))

current_node.next_ptr = new_node
new_node.next_ptr = self.head
else:
self.head = new_node
def __repr__(self):
return "->".join(str(item) for item in iter(self))

self.length += 1
def insert_tail(self, data: Any) -> None:
self.insert_nth(len(self), data)

def prepend(self, data: Any) -> None:
"""
Adds a node with given data to the front of the CircularLinkedList
>>> cll = CircularLinkedList()
>>> cll.prepend(1)
>>> cll.prepend(2)
>>> print(f"{len(cll)}: {cll}")
2: <Node data=2> => <Node data=1>
"""
current_node = self.head
def insert_head(self, data: Any) -> None:
self.insert_nth(0, data)

def insert_nth(self, index: int, data: Any) -> None:
if index < 0 or index > len(self):
raise IndexError("list index out of range.")
new_node = Node(data)
new_node.next_ptr = new_node

if current_node:
while current_node.next_ptr != self.head:
current_node = current_node.next_ptr

current_node.next_ptr = new_node
new_node.next_ptr = self.head

self.head = new_node
self.length += 1

def delete_front(self) -> None:
"""
Removes the 1st node from the CircularLinkedList
>>> cll = CircularLinkedList()
>>> cll.delete_front()
Traceback (most recent call last):
...
IndexError: Deleting from an empty list
>>> cll.append(1)
>>> cll.append(2)
>>> print(f"{len(cll)}: {cll}")
2: <Node data=1> => <Node data=2>
>>> cll.delete_front()
>>> print(f"{len(cll)}: {cll}")
1: <Node data=2>
>>> cll.delete_front()
>>> print(f"{len(cll)}: {cll}")
0: Empty linked list
"""
if not self.head:
raise IndexError("Deleting from an empty list")

current_node = self.head

if current_node.next_ptr == current_node:
self.head = None
if self.head is None:
new_node.next = new_node # first node points itself
self.tail = self.head = new_node
elif index == 0: # insert at head
new_node.next = self.head
self.head = self.tail.next = new_node
else:
while current_node.next_ptr != self.head:
current_node = current_node.next_ptr

current_node.next_ptr = self.head.next_ptr
self.head = self.head.next_ptr

self.length -= 1
if not self.head:
assert self.length == 0

def delete_rear(self) -> None:
"""
Removes the last node from the CircularLinkedList
>>> cll = CircularLinkedList()
>>> cll.delete_rear()
Traceback (most recent call last):
...
IndexError: Deleting from an empty list
>>> cll.append(1)
>>> cll.append(2)
>>> print(f"{len(cll)}: {cll}")
2: <Node data=1> => <Node data=2>
>>> cll.delete_rear()
>>> print(f"{len(cll)}: {cll}")
1: <Node data=1>
>>> cll.delete_rear()
>>> print(f"{len(cll)}: {cll}")
0: Empty linked list
"""
if not self.head:
raise IndexError("Deleting from an empty list")

temp_node, current_node = self.head, self.head

if current_node.next_ptr == current_node:
self.head = None
temp = self.head
for _ in range(index - 1):
temp = temp.next
new_node.next = temp.next
temp.next = new_node
if index == len(self) - 1: # insert at tail
self.tail = new_node

def delete_front(self):
return self.delete_nth(0)

def delete_tail(self) -> None:
return self.delete_nth(len(self) - 1)

def delete_nth(self, index: int = 0):
if not 0 <= index < len(self):
raise IndexError("list index out of range.")
delete_node = self.head
if self.head == self.tail: # just one node
self.head = self.tail = None
elif index == 0: # delete head node
self.tail.next = self.tail.next.next
self.head = self.head.next
else:
while current_node.next_ptr != self.head:
temp_node = current_node
current_node = current_node.next_ptr
temp = self.head
for _ in range(index - 1):
temp = temp.next
delete_node = temp.next
temp.next = temp.next.next
if index == len(self) - 1: # delete at tail
self.tail = temp
return delete_node.data

temp_node.next_ptr = current_node.next_ptr
def print_list(self) -> None:
print(self)

self.length -= 1
if not self.head:
assert self.length == 0
def is_empty(self):
return len(self) == 0


def test_circular_linked_list() -> None:
"""
>>> test_circular_linked_list()
"""
circular_linked_list = CircularLinkedList()
assert len(circular_linked_list) == 0
assert circular_linked_list.is_empty() is True
assert str(circular_linked_list) == ""

try:
circular_linked_list.delete_front()
assert False # This should not happen
except IndexError:
assert True # This should happen

try:
circular_linked_list.delete_tail()
assert False # This should not happen
except IndexError:
assert True # This should happen

try:
circular_linked_list.delete_nth(-1)
assert False
except IndexError:
assert True

try:
circular_linked_list.delete_nth(0)
assert False
except IndexError:
assert True

for i in range(5):
assert len(circular_linked_list) == i
circular_linked_list.insert_nth(i, i + 1)
Copy link
Member

Choose a reason for hiding this comment

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

Please add another is_empty() test.

Copy link
Member Author

Choose a reason for hiding this comment

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

Like this ?

    assert circular_linked_list.is_empty() is True
    for i in range(5):
        assert len(circular_linked_list) == i
        circular_linked_list.insert_nth(i, i + 1)

assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6))

circular_linked_list.insert_tail(6)
assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 7))
circular_linked_list.insert_head(0)
assert str(circular_linked_list) == "->".join(str(i) for i in range(0, 7))

assert circular_linked_list.delete_front() == 0
assert circular_linked_list.delete_tail() == 6
assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6))
assert circular_linked_list.delete_nth(2) == 3

circular_linked_list.insert_nth(2, 3)
assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6))


if __name__ == "__main__":
Expand Down