Skip to content

Instantly share code, notes, and snippets.

@masterPiece93
Last active November 25, 2025 04:16
Show Gist options
  • Select an option

  • Save masterPiece93/451f8fad30a230c29d9d19c53ddd4a72 to your computer and use it in GitHub Desktop.

Select an option

Save masterPiece93/451f8fad30a230c29d9d19c53ddd4a72 to your computer and use it in GitHub Desktop.
LinkedList

Linked List

A Linked List Implementation .

Visualization

        head
           \
            \
    None <- [1st elem] <=> [2nd elem] <=> [3rd elem] ..... <=> [nth elem] -> None
                                                                    \
                                                                     \
                                                                    tail
  • <- : denotes a link to left side
  • -> : denotes a link to right side
  • <=> : denotes a coloated visualisation of link from left and right.
    • <- + -> implies <=>
"""
Linked List
===========
Visualization :
---------------
head
\
\
None <- [1st elem] <=> [2nd elem] <=> [3rd elem] ..... <=> [nth elem] -> None
\
\
tail
`<-` : denotes a link to left side
`->` : denotes a link to right side
`<=>` : denotes a coloated visualisation of link from left and right.
* `<-` + `->` = `<=>`
Notes :
-------
* Every element have `left` and `right` link .
* Hence a link between two elements is double .
* we can traverse from [1st elem] to [nth elem] as well as from [nth elem] to [1st elem] .
"""
import sys
import inspect
import dataclasses
import datetime
from typing import Any, Optional, TypeVar, Dict, Union, Iterable, Generator, Tuple, Type, Self
from collections.abc import Iterator
T = TypeVar('T')
class MemoryBlock:
"""
Represents a memory block in a doubly linked list.
Attributes:
data (Any): The data stored in the memory block.
left (Optional[MemoryBlock]): The left (previous) memory block.
right (Optional[MemoryBlock]): The right (next) memory block.
"""
def __init__(self, data: Any, left: Optional['MemoryBlock'] = None, right: Optional['MemoryBlock'] = None):
"""
Initializes a MemoryBlock instance.
Args:
data (Any): The data to store in the memory block.
left (Optional[MemoryBlock]): The left memory block.
right (Optional[MemoryBlock]): The right memory block.
"""
self._data = data
self._left = left
self._right = right
@property
def data(self) -> 'MemoryBlock':
"""Gets the data stored in the memory block."""
return self._data
@property
def left(self) -> 'MemoryBlock':
"""Gets the left memory block."""
return self._left
@property
def right(self) -> 'MemoryBlock':
"""Gets the right memory block."""
return self._right
@data.setter
def data(self, value: T) -> T:
"""Sets the data in the memory block."""
self._data = value
return self._data
@left.setter
def left(self, value: T) -> T:
"""Sets the left memory block."""
self._left = value
return self._left
@right.setter
def right(self, value: T) -> T:
"""Sets the right memory block."""
self._right = value
return self._right
def __str__(self) -> str:
"""Returns a string representation of the memory block."""
return f"{self._data}"
def __repr__(self):
"""Returns a detailed string representation of the memory block."""
return f"MemoryBlock({self._data}, left={self._left}, right={self._right})"
@dataclasses.dataclass(frozen=True)
class Metadata:
"""
Metadata for a LinkedList Node.
"""
created_at: Optional[str] = datetime.datetime.now()
class LinkedListNode(MemoryBlock):
"""
Represents a node in a doubly linked list.
Attributes:
metadata (Metadata): Metadata associated with the node.
"""
def __init__(self, data, left=None, right=None, metadata: Metadata = Metadata()):
"""
Initializes a LinkedListNode instance.
Args:
data (Any): The data to store in the node.
left (Optional[MemoryBlock]): The left memory block.
right (Optional[MemoryBlock]): The right memory block.
metadata (Metadata): Metadata associated with the node.
"""
self._metadata = metadata
super().__init__(data, left, right)
@property
def metadata(self):
"""Gets the metadata associated with the node."""
return self._metadata
class LRIterator(Iterator):
"""
Iterator for traversing a linked list from left to right.
Attributes:
_pointer (Optional[MemoryBlock]): The current memory block being traversed.
"""
def __init__(self, ll: 'LinkedList'):
"""
Initializes the iterator.
Args:
ll (LinkedList): The linked list to iterate over.
"""
self._pointer: Optional[MemoryBlock] = ll.head.right
def __next__(self):
"""
Returns the next memory block in the iteration.
Raises:
StopIteration: If the end of the list is reached.
"""
if self._pointer:
value = self._pointer
self._pointer = self._pointer.right
return value
else:
raise StopIteration
class RLIterator(Iterator):
"""
Iterator for traversing a linked list from right to left.
Attributes:
_pointer (Optional[MemoryBlock]): The current memory block being traversed.
"""
def __init__(self, ll: 'LinkedList'):
"""
Initializes the iterator.
Args:
ll (LinkedList): The linked list to iterate over.
"""
self._pointer: Optional[MemoryBlock] = ll.tail.left
def __next__(self):
"""
Returns the next memory block in the iteration.
Raises:
StopIteration: If the start of the list is reached.
"""
if self._pointer:
value = self._pointer
self._pointer = self._pointer.left
return value
else:
raise StopIteration
class LinkedList:
"""
Doubly Linked List Implementation
-----
Usage
-----
# Prepare
>>> ll = LinkedList()
>>> ll.append(3)
>>> ll.prepend(4)
>>> ll.append(8)
>>> print(ll)
[4, 3, 8]
# Fetch Node data
>>> value=ll[1]
>>> value.data
3
# Prepend Node
>>> ll.prepend(6)
>>> print(ll)
[6, 4, 3, 8]
# Get Node by Index
>>> value=ll[2]
>>> value.data
3
# Index Map
>>> ll.index_store
{0: MemoryBlock(6, left=None, right=4), 1: MemoryBlock(4, left=6, right=3), 2: MemoryBlock(3, left=4, right=8), 3: MemoryBlock(8, left=3, right=None)}
# Length
>>> len(ll)
4
# Iteration
>>> [v.data for v in ll]
[6, 4, 3, 8]
# Reverse Iteration
# # ( It Sets the order of linkedlist in reverse )
>>> ll.root_iterator_cls=RLIterator
>>> [v.data for v in ll]
[8, 3, 4, 6]
# Reverse Iteration : Method 2
# # - using Operations.reversed(...) function
>>> ll.root_iterator_cls=LRIterator
>>> print([v.data for v in Operations.reversed(ll)])
[8, 3, 4, 6]
# Reverse Iteration : Method 3
# # - using builtin reversed(...) function
>>> ll.root_iterator_cls=LRIterator
>>> print([v.data for v in reversed(ll)])
[8, 3, 4, 6]
---
NOTES
- does not support -ve indices as of now
"""
def __init__(self, root_iterator_cls: Union[str, Type[Iterator]] = 'LRIterator'):
"""
Initializes a LinkedList instance.
Args:
root_iterator_cls (Iterator): The iterator class used for traversal.
"""
# Positioning Markers
self._head = LinkedListNode(-1)
self._tail = LinkedListNode(-1)
# Initialization
self._head._right = self._tail
self._head._left = None
self._tail._left = self._head
self._tail._right = None
# Index Map
self._index_store: Dict[int, LinkedListNode] = {}
# Config
# # Root Iterator
self.root_iterator_cls: Iterator = root_iterator_cls
@property
def head(self) -> LinkedListNode:
"""Gets the head node of the list."""
return self._head
@property
def tail(self) -> LinkedListNode:
"""Gets the tail node of the list."""
return self._tail
@property
def index_store(self) -> LinkedListNode:
"""Gets the index store of the list."""
return self._index_store
def append(self, data: Any):
"""
Adds a new element at the end of the list.
Args:
data (Any): The data to append.
Complexity:
O(1)
"""
# current : last-element <--- Tail
# new : last-element <--- . <--- Tail
last_element: LinkedListNode = self._tail.left
# adjusting indexes
index = self._tail.data
index += 1
# creation
new_node: LinkedListNode = LinkedListNode(data, metadata=Metadata())
self._tail.left = new_node
if last_element is self._head:
# last_element is head
last_element.right = new_node
new_node.left = None
self._head.data += 1
else:
self._joint(last_element, new_node)
# storing adjusted indexes
self._index_store[index] = new_node
self._tail.data = index
def prepend(self, data: Any):
"""
Adds a new element at the beginning of the list.
Args:
data (Any): The data to prepend.
Complexity:
O(N)
"""
# current : Head ---> first-element
# new : Head ---> . ---> first-element
first_element: LinkedListNode = self._head.right
# creation
new_node: LinkedListNode = LinkedListNode(data, metadata=Metadata())
self._head.right = new_node
if first_element is self._tail:
# first_element is tail
self._tail.left = new_node
new_node.right = None
self._head.data += 1
else:
self._joint(new_node, first_element)
# adjusting indexes
index = self._tail.data
index += 1
for idx in range(index, 0, -1):
self._index_store[idx] = self._index_store[idx-1]
else:
self._index_store[0] = new_node
self._tail.data = index
def sort(self):
raise NotImplementedError('...')
def reverse(self):
"""
Reverses a linked list
Complexity:
O(1)
"""
temp = self._head._right
while temp:
current_node = temp
next_node = temp._right
current_node._left, current_node._right = current_node._right, current_node._left
temp = next_node
self._head._right, self._tail._left = self._tail._left, self._head._right
def pop(self):
raise NotImplementedError('...')
def insert(self):
raise NotImplementedError('...')
def index(self):
raise NotImplementedError('...')
def extend(self):
raise NotImplementedError('...')
def count(self):
raise NotImplementedError('...')
def copy(self):
raise NotImplementedError('...')
def clear(self):
raise NotImplementedError('...')
def remove(self):
raise NotImplementedError('...')
def delete(self, node: LinkedListNode):
"""
special functionality
- it saves the time to search the node
"""
raise NotImplementedError('...')
def _joint(self, left_node: LinkedListNode, right_node: LinkedListNode) -> None:
"""
Joins two memory blocks.
Args:
left_node (LinkedListNode): The left memory block.
right_node (LinkedListNode): The right memory block.
"""
left_node.right = right_node
right_node.left = left_node
def __rmul__(self):
raise NotImplementedError('...')
def __mul__(self, value: int):
_ll = LinkedList()
while value:
_pointer = self._head._right
while _pointer:
_ll.append(_pointer._data)
_pointer=_pointer._right
value -= 1
return _ll
def __imul__(self, value: int) -> 'LinkedList':
self_last_node = self._tail.left
while value:
_pointer = self._head._right
while _pointer != self_last_node:
self.append(_pointer.data)
_pointer=_pointer._right
else:
self.append(self_last_node)
value -= 1
return self
def __add__(self, other: 'LinkedList') -> 'LinkedList':
new_ll = LinkedList()
_pointer = self._head._right
while _pointer:
new_ll.append(_pointer.data)
_pointer = _pointer.right
_pointer = other._head._right
while _pointer:
new_ll.append(_pointer.data)
_pointer = _pointer.right
return new_ll
def __iadd__(self, other: 'LinkedList') -> Self:
# first node @ self
self_last_element_pointer = self._tail._left
# last node @ other
other_first_element_pointer = other._head._right
# linking
self_last_element_pointer._right = other_first_element_pointer
other_first_element_pointer.left = self_last_element_pointer
self._tail = other._tail
return self
def __lt__(self, other: 'LinkedList') -> Optional[bool]:
self_pointer = self._head._right
other_pointer = other._head._right
# iterative check for inequality resolution
while self_pointer and other_pointer:
if self_pointer.data < other_pointer.data:
return True
if self_pointer.data > other_pointer.data:
return False
# elements are equal
self_pointer = self_pointer._right
other_pointer = other_pointer.right
# exact equality case
if self_pointer == other_pointer == None:
return False
# shorter length case
if self_pointer == None:
return True
if other_pointer == None:
return False
def __ge__(self, other: 'LinkedList'):
self_pointer = self._head._right
other_pointer = other._head._right
# iterative check for inequality resolution
while self_pointer and other_pointer:
if self_pointer.data > other_pointer.data:
return True
if self_pointer.data < other_pointer.data:
return False
# elements are equal
self_pointer = self_pointer._right
other_pointer = other_pointer.right
# exact equality case
if self_pointer == other_pointer == None:
return True
# shorter length case
if self_pointer == None: # LHS shorter
return False
if other_pointer == None: # RHS shorter
return True
def __gt__(self, other: 'LinkedList'):
self_pointer = self._head._right
other_pointer = other._head._right
# iterative check for inequality resolution
while self_pointer and other_pointer:
if self_pointer.data > other_pointer.data:
return True
if self_pointer.data < other_pointer.data:
return False
# elements are equal
self_pointer = self_pointer._right
other_pointer = other_pointer.right
# exact equality case
if self_pointer == other_pointer == None:
return False
# shorter length case
if self_pointer == None: # LHS shorter
return False
if other_pointer == None: # RHS shorter
return True
def __le__(self, other: 'LinkedList'):
self_pointer = self._head._right
other_pointer = other._head._right
# iterative check for inequality resolution
while self_pointer and other_pointer:
if self_pointer.data < other_pointer.data:
return True
if self_pointer.data > other_pointer.data:
return False
# elements are equal
self_pointer = self_pointer._right
other_pointer = other_pointer.right
# exact equality case
if self_pointer == other_pointer == None:
return True
# shorter length case
if self_pointer == None:
return True
if other_pointer == None:
return False
def __eq__(self, other: 'LinkedList'):
"""
Comapring two linked lists for equlity
"""
self_pointer = self._head._right
other_pointer = other.head.right
while self_pointer and other_pointer:
if self_pointer.data != other_pointer.data:
return False
self_pointer = self_pointer._right
other_pointer = other_pointer.right
if self_pointer == other_pointer == None:
return True
return False
def __ne__(self, other: 'LinkedList'):
return not self.__eq__(other)
def __delitem__(self, index: int):
_pointer = self._head._right
index_counter = 0
while _pointer:
if index_counter == index:
prev_node = _pointer.left
next_node = _pointer.right
# link prev --> next
if prev_node:
prev_node.right = next_node
else: # this means current pointer is first node
self._head.right = next_node
# link next <-- prev
if next_node:
next_node.left = prev_node
else: # this means current pointer is last node
self._tail.left = prev_node
# resume the pointer
_pointer = _pointer.right
index_counter += 1
break # break from here & resume in next loop --- (1)
_pointer = _pointer.right
index_counter += 1
else:
# if loop completes without being able to search a valid index
return
# post node deletion : adjust the indexes in index_store.
# decrement the indexes of remaining nodes --- (1) resuming from prev loop break
while _pointer:
self._index_store[index_counter-1] = _pointer
_pointer = _pointer.right
index_counter += 1
else:
# updating tail with last index value
self._tail._data = index_counter-2
# removing the stale index entry from index_store
self._index_store.pop(index_counter-1, None)
def __contains__(self, value: Union[Any, LinkedListNode]) -> bool:
_pointer = self._head.right
while _pointer:
if (isinstance(value, LinkedListNode) and _pointer.data == value.data) or _pointer.data == value:
return True
_pointer = _pointer.right
return False
def __getitem__(self, key):
"""
Gets an item from the linked list by index or slice.
Args:
key (int or slice): The index or slice to retrieve.
Returns:
LinkedListNode or LinkedList: The node or sublist corresponding to the key.
Raises:
TypeError: If the key is not an integer or slice.
"""
if isinstance(key, int):
# Handle integer indexing
return self._index_store[key]
elif isinstance(key, slice):
# Handle slicing
_ll = LinkedList()
step = key.step if key.step is not None else 1
stop = key.stop if key.stop and 0 <= key.stop < len(self._index_store) else len(self._index_store)
start = key.start if key.start and 0 <= key.start < len(self._index_store) else 0
for idx in range(start, stop, step):
obj = self._index_store[idx]
_ll.append(obj.data)
return _ll
else:
raise TypeError("Invalid key type")
def __setitem__(self):
raise NotImplementedError('...')
def __iter__(self):
"""
Returns an iterator for the linked list.
Returns:
Iterator: The iterator for the linked list.
"""
if inspect.isclass(self.root_iterator_cls):
return self.root_iterator_cls(self)
else:
# memory efficient approach
if self.root_iterator_cls == 'LRIterator':
return LRIterator(self)
if self.root_iterator_cls == 'RLIterator':
return RLIterator(self)
raise NotImplementedError('root_iterator_cls specified isn`t supported yet')
def __len__(self):
"""
Returns the length of the linked list.
Returns:
int: The length of the linked list.
"""
return self._tail.data - self._head.data + 1
def __sizeof__(self):
# Calculate the size of all nodes
all_nodes_size = sys.getsizeof(self._head) + sys.getsizeof(self._tail)
for node in iter(self):
all_nodes_size += sys.getsizeof(node)
# Calculate the size of the idex store
index_store_size = sys.getsizeof(self._index_store)
# Calculate the size of the iterator class reference
iterator_class_ref_size = sys.getsizeof(self.root_iterator_cls)
# Add the size of the object itself (base object size)
# This is typically the size of an empty instance of the class
base_object_size = super().__sizeof__()
return base_object_size + iterator_class_ref_size + index_store_size + all_nodes_size
def print_detailed_size_information(self):
# Calculate the size of all nodes
all_nodes_size = sys.getsizeof(self._head) + sys.getsizeof(self._tail)
for node in iter(self):
all_nodes_size += sys.getsizeof(node)
# Calculate the size of the idex store
index_store_size = sys.getsizeof(self._index_store)
# Calculate the size of the iterator class reference
iterator_class_ref_size = sys.getsizeof(self.root_iterator_cls)
# Add the size of the object itself (base object size)
# This is typically the size of an empty instance of the class
base_object_size = super().__sizeof__()
total = base_object_size + iterator_class_ref_size + index_store_size + all_nodes_size
percent = lambda field: field/total*100
cols = ['base_object_size', 'iterator_class_ref_size', 'index_store_size', 'all_nodes_size']
data = [
cols,
[locals()[v_name] for v_name in cols] + ['bytes'],
[percent(locals()[v_name]) for v_name in cols] + ['%']
]
# Using f-strings (Python 3.6+)
header = f"| {data[0][0]:<10} | {data[0][1]:^5} | {data[0][2]:>10} | {data[0][3]:>10} |"
print("-" * len(header))
print(header)
print("-" * len(header))
for row in data[1:]:
print(f"| {row[0]:^16.2f} | {row[1]:^23.2f} | {row[2]:^16.2f} | {row[3]:^14.2f} | {row[4]:^16} | ")
print("-" * len(header))
print(f"* Total Size : {total}")
print("\n")
def __reversed__(self):
"""
Returns a reversed iterator for the linked list.
Returns:
Iterator: The reversed iterator for the linked list.
"""
if self.root_iterator_cls == RLIterator:
for pointer in LRIterator(self):
yield pointer
else:
for pointer in RLIterator(self):
yield pointer
def __str__(self) -> str:
"""
Returns a string representation of the linked list.
Returns:
str: The string representation of the linked list.
"""
return f"[{', '.join([str(node.data) for node in iter(self)]).strip(',')}]"
class Operations:
"""
Utility class for performing operations on linked lists.
"""
@staticmethod
def reversed(ll: LinkedList, method=1) -> Generator[LinkedListNode, None, None]:
"""
Generates linked list nodes in reverse order.
Args:
ll (LinkedList): The linked list to reverse.
method (int): The method to use for reversal (1 or 2).
Returns:
Generator[LinkedListNode, None, None]: A generator for the reversed nodes.
Complexity:
O(N)
"""
if not issubclass(ll.root_iterator_cls, Iterator):
raise Exception('must implement typing.Iterator class')
if ll.root_iterator_cls == LRIterator:
if method == 1:
pointer = ll.tail.left
while pointer:
yield pointer
pointer = pointer.left
elif method == 2:
for pointer in RLIterator(ll):
yield pointer
elif ll.root_iterator_cls == RLIterator:
if method == 1:
pointer = ll.head.right
while pointer:
yield pointer
pointer = pointer.right
elif method == 2:
for pointer in RLIterator(ll):
yield pointer
else:
raise NotImplementedError('`root_iterator_cls` not specified')
@staticmethod
def enumerate(ll: Union[LinkedList, Generator[LinkedListNode, None, None]] ) -> Generator[Tuple[int, LinkedListNode], None, None]:
"""
Enumerates the nodes in the linked list.
Args:
ll (LinkedList or Generator): The linked list or generator to enumerate.
Returns:
Generator[Tuple[int, LinkedListNode], None, None]: A generator for the enumerated nodes.
"""
_iterator: Iterator = iter(ll)
index_counter = 0
for pointer in _iterator:
yield (index_counter, pointer)
index_counter += 1
@staticmethod
def ll_from(obj: Iterable) -> LinkedList:
"""
Converts an iterable to a linked list.
Args:
obj (Iterable): The iterable to convert.
Returns:
LinkedList: The resulting linked list.
"""
_ll = LinkedList()
for value in obj:
_ll.append(value)
return _ll
  • A Python List Implements for following dunder methods that give it's the features that we see in real life with python:lists

  • def __rmul__(self): ...

  • def __mul__(self, value: int): ...

  • def __imul__(self, value: int) -> 'LinkedList': ...

  • def __add__(self, other: 'LinkedList') -> 'LinkedList': ...

  • def __iadd__(self, other: 'LinkedList') -> Self: ...

  • def __lt__(self, other: 'LinkedList') -> Optional[bool]: ...

  • def __ge__(self, other: 'LinkedList'): ...

  • def __gt__(self, other: 'LinkedList'): ...

  • def __le__(self, other: 'LinkedList'): ...

  • def __eq__(self, other: 'LinkedList'): ...

  • def __ne__(self, other: 'LinkedList'): ...

  • def __delitem__(self, index: int): ...

  • def __contains__(self, value: Union[Any, LinkedListNode]) -> bool:

  • def __getitem__(self, key): ...

  • def __setitem__(self): ...

  • def __iter__(self) -> Iterator: ...

  • def __len__(self) -> int: ...

  • def __sizeof__(self) -> int: ...

    • Returns total size of linked list
  • def print_detailed_size_information(self): ...

    • Prints in detail - the size information of linked list
  • def __reversed__(self): ...

    • Reverses a linked list nodes
  • def __str__(self) -> str: ...

    • Returns a string representation of the linked list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment