Implementation of Singly Linked List
up vote
4
down vote
favorite
I'm a data scientist attempting to build stronger CS fundamentals, particularly in Data Structures & Algorithms.
Below is my attempt at implementing a Singly Linked List. Looking for advice regarding:
- Code style
- Can space/time complexity of methods be improved?
- Debugging in case I missed any edge cases
- Checking for premature optimizations
Note this is an edit from the original post: Linked List implementation in Python
Have made the following edits to the code according to the answer by @AJNeufeld:
- Made
Nodeclass & its attributes private - Added
__slots__to save memory - Added docstrings for all methods - including one for class that explains the assumed structure and indexing
- Removed unnecessary attribute
self._args
- Added private helper methods:
_is_head(self, index)_is_tail(self, index)_get_values(self)
- Made
__repr__(self)in accordance with Python Standard - Added
__str__(self)to represent "Linking values" - Added iteration protocol
- I think the way I built it is
O(n^2)and wondering if there is a faster implementation? - I did not account for the container size changing through iteration? Any hints on this?
- Also is anything ever put in the
__iter__(self)method, or should it just trivially return the object?
- I think the way I built it is
Please find below the new implementation
class Linkedlist:
"""A linear container data structure that provides O(1) time insertion/deletion of items
to/from the head and tail of the sequence of values.
Utilizes _Node object as underlying data structure
----------------------------------------------------
Structure of class is as follows:
Index 0: 1st Node <- Head Node
Index 1: 2nd Node
Index 2: 3rd Node
...
Index n - 1: Nth Node <- Tail Node
----------------------------------------------------
Methods:
1). __getitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
2). __delitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
3). __iter__(self)
Time Complexity: O(n^2)
Space Complexity: O(1)
4). __repr__(self)
Time Complexity: O(n)
Space Complexity: O(n)
5). __str__(self)
Time Complexity: O(n)
Space Complexity: O(n)
6). append(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
7). prepend(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
8). insert(self, value, index)
Time Complexity: O(n)
Space Complexity: O(n)
"""
class _Node:
"""Data structure used to implement Linked List - has fields:
1. Data value
2. Pointer to next node
"""
def __init__(self, value=None):
__slots__ = ('_value', '_next')
self._value = value
self._next = None
def __init__(self, *args):
self.head = self._Node()
self.tail = self.head
self._size = 0
self._iter_counter = 1
for val in args:
self.append(val)
def __len__(self):
"""Returns number of non-empty nodes in Linked List"""
return self._size
def _get_prev_node(self, index):
"""helper method to obtain Node previous to given index in O(n) time
i.e. if index is 1, will return 1st Node
i.e. if size of linked list is 6 & index is -3, will return 4th Node
"""
if index < 0:
index += self._size
cur_node = self.head
prev_node_number = 1
while prev_node_number < index:
cur_node = cur_node._next
prev_node_number += 1
return cur_node
def _is_head(self, index):
"""Helper method to determine if given index is head node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == 0 or index == -self._size
def _is_tail(self, index):
"""Helper method to determine if given index is tail node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == -1 or index == self._size - 1
def _get_values(self):
"""Helper method to generate string values of all node values"""
cur_node = self.head
for _ in range(self._size):
yield str(cur_node._value)
cur_node = cur_node._next
def __getitem__(self, index):
"""Getter method to obtain value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
return self.head._value
else:
prev_node = self._get_prev_node(index)
cur_node = prev_node._next
return cur_node._value
def __delitem__(self, index):
"""Method to delete value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
self.head = self.head._next
else:
prev_node = self._get_prev_node(index)
prev_node._next = prev_node._next._next
if self._is_tail(index):
self.tail = prev_node
self._size -= 1
def __iter__(self):
"""Returns iterator object which user can iterate through"""
return self
def __next__(self):
"""Loops through iterator returning each Node value"""
# TODO See if there's a way to improve iteration speed from quadratic to linear
cur_node = self.head
if self._iter_counter > self._size:
self._iter_counter = 1
raise StopIteration
prev_node = self._get_prev_node(self._iter_counter)
self._iter_counter += 1
return prev_node._value
def __repr__(self):
"""Provides valid Python expression that can be used to recreate an object with the same value"""
values = ', '.join(self._get_values())
cls_name = type(self).__name__
return f'{cls_name}({values})'
def __str__(self):
"""Displays printable representation of Linked List"""
return ' -> '.join(self._get_values())
def append(self, value):
"""Inserts node with given value to end of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
self.tail._next = new_node
self.tail = new_node
self._size += 1
def prepend(self, value):
"""Inserts node with given value to front of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
new_node._next = self.head
self.head = new_node
self._size += 1
def insert(self, value, index):
"""Inserts node with given value at a given index of Linked List in O(n) time.
If insertion occurs at head or tail of Linked List, operation becomes O(1).
n := len(self)
* Index must be in interval [-n, n]
"""
if abs(index) > self._size:
raise IndexError
elif self._is_head(index):
self.prepend(value)
elif index == self._size:
self.append(value)
else:
prev_node = self._get_prev_node(index)
new_node = self._Node(value)
new_node._next = prev_node._next
prev_node._next = new_node
self._size += 1
def main():
def disp_attributes(lnk_list_obj):
print(f'Linked List: {lnk_list_obj}')
print(f'tSize: {len(lnk_list_obj)}')
print(f'tHead node value: {lnk_list_obj.head._value}')
print(f'tTail node value: {lnk_list_obj.tail._value}')
print('<< Instantiate empty Linked List >>')
lnk = Linkedlist()
disp_attributes(lnk)
print('<< Append -3, 1, 0 to Linked List >>')
values = -3, 1, 0
for val in values:
lnk.append(val)
disp_attributes(lnk)
print('<< Prepend -12 to Linked List >>')
lnk.prepend(-12)
disp_attributes(lnk)
print(f'Linked List value at first Node: {lnk[0]}')
print('<< Instantiate Linked List with values 1, -2, -6, 0, 2 >>')
lnk2 = Linkedlist(1, -2, -6, 0, 2)
disp_attributes(lnk2)
print('<< Prepend 6 to Linked List >>')
lnk2.prepend(6)
disp_attributes(lnk2)
print(f'Linked List value at second Node: {lnk2[1]}')
print('<< Delete 1st Node >>')
del lnk2[0]
disp_attributes(lnk2)
print('<< Delete Last Node >>')
del lnk2[-1]
disp_attributes(lnk2)
print('<< Append 7 to LinkedList >>')
lnk2.append(7)
disp_attributes(lnk2)
print('<< Delete 3rd Node >>')
del lnk2[2]
disp_attributes(lnk2)
print('<< Insert -10 before 2nd Node >>')
lnk2.insert(-10, 1)
disp_attributes(lnk2)
if __name__ == '__main__':
main()
python object-oriented python-3.x linked-list
bumped to the homepage by Community♦ 3 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
up vote
4
down vote
favorite
I'm a data scientist attempting to build stronger CS fundamentals, particularly in Data Structures & Algorithms.
Below is my attempt at implementing a Singly Linked List. Looking for advice regarding:
- Code style
- Can space/time complexity of methods be improved?
- Debugging in case I missed any edge cases
- Checking for premature optimizations
Note this is an edit from the original post: Linked List implementation in Python
Have made the following edits to the code according to the answer by @AJNeufeld:
- Made
Nodeclass & its attributes private - Added
__slots__to save memory - Added docstrings for all methods - including one for class that explains the assumed structure and indexing
- Removed unnecessary attribute
self._args
- Added private helper methods:
_is_head(self, index)_is_tail(self, index)_get_values(self)
- Made
__repr__(self)in accordance with Python Standard - Added
__str__(self)to represent "Linking values" - Added iteration protocol
- I think the way I built it is
O(n^2)and wondering if there is a faster implementation? - I did not account for the container size changing through iteration? Any hints on this?
- Also is anything ever put in the
__iter__(self)method, or should it just trivially return the object?
- I think the way I built it is
Please find below the new implementation
class Linkedlist:
"""A linear container data structure that provides O(1) time insertion/deletion of items
to/from the head and tail of the sequence of values.
Utilizes _Node object as underlying data structure
----------------------------------------------------
Structure of class is as follows:
Index 0: 1st Node <- Head Node
Index 1: 2nd Node
Index 2: 3rd Node
...
Index n - 1: Nth Node <- Tail Node
----------------------------------------------------
Methods:
1). __getitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
2). __delitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
3). __iter__(self)
Time Complexity: O(n^2)
Space Complexity: O(1)
4). __repr__(self)
Time Complexity: O(n)
Space Complexity: O(n)
5). __str__(self)
Time Complexity: O(n)
Space Complexity: O(n)
6). append(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
7). prepend(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
8). insert(self, value, index)
Time Complexity: O(n)
Space Complexity: O(n)
"""
class _Node:
"""Data structure used to implement Linked List - has fields:
1. Data value
2. Pointer to next node
"""
def __init__(self, value=None):
__slots__ = ('_value', '_next')
self._value = value
self._next = None
def __init__(self, *args):
self.head = self._Node()
self.tail = self.head
self._size = 0
self._iter_counter = 1
for val in args:
self.append(val)
def __len__(self):
"""Returns number of non-empty nodes in Linked List"""
return self._size
def _get_prev_node(self, index):
"""helper method to obtain Node previous to given index in O(n) time
i.e. if index is 1, will return 1st Node
i.e. if size of linked list is 6 & index is -3, will return 4th Node
"""
if index < 0:
index += self._size
cur_node = self.head
prev_node_number = 1
while prev_node_number < index:
cur_node = cur_node._next
prev_node_number += 1
return cur_node
def _is_head(self, index):
"""Helper method to determine if given index is head node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == 0 or index == -self._size
def _is_tail(self, index):
"""Helper method to determine if given index is tail node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == -1 or index == self._size - 1
def _get_values(self):
"""Helper method to generate string values of all node values"""
cur_node = self.head
for _ in range(self._size):
yield str(cur_node._value)
cur_node = cur_node._next
def __getitem__(self, index):
"""Getter method to obtain value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
return self.head._value
else:
prev_node = self._get_prev_node(index)
cur_node = prev_node._next
return cur_node._value
def __delitem__(self, index):
"""Method to delete value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
self.head = self.head._next
else:
prev_node = self._get_prev_node(index)
prev_node._next = prev_node._next._next
if self._is_tail(index):
self.tail = prev_node
self._size -= 1
def __iter__(self):
"""Returns iterator object which user can iterate through"""
return self
def __next__(self):
"""Loops through iterator returning each Node value"""
# TODO See if there's a way to improve iteration speed from quadratic to linear
cur_node = self.head
if self._iter_counter > self._size:
self._iter_counter = 1
raise StopIteration
prev_node = self._get_prev_node(self._iter_counter)
self._iter_counter += 1
return prev_node._value
def __repr__(self):
"""Provides valid Python expression that can be used to recreate an object with the same value"""
values = ', '.join(self._get_values())
cls_name = type(self).__name__
return f'{cls_name}({values})'
def __str__(self):
"""Displays printable representation of Linked List"""
return ' -> '.join(self._get_values())
def append(self, value):
"""Inserts node with given value to end of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
self.tail._next = new_node
self.tail = new_node
self._size += 1
def prepend(self, value):
"""Inserts node with given value to front of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
new_node._next = self.head
self.head = new_node
self._size += 1
def insert(self, value, index):
"""Inserts node with given value at a given index of Linked List in O(n) time.
If insertion occurs at head or tail of Linked List, operation becomes O(1).
n := len(self)
* Index must be in interval [-n, n]
"""
if abs(index) > self._size:
raise IndexError
elif self._is_head(index):
self.prepend(value)
elif index == self._size:
self.append(value)
else:
prev_node = self._get_prev_node(index)
new_node = self._Node(value)
new_node._next = prev_node._next
prev_node._next = new_node
self._size += 1
def main():
def disp_attributes(lnk_list_obj):
print(f'Linked List: {lnk_list_obj}')
print(f'tSize: {len(lnk_list_obj)}')
print(f'tHead node value: {lnk_list_obj.head._value}')
print(f'tTail node value: {lnk_list_obj.tail._value}')
print('<< Instantiate empty Linked List >>')
lnk = Linkedlist()
disp_attributes(lnk)
print('<< Append -3, 1, 0 to Linked List >>')
values = -3, 1, 0
for val in values:
lnk.append(val)
disp_attributes(lnk)
print('<< Prepend -12 to Linked List >>')
lnk.prepend(-12)
disp_attributes(lnk)
print(f'Linked List value at first Node: {lnk[0]}')
print('<< Instantiate Linked List with values 1, -2, -6, 0, 2 >>')
lnk2 = Linkedlist(1, -2, -6, 0, 2)
disp_attributes(lnk2)
print('<< Prepend 6 to Linked List >>')
lnk2.prepend(6)
disp_attributes(lnk2)
print(f'Linked List value at second Node: {lnk2[1]}')
print('<< Delete 1st Node >>')
del lnk2[0]
disp_attributes(lnk2)
print('<< Delete Last Node >>')
del lnk2[-1]
disp_attributes(lnk2)
print('<< Append 7 to LinkedList >>')
lnk2.append(7)
disp_attributes(lnk2)
print('<< Delete 3rd Node >>')
del lnk2[2]
disp_attributes(lnk2)
print('<< Insert -10 before 2nd Node >>')
lnk2.insert(-10, 1)
disp_attributes(lnk2)
if __name__ == '__main__':
main()
python object-oriented python-3.x linked-list
bumped to the homepage by Community♦ 3 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
up vote
4
down vote
favorite
up vote
4
down vote
favorite
I'm a data scientist attempting to build stronger CS fundamentals, particularly in Data Structures & Algorithms.
Below is my attempt at implementing a Singly Linked List. Looking for advice regarding:
- Code style
- Can space/time complexity of methods be improved?
- Debugging in case I missed any edge cases
- Checking for premature optimizations
Note this is an edit from the original post: Linked List implementation in Python
Have made the following edits to the code according to the answer by @AJNeufeld:
- Made
Nodeclass & its attributes private - Added
__slots__to save memory - Added docstrings for all methods - including one for class that explains the assumed structure and indexing
- Removed unnecessary attribute
self._args
- Added private helper methods:
_is_head(self, index)_is_tail(self, index)_get_values(self)
- Made
__repr__(self)in accordance with Python Standard - Added
__str__(self)to represent "Linking values" - Added iteration protocol
- I think the way I built it is
O(n^2)and wondering if there is a faster implementation? - I did not account for the container size changing through iteration? Any hints on this?
- Also is anything ever put in the
__iter__(self)method, or should it just trivially return the object?
- I think the way I built it is
Please find below the new implementation
class Linkedlist:
"""A linear container data structure that provides O(1) time insertion/deletion of items
to/from the head and tail of the sequence of values.
Utilizes _Node object as underlying data structure
----------------------------------------------------
Structure of class is as follows:
Index 0: 1st Node <- Head Node
Index 1: 2nd Node
Index 2: 3rd Node
...
Index n - 1: Nth Node <- Tail Node
----------------------------------------------------
Methods:
1). __getitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
2). __delitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
3). __iter__(self)
Time Complexity: O(n^2)
Space Complexity: O(1)
4). __repr__(self)
Time Complexity: O(n)
Space Complexity: O(n)
5). __str__(self)
Time Complexity: O(n)
Space Complexity: O(n)
6). append(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
7). prepend(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
8). insert(self, value, index)
Time Complexity: O(n)
Space Complexity: O(n)
"""
class _Node:
"""Data structure used to implement Linked List - has fields:
1. Data value
2. Pointer to next node
"""
def __init__(self, value=None):
__slots__ = ('_value', '_next')
self._value = value
self._next = None
def __init__(self, *args):
self.head = self._Node()
self.tail = self.head
self._size = 0
self._iter_counter = 1
for val in args:
self.append(val)
def __len__(self):
"""Returns number of non-empty nodes in Linked List"""
return self._size
def _get_prev_node(self, index):
"""helper method to obtain Node previous to given index in O(n) time
i.e. if index is 1, will return 1st Node
i.e. if size of linked list is 6 & index is -3, will return 4th Node
"""
if index < 0:
index += self._size
cur_node = self.head
prev_node_number = 1
while prev_node_number < index:
cur_node = cur_node._next
prev_node_number += 1
return cur_node
def _is_head(self, index):
"""Helper method to determine if given index is head node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == 0 or index == -self._size
def _is_tail(self, index):
"""Helper method to determine if given index is tail node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == -1 or index == self._size - 1
def _get_values(self):
"""Helper method to generate string values of all node values"""
cur_node = self.head
for _ in range(self._size):
yield str(cur_node._value)
cur_node = cur_node._next
def __getitem__(self, index):
"""Getter method to obtain value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
return self.head._value
else:
prev_node = self._get_prev_node(index)
cur_node = prev_node._next
return cur_node._value
def __delitem__(self, index):
"""Method to delete value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
self.head = self.head._next
else:
prev_node = self._get_prev_node(index)
prev_node._next = prev_node._next._next
if self._is_tail(index):
self.tail = prev_node
self._size -= 1
def __iter__(self):
"""Returns iterator object which user can iterate through"""
return self
def __next__(self):
"""Loops through iterator returning each Node value"""
# TODO See if there's a way to improve iteration speed from quadratic to linear
cur_node = self.head
if self._iter_counter > self._size:
self._iter_counter = 1
raise StopIteration
prev_node = self._get_prev_node(self._iter_counter)
self._iter_counter += 1
return prev_node._value
def __repr__(self):
"""Provides valid Python expression that can be used to recreate an object with the same value"""
values = ', '.join(self._get_values())
cls_name = type(self).__name__
return f'{cls_name}({values})'
def __str__(self):
"""Displays printable representation of Linked List"""
return ' -> '.join(self._get_values())
def append(self, value):
"""Inserts node with given value to end of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
self.tail._next = new_node
self.tail = new_node
self._size += 1
def prepend(self, value):
"""Inserts node with given value to front of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
new_node._next = self.head
self.head = new_node
self._size += 1
def insert(self, value, index):
"""Inserts node with given value at a given index of Linked List in O(n) time.
If insertion occurs at head or tail of Linked List, operation becomes O(1).
n := len(self)
* Index must be in interval [-n, n]
"""
if abs(index) > self._size:
raise IndexError
elif self._is_head(index):
self.prepend(value)
elif index == self._size:
self.append(value)
else:
prev_node = self._get_prev_node(index)
new_node = self._Node(value)
new_node._next = prev_node._next
prev_node._next = new_node
self._size += 1
def main():
def disp_attributes(lnk_list_obj):
print(f'Linked List: {lnk_list_obj}')
print(f'tSize: {len(lnk_list_obj)}')
print(f'tHead node value: {lnk_list_obj.head._value}')
print(f'tTail node value: {lnk_list_obj.tail._value}')
print('<< Instantiate empty Linked List >>')
lnk = Linkedlist()
disp_attributes(lnk)
print('<< Append -3, 1, 0 to Linked List >>')
values = -3, 1, 0
for val in values:
lnk.append(val)
disp_attributes(lnk)
print('<< Prepend -12 to Linked List >>')
lnk.prepend(-12)
disp_attributes(lnk)
print(f'Linked List value at first Node: {lnk[0]}')
print('<< Instantiate Linked List with values 1, -2, -6, 0, 2 >>')
lnk2 = Linkedlist(1, -2, -6, 0, 2)
disp_attributes(lnk2)
print('<< Prepend 6 to Linked List >>')
lnk2.prepend(6)
disp_attributes(lnk2)
print(f'Linked List value at second Node: {lnk2[1]}')
print('<< Delete 1st Node >>')
del lnk2[0]
disp_attributes(lnk2)
print('<< Delete Last Node >>')
del lnk2[-1]
disp_attributes(lnk2)
print('<< Append 7 to LinkedList >>')
lnk2.append(7)
disp_attributes(lnk2)
print('<< Delete 3rd Node >>')
del lnk2[2]
disp_attributes(lnk2)
print('<< Insert -10 before 2nd Node >>')
lnk2.insert(-10, 1)
disp_attributes(lnk2)
if __name__ == '__main__':
main()
python object-oriented python-3.x linked-list
I'm a data scientist attempting to build stronger CS fundamentals, particularly in Data Structures & Algorithms.
Below is my attempt at implementing a Singly Linked List. Looking for advice regarding:
- Code style
- Can space/time complexity of methods be improved?
- Debugging in case I missed any edge cases
- Checking for premature optimizations
Note this is an edit from the original post: Linked List implementation in Python
Have made the following edits to the code according to the answer by @AJNeufeld:
- Made
Nodeclass & its attributes private - Added
__slots__to save memory - Added docstrings for all methods - including one for class that explains the assumed structure and indexing
- Removed unnecessary attribute
self._args
- Added private helper methods:
_is_head(self, index)_is_tail(self, index)_get_values(self)
- Made
__repr__(self)in accordance with Python Standard - Added
__str__(self)to represent "Linking values" - Added iteration protocol
- I think the way I built it is
O(n^2)and wondering if there is a faster implementation? - I did not account for the container size changing through iteration? Any hints on this?
- Also is anything ever put in the
__iter__(self)method, or should it just trivially return the object?
- I think the way I built it is
Please find below the new implementation
class Linkedlist:
"""A linear container data structure that provides O(1) time insertion/deletion of items
to/from the head and tail of the sequence of values.
Utilizes _Node object as underlying data structure
----------------------------------------------------
Structure of class is as follows:
Index 0: 1st Node <- Head Node
Index 1: 2nd Node
Index 2: 3rd Node
...
Index n - 1: Nth Node <- Tail Node
----------------------------------------------------
Methods:
1). __getitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
2). __delitem__(self, index)
Time Complexity: O(n)
Space Complexity: O(1)
3). __iter__(self)
Time Complexity: O(n^2)
Space Complexity: O(1)
4). __repr__(self)
Time Complexity: O(n)
Space Complexity: O(n)
5). __str__(self)
Time Complexity: O(n)
Space Complexity: O(n)
6). append(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
7). prepend(self, value)
Time Complexity: O(1)
Space Complexity: O(1)
8). insert(self, value, index)
Time Complexity: O(n)
Space Complexity: O(n)
"""
class _Node:
"""Data structure used to implement Linked List - has fields:
1. Data value
2. Pointer to next node
"""
def __init__(self, value=None):
__slots__ = ('_value', '_next')
self._value = value
self._next = None
def __init__(self, *args):
self.head = self._Node()
self.tail = self.head
self._size = 0
self._iter_counter = 1
for val in args:
self.append(val)
def __len__(self):
"""Returns number of non-empty nodes in Linked List"""
return self._size
def _get_prev_node(self, index):
"""helper method to obtain Node previous to given index in O(n) time
i.e. if index is 1, will return 1st Node
i.e. if size of linked list is 6 & index is -3, will return 4th Node
"""
if index < 0:
index += self._size
cur_node = self.head
prev_node_number = 1
while prev_node_number < index:
cur_node = cur_node._next
prev_node_number += 1
return cur_node
def _is_head(self, index):
"""Helper method to determine if given index is head node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == 0 or index == -self._size
def _is_tail(self, index):
"""Helper method to determine if given index is tail node"""
if index >= self._size or index < -self._size:
raise IndexError
return index == -1 or index == self._size - 1
def _get_values(self):
"""Helper method to generate string values of all node values"""
cur_node = self.head
for _ in range(self._size):
yield str(cur_node._value)
cur_node = cur_node._next
def __getitem__(self, index):
"""Getter method to obtain value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
return self.head._value
else:
prev_node = self._get_prev_node(index)
cur_node = prev_node._next
return cur_node._value
def __delitem__(self, index):
"""Method to delete value of a node at given index in O(1) time - this is considering that finding the node is encapsulated by helper method self._get_prev_node(index)
"""
if self._is_head(index):
self.head = self.head._next
else:
prev_node = self._get_prev_node(index)
prev_node._next = prev_node._next._next
if self._is_tail(index):
self.tail = prev_node
self._size -= 1
def __iter__(self):
"""Returns iterator object which user can iterate through"""
return self
def __next__(self):
"""Loops through iterator returning each Node value"""
# TODO See if there's a way to improve iteration speed from quadratic to linear
cur_node = self.head
if self._iter_counter > self._size:
self._iter_counter = 1
raise StopIteration
prev_node = self._get_prev_node(self._iter_counter)
self._iter_counter += 1
return prev_node._value
def __repr__(self):
"""Provides valid Python expression that can be used to recreate an object with the same value"""
values = ', '.join(self._get_values())
cls_name = type(self).__name__
return f'{cls_name}({values})'
def __str__(self):
"""Displays printable representation of Linked List"""
return ' -> '.join(self._get_values())
def append(self, value):
"""Inserts node with given value to end of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
self.tail._next = new_node
self.tail = new_node
self._size += 1
def prepend(self, value):
"""Inserts node with given value to front of Linked List in O(1) time"""
if self.head._value is None:
self.head._value = value
else:
new_node = self._Node(value)
new_node._next = self.head
self.head = new_node
self._size += 1
def insert(self, value, index):
"""Inserts node with given value at a given index of Linked List in O(n) time.
If insertion occurs at head or tail of Linked List, operation becomes O(1).
n := len(self)
* Index must be in interval [-n, n]
"""
if abs(index) > self._size:
raise IndexError
elif self._is_head(index):
self.prepend(value)
elif index == self._size:
self.append(value)
else:
prev_node = self._get_prev_node(index)
new_node = self._Node(value)
new_node._next = prev_node._next
prev_node._next = new_node
self._size += 1
def main():
def disp_attributes(lnk_list_obj):
print(f'Linked List: {lnk_list_obj}')
print(f'tSize: {len(lnk_list_obj)}')
print(f'tHead node value: {lnk_list_obj.head._value}')
print(f'tTail node value: {lnk_list_obj.tail._value}')
print('<< Instantiate empty Linked List >>')
lnk = Linkedlist()
disp_attributes(lnk)
print('<< Append -3, 1, 0 to Linked List >>')
values = -3, 1, 0
for val in values:
lnk.append(val)
disp_attributes(lnk)
print('<< Prepend -12 to Linked List >>')
lnk.prepend(-12)
disp_attributes(lnk)
print(f'Linked List value at first Node: {lnk[0]}')
print('<< Instantiate Linked List with values 1, -2, -6, 0, 2 >>')
lnk2 = Linkedlist(1, -2, -6, 0, 2)
disp_attributes(lnk2)
print('<< Prepend 6 to Linked List >>')
lnk2.prepend(6)
disp_attributes(lnk2)
print(f'Linked List value at second Node: {lnk2[1]}')
print('<< Delete 1st Node >>')
del lnk2[0]
disp_attributes(lnk2)
print('<< Delete Last Node >>')
del lnk2[-1]
disp_attributes(lnk2)
print('<< Append 7 to LinkedList >>')
lnk2.append(7)
disp_attributes(lnk2)
print('<< Delete 3rd Node >>')
del lnk2[2]
disp_attributes(lnk2)
print('<< Insert -10 before 2nd Node >>')
lnk2.insert(-10, 1)
disp_attributes(lnk2)
if __name__ == '__main__':
main()
python object-oriented python-3.x linked-list
python object-oriented python-3.x linked-list
edited Nov 8 at 9:22
Peilonrayz
25.1k336105
25.1k336105
asked Nov 8 at 7:25
Vivek Jha
1024
1024
bumped to the homepage by Community♦ 3 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
bumped to the homepage by Community♦ 3 hours ago
This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
up vote
0
down vote
I think you’ve misunderstood the purpose of """docstrings""". If someone using your code types help(LinkedList), they will get the contents of the class’s docstring and the public member docstrings, formatted together into one long output string describing (ideally) how to use the class and its member functions. The internal details of how the class works should not be included.
Moreover, the class docstring should not repeat the information given in a member function. For example, you don’t need to document LinkedList.append in the LinkedList docstring, because help(LinkedList) automatically also output the help for help(LinkedList.append).
Yes, your __iter__ implementation is $O(n^2)$. Instead of returning an iterator Object, you’ve returned the original list, and made the original list implement the iterator protocol. This means you cannot have two iterators going at the same time. Also, you cannot iterate halfway through the list, stop and then start a new iteration from the beginning.
You should either create a new class LinkedList._Iter object (which implements the iterator protocol) and return that from __iter__ or, use the same technique you used in _get_values() and return a generator that yields successive values from the list. In either case, these returned objects/generators would be independent; you could have multiple iterations running separately, and/or abandoned halfway through iteration without messing up future iterations.
Edge case:
- start with empty list: head = tail = empty-node
- append one item: head = tail = node-with-value
- delete element 0: head =
None; tail = node-with-value - every subsequent operation (other than
len()) will now raise an exception due to referencing an attribute ofself.head, which is nowNone.
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
I think you’ve misunderstood the purpose of """docstrings""". If someone using your code types help(LinkedList), they will get the contents of the class’s docstring and the public member docstrings, formatted together into one long output string describing (ideally) how to use the class and its member functions. The internal details of how the class works should not be included.
Moreover, the class docstring should not repeat the information given in a member function. For example, you don’t need to document LinkedList.append in the LinkedList docstring, because help(LinkedList) automatically also output the help for help(LinkedList.append).
Yes, your __iter__ implementation is $O(n^2)$. Instead of returning an iterator Object, you’ve returned the original list, and made the original list implement the iterator protocol. This means you cannot have two iterators going at the same time. Also, you cannot iterate halfway through the list, stop and then start a new iteration from the beginning.
You should either create a new class LinkedList._Iter object (which implements the iterator protocol) and return that from __iter__ or, use the same technique you used in _get_values() and return a generator that yields successive values from the list. In either case, these returned objects/generators would be independent; you could have multiple iterations running separately, and/or abandoned halfway through iteration without messing up future iterations.
Edge case:
- start with empty list: head = tail = empty-node
- append one item: head = tail = node-with-value
- delete element 0: head =
None; tail = node-with-value - every subsequent operation (other than
len()) will now raise an exception due to referencing an attribute ofself.head, which is nowNone.
add a comment |
up vote
0
down vote
I think you’ve misunderstood the purpose of """docstrings""". If someone using your code types help(LinkedList), they will get the contents of the class’s docstring and the public member docstrings, formatted together into one long output string describing (ideally) how to use the class and its member functions. The internal details of how the class works should not be included.
Moreover, the class docstring should not repeat the information given in a member function. For example, you don’t need to document LinkedList.append in the LinkedList docstring, because help(LinkedList) automatically also output the help for help(LinkedList.append).
Yes, your __iter__ implementation is $O(n^2)$. Instead of returning an iterator Object, you’ve returned the original list, and made the original list implement the iterator protocol. This means you cannot have two iterators going at the same time. Also, you cannot iterate halfway through the list, stop and then start a new iteration from the beginning.
You should either create a new class LinkedList._Iter object (which implements the iterator protocol) and return that from __iter__ or, use the same technique you used in _get_values() and return a generator that yields successive values from the list. In either case, these returned objects/generators would be independent; you could have multiple iterations running separately, and/or abandoned halfway through iteration without messing up future iterations.
Edge case:
- start with empty list: head = tail = empty-node
- append one item: head = tail = node-with-value
- delete element 0: head =
None; tail = node-with-value - every subsequent operation (other than
len()) will now raise an exception due to referencing an attribute ofself.head, which is nowNone.
add a comment |
up vote
0
down vote
up vote
0
down vote
I think you’ve misunderstood the purpose of """docstrings""". If someone using your code types help(LinkedList), they will get the contents of the class’s docstring and the public member docstrings, formatted together into one long output string describing (ideally) how to use the class and its member functions. The internal details of how the class works should not be included.
Moreover, the class docstring should not repeat the information given in a member function. For example, you don’t need to document LinkedList.append in the LinkedList docstring, because help(LinkedList) automatically also output the help for help(LinkedList.append).
Yes, your __iter__ implementation is $O(n^2)$. Instead of returning an iterator Object, you’ve returned the original list, and made the original list implement the iterator protocol. This means you cannot have two iterators going at the same time. Also, you cannot iterate halfway through the list, stop and then start a new iteration from the beginning.
You should either create a new class LinkedList._Iter object (which implements the iterator protocol) and return that from __iter__ or, use the same technique you used in _get_values() and return a generator that yields successive values from the list. In either case, these returned objects/generators would be independent; you could have multiple iterations running separately, and/or abandoned halfway through iteration without messing up future iterations.
Edge case:
- start with empty list: head = tail = empty-node
- append one item: head = tail = node-with-value
- delete element 0: head =
None; tail = node-with-value - every subsequent operation (other than
len()) will now raise an exception due to referencing an attribute ofself.head, which is nowNone.
I think you’ve misunderstood the purpose of """docstrings""". If someone using your code types help(LinkedList), they will get the contents of the class’s docstring and the public member docstrings, formatted together into one long output string describing (ideally) how to use the class and its member functions. The internal details of how the class works should not be included.
Moreover, the class docstring should not repeat the information given in a member function. For example, you don’t need to document LinkedList.append in the LinkedList docstring, because help(LinkedList) automatically also output the help for help(LinkedList.append).
Yes, your __iter__ implementation is $O(n^2)$. Instead of returning an iterator Object, you’ve returned the original list, and made the original list implement the iterator protocol. This means you cannot have two iterators going at the same time. Also, you cannot iterate halfway through the list, stop and then start a new iteration from the beginning.
You should either create a new class LinkedList._Iter object (which implements the iterator protocol) and return that from __iter__ or, use the same technique you used in _get_values() and return a generator that yields successive values from the list. In either case, these returned objects/generators would be independent; you could have multiple iterations running separately, and/or abandoned halfway through iteration without messing up future iterations.
Edge case:
- start with empty list: head = tail = empty-node
- append one item: head = tail = node-with-value
- delete element 0: head =
None; tail = node-with-value - every subsequent operation (other than
len()) will now raise an exception due to referencing an attribute ofself.head, which is nowNone.
edited Nov 11 at 21:59
answered Nov 11 at 17:29
AJNeufeld
3,869317
3,869317
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f207192%2fimplementation-of-singly-linked-list%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown