Skip to content

Instantly share code, notes, and snippets.

@Casper-Smet
Last active June 18, 2021 15:53
Show Gist options
  • Select an option

  • Save Casper-Smet/7a64715881672b821c802f146b1c6862 to your computer and use it in GitHub Desktop.

Select an option

Save Casper-Smet/7a64715881672b821c802f146b1c6862 to your computer and use it in GitHub Desktop.
Linked list in Python based on MutableSequence
from collections.abc import MutableSequence
from dataclasses import dataclass
from typing import Any, Generator
@dataclass
class LinkedNode():
value: Any
next_node: "LinkedNode" = None
def forward(self) -> "LinkedNode":
return self.next_node
class LinkedList(MutableSequence):
def __init__(self, *values):
self.length = 0
self.head = None
self.tail = None
if values:
self.head = LinkedNode(values[0])
self.tail = self.head
self.length = 1
if len(values) > 1:
self.extend(values[1:])
def append(self, val: Any):
new_node = LinkedNode(val)
if self.tail is not None:
self.tail.next_node = new_node
self.tail = new_node
self.length += 1
def extend(self, iterable):
if not isinstance(iterable, LinkedList):
for val in iterable:
self.append(val)
else:
self.tail.next_node = iterable.head
self.tail = iterable.head
self.length += len(iterable)
def traverse(self) -> Generator[LinkedNode, None, None]:
temp = self.head
if temp is None:
return None
yield temp
while temp.next_node is not None:
temp = temp.forward()
yield temp
def __len__(self) -> int:
return self.length
def _process_idx(self, idx: int) -> LinkedNode:
if idx > 0:
range_goal = idx + 1
elif idx < 0:
range_goal = len(self) + idx + 1
else:
return self.head
if range_goal > len(self) or range_goal <= 0:
raise IndexError(
f"{self.__class__.__name__} index {idx} is out of range for range {len(self)}")
for _, val in zip(range(range_goal), self.traverse()):
continue
return val
def index(self, val: Any):
for i, iter_val in enumerate(self):
if iter_val == val:
return i
raise ValueError(f"{val} not in {self.__class__.__name__}")
def __delitem__(self, idx: int) -> None:
previous_node = self.head
for i, node in enumerate(self.traverse()):
if i == idx:
break
previous_node = node
if i != idx:
raise ValueError(
f"{self.__class__.__name__}.remove(x): x not in {self.__class__.__name__}")
next_node = node.forward()
if node == previous_node:
self.head = next_node
else:
previous_node.next_node = next_node
del node
def __getitem__(self, idx: int) -> Any:
return self._process_idx(idx).value
def __setitem__(self, idx: int, val: Any) -> None:
self._process_idx(idx).value = val
def insert(self, idx: int, val: Any) -> None:
if idx < 0:
raise IndexError(
f"{self.__class__.__name__} insert method index should be in range [0, len({self.__class__.__name__})")
elif idx == 0:
old_head = self.head
self.head = LinkedNode(val, old_head)
else:
prev_node = self._process_idx(idx - 1)
old_node = prev_node.forward()
new_node = LinkedNode(val, old_node)
prev_node.next_node = new_node
self.length += 1
def __iter__(self) -> Generator[LinkedNode, None, None]:
return (node.value for node in self.traverse())
def reverse(self) -> "LinkedList":
new_ll = LinkedList()
new_ll.length = len(self)
tail = LinkedNode(self.head.value)
new_ll.tail = tail
head = self.head.forward()
while head is not None:
tail = LinkedNode(head.value, tail)
head = head.forward()
new_ll.head = tail
return new_ll
def __reversed__(self) -> "LinkedList":
return self.reverse()
def __repr__(self) -> str:
return str(list(self))
def main():
lst = [1, 2, 3, 4]
linked_lst = LinkedList(*lst)
print("Linked list: ", linked_lst)
print("Reversed: ", linked_lst.reverse())
linked_lst.append(5)
print("Append: ", linked_lst)
print("Length: ", len(linked_lst))
print("Index 1: ", linked_lst[1])
print("Index -1: ", linked_lst[-1])
try:
print(linked_lst[-6])
except IndexError:
print("Below range index error successful")
try:
print(linked_lst[6])
except IndexError:
print("Above range index error successful")
linked_lst.extend(linked_lst.reverse())
print("Extend: ", linked_lst)
print("Index of 5: ", linked_lst.index(5))
linked_lst.remove(5)
print("Remove 5:", linked_lst)
print("Pop 5: ", linked_lst.pop(4), linked_lst)
print("Is 1 in: ", 1 in linked_lst)
linked_lst[0] = 2
print("Index 0 to 2: ", linked_lst)
linked_lst[1] += 20
print("Index 1 += 1: ", linked_lst)
linked_lst[-1] = "Wow"
print("Last item to 'Wow': ", linked_lst)
print("Count occurrences of 4: ", linked_lst.count(4))
if __name__ == "__main__":
main()
Linked list: [1, 2, 3, 4]
Reversed: [4, 3, 2, 1]
Append: [1, 2, 3, 4, 5]
Length: 5
Index 1: 2
Index -1: 5
Below range index error successful
Above range index error successful
Extend: [1, 2, 3, 4, 5, 5, 4, 3, 2, 1]
Index of 5: 4
Remove 5: [1, 2, 3, 4, 5, 4, 3, 2, 1]
Pop 5: 5 [1, 2, 3, 4, 4, 3, 2, 1]
Is 1 in: True
Index 0 to 2: [2, 2, 3, 4, 4, 3, 2, 1]
Index 1 += 1: [2, 22, 3, 4, 4, 3, 2, 1]
Last item to 'Wow': [2, 22, 3, 4, 4, 3, 2, 'Wow']
Count occurrences of 4: 2
@Casper-Smet
Copy link
Author

This is (afaik) a full implementation of a linked list. I based the implementation on this image from wikipedia.

The inner workings of reversing and the MutableSequence methods were thought up by me as an exercise in implementing a custom data structure and methods in Python using abstract base classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment