Module solists.doubly_linked_list
Expand source code
from ._node import Node
__all__ = ['List']
class EmptyError(ValueError):
"""
Raised when a list is empty.
"""
class NotEmptyError(ValueError):
"""
Raised when a list not is empty.
"""
class List:
"""
Doubly linked list.
>>> a = List.from_iterable([1, 2, 3])
>>> print(a)
[1, 2, 3]
>>> 1 in a
True
>>> print(a)
[1, 2, 3]
>>> 5 in a
False
>>> print(a)
[1, 2, 3]
"""
def __init__(self):
self.head = None
self.tail = None
self.size = 0
@classmethod
def from_iterable(cls, iterable):
"""
Alternate constructor for `List`.
Gets data from a single `iterable` argument.
>>> a = List.from_iterable([1, 2, 3])
>>> print(a)
[1, 2, 3]
>>> b = List.from_iterable(('Hello', 'World'))
>>> print(b)
['Hello', 'World']
"""
new_list = cls()
new_list.extend(iterable)
return new_list
def __repr__(self):
"""
Return an unambiguous representation of an object.
"""
return (f'{self.__class__.__name__}('
f'head={self.head!r}, tail={self.tail!r})')
def __str__(self):
"""
Return elements of the list as a string.
"""
return f'{[element for element in self]}'
def __iter__(self):
"""
Traverse the list in forward direction.
"""
node = self.head
while node:
yield node.data
node = node.next_node
def __reversed__(self):
"""
Traverse the list in reverse direction.
"""
node = self.tail
while node:
yield node.data
node = node.prev_node
def __len__(self):
return self.size
def __contains__(self, data):
"""
Check if the list contains an element with
a given data.
"""
for node in self._nodes():
if node.data == data:
self._rearrange(node)
return True
return False
def __eq__(self, iterable):
"""
Check if list is equal to `iterable`.
"""
for element, other_element in zip(self, iterable):
if element != other_element:
return False
return True
def append(self, data):
"""
Insert an element to the end of the list.
>>> a = List()
>>> a.append(1)
>>> a.append(2)
>>> print(a)
[1, 2]
"""
new_node = Node(data)
if self.tail:
self._insert_after(self.tail, new_node)
else:
self._insert_first(new_node)
def prepend(self, data):
"""
Insert an element to the beginning of the list.
>>> a = List()
>>> a.prepend(1)
>>> a.prepend(0)
>>> print(a)
[0, 1]
"""
new_node = Node(data)
if self.head:
self._insert_before(self.head, new_node)
else:
self._insert_first(new_node)
def _insert_first(self, new_node):
"""
Insert a node into the empty list.
Raise `NotEmptyError` if the list is not empty.
"""
if self.size > 0:
raise NotEmptyError('List must be empty')
self.head = new_node
self.tail = new_node
self.size += 1
def _insert_before(self, existing_node, new_node):
"""
Insert a node before a given node.
"""
new_node.next_node = existing_node
if existing_node is self.head:
new_node.prev_node = None
self.head = new_node
else:
new_node.prev_node = existing_node.prev_node
existing_node.prev_node.next_node = new_node
existing_node.prev_node = new_node
self.size += 1
def _insert_after(self, existing_node, new_node):
"""
Insert a node after a given one.
"""
new_node.prev_node = existing_node
if existing_node is self.tail:
new_node.next_node = None
self.tail = new_node
else:
new_node.next_node = existing_node.next_node
existing_node.next_node.prev_node = new_node
existing_node.next_node = new_node
self.size += 1
def extend(self, iterable):
"""
Insert items from `iterable` to the end of the list.
>>> a = List()
>>> b = [1, 2, 3]
>>> a.extend(b)
>>> print(a)
[1, 2, 3]
"""
for item in iterable:
self.append(item)
def is_empty(self):
"""
Check if list is empty.
"""
return len(self) == 0
def pop_back(self):
"""
Remove the last element of the list.
>>> a = List.from_iterable([1, 2, 3])
>>> print(a)
[1, 2, 3]
>>> a.pop_back()
>>> print(a)
[1, 2]
"""
if self.is_empty():
raise EmptyError('List must not be empty')
if self.size == 1:
self._remove_last()
else:
self.tail = self.tail.prev_node
self.tail.next_node = None
self.size -= 1
def pop_front(self):
"""
Remove the first element of the list.
>>> a = List.from_iterable([1, 2, 3])
>>> print(a)
[1, 2, 3]
>>> a.pop_front()
>>> print(a)
[2, 3]
"""
if self.is_empty():
raise EmptyError('List must not be empty')
if self.size == 1:
self._remove_last()
else:
self.head = self.head.next_node
self.head.prev_node = None
self.size -= 1
def erase(self, data):
"""
Erase all elements of the list that
contain a given data.
>>> a = List.from_iterable([1, 2, 3, 3, 3, 4])
>>> print(a)
[1, 2, 3, 3, 3, 4]
>>> a.erase(3)
>>> print(a)
[1, 2, 4]
"""
for node in self._nodes():
next_node = node.next_node
if node.data == data:
self._remove(node)
node = next_node
def _remove(self, node):
"""
Remove a given node from the list.
"""
if node is self.head:
self.pop_front()
elif node is self.tail:
self.pop_back()
else:
node.next_node.prev_node = node.prev_node
node.prev_node.next_node = node.next_node
self.size -= 1
def _remove_last(self):
"""
Remove the only node in the list.
"""
if self.size > 1:
raise ValueError('List has more than one node')
self.head = None
self.tail = None
self.size = 0
def _rearrange(self, node):
"""
Apply self-organizing method.
"""
return
def _nodes(self):
"""
Traverse the list _nodes in forward direction.
"""
node = self.head
while node:
yield node
node = node.next_node
Classes
class List
-
Doubly linked list.
>>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> 1 in a True >>> print(a) [1, 2, 3] >>> 5 in a False >>> print(a) [1, 2, 3]
Expand source code
class List: """ Doubly linked list. >>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> 1 in a True >>> print(a) [1, 2, 3] >>> 5 in a False >>> print(a) [1, 2, 3] """ def __init__(self): self.head = None self.tail = None self.size = 0 @classmethod def from_iterable(cls, iterable): """ Alternate constructor for `List`. Gets data from a single `iterable` argument. >>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> b = List.from_iterable(('Hello', 'World')) >>> print(b) ['Hello', 'World'] """ new_list = cls() new_list.extend(iterable) return new_list def __repr__(self): """ Return an unambiguous representation of an object. """ return (f'{self.__class__.__name__}(' f'head={self.head!r}, tail={self.tail!r})') def __str__(self): """ Return elements of the list as a string. """ return f'{[element for element in self]}' def __iter__(self): """ Traverse the list in forward direction. """ node = self.head while node: yield node.data node = node.next_node def __reversed__(self): """ Traverse the list in reverse direction. """ node = self.tail while node: yield node.data node = node.prev_node def __len__(self): return self.size def __contains__(self, data): """ Check if the list contains an element with a given data. """ for node in self._nodes(): if node.data == data: self._rearrange(node) return True return False def __eq__(self, iterable): """ Check if list is equal to `iterable`. """ for element, other_element in zip(self, iterable): if element != other_element: return False return True def append(self, data): """ Insert an element to the end of the list. >>> a = List() >>> a.append(1) >>> a.append(2) >>> print(a) [1, 2] """ new_node = Node(data) if self.tail: self._insert_after(self.tail, new_node) else: self._insert_first(new_node) def prepend(self, data): """ Insert an element to the beginning of the list. >>> a = List() >>> a.prepend(1) >>> a.prepend(0) >>> print(a) [0, 1] """ new_node = Node(data) if self.head: self._insert_before(self.head, new_node) else: self._insert_first(new_node) def _insert_first(self, new_node): """ Insert a node into the empty list. Raise `NotEmptyError` if the list is not empty. """ if self.size > 0: raise NotEmptyError('List must be empty') self.head = new_node self.tail = new_node self.size += 1 def _insert_before(self, existing_node, new_node): """ Insert a node before a given node. """ new_node.next_node = existing_node if existing_node is self.head: new_node.prev_node = None self.head = new_node else: new_node.prev_node = existing_node.prev_node existing_node.prev_node.next_node = new_node existing_node.prev_node = new_node self.size += 1 def _insert_after(self, existing_node, new_node): """ Insert a node after a given one. """ new_node.prev_node = existing_node if existing_node is self.tail: new_node.next_node = None self.tail = new_node else: new_node.next_node = existing_node.next_node existing_node.next_node.prev_node = new_node existing_node.next_node = new_node self.size += 1 def extend(self, iterable): """ Insert items from `iterable` to the end of the list. >>> a = List() >>> b = [1, 2, 3] >>> a.extend(b) >>> print(a) [1, 2, 3] """ for item in iterable: self.append(item) def is_empty(self): """ Check if list is empty. """ return len(self) == 0 def pop_back(self): """ Remove the last element of the list. >>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> a.pop_back() >>> print(a) [1, 2] """ if self.is_empty(): raise EmptyError('List must not be empty') if self.size == 1: self._remove_last() else: self.tail = self.tail.prev_node self.tail.next_node = None self.size -= 1 def pop_front(self): """ Remove the first element of the list. >>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> a.pop_front() >>> print(a) [2, 3] """ if self.is_empty(): raise EmptyError('List must not be empty') if self.size == 1: self._remove_last() else: self.head = self.head.next_node self.head.prev_node = None self.size -= 1 def erase(self, data): """ Erase all elements of the list that contain a given data. >>> a = List.from_iterable([1, 2, 3, 3, 3, 4]) >>> print(a) [1, 2, 3, 3, 3, 4] >>> a.erase(3) >>> print(a) [1, 2, 4] """ for node in self._nodes(): next_node = node.next_node if node.data == data: self._remove(node) node = next_node def _remove(self, node): """ Remove a given node from the list. """ if node is self.head: self.pop_front() elif node is self.tail: self.pop_back() else: node.next_node.prev_node = node.prev_node node.prev_node.next_node = node.next_node self.size -= 1 def _remove_last(self): """ Remove the only node in the list. """ if self.size > 1: raise ValueError('List has more than one node') self.head = None self.tail = None self.size = 0 def _rearrange(self, node): """ Apply self-organizing method. """ return def _nodes(self): """ Traverse the list _nodes in forward direction. """ node = self.head while node: yield node node = node.next_node
Subclasses
Static methods
def from_iterable(iterable)
-
Alternate constructor for
List
. Gets data from a singleiterable
argument.>>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> b = List.from_iterable(('Hello', 'World')) >>> print(b) ['Hello', 'World']
Expand source code
@classmethod def from_iterable(cls, iterable): """ Alternate constructor for `List`. Gets data from a single `iterable` argument. >>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> b = List.from_iterable(('Hello', 'World')) >>> print(b) ['Hello', 'World'] """ new_list = cls() new_list.extend(iterable) return new_list
Methods
def append(self, data)
-
Insert an element to the end of the list.
>>> a = List() >>> a.append(1) >>> a.append(2) >>> print(a) [1, 2]
Expand source code
def append(self, data): """ Insert an element to the end of the list. >>> a = List() >>> a.append(1) >>> a.append(2) >>> print(a) [1, 2] """ new_node = Node(data) if self.tail: self._insert_after(self.tail, new_node) else: self._insert_first(new_node)
def erase(self, data)
-
Erase all elements of the list that contain a given data.
>>> a = List.from_iterable([1, 2, 3, 3, 3, 4]) >>> print(a) [1, 2, 3, 3, 3, 4] >>> a.erase(3) >>> print(a) [1, 2, 4]
Expand source code
def erase(self, data): """ Erase all elements of the list that contain a given data. >>> a = List.from_iterable([1, 2, 3, 3, 3, 4]) >>> print(a) [1, 2, 3, 3, 3, 4] >>> a.erase(3) >>> print(a) [1, 2, 4] """ for node in self._nodes(): next_node = node.next_node if node.data == data: self._remove(node) node = next_node
def extend(self, iterable)
-
Insert items from
iterable
to the end of the list.>>> a = List() >>> b = [1, 2, 3] >>> a.extend(b) >>> print(a) [1, 2, 3]
Expand source code
def extend(self, iterable): """ Insert items from `iterable` to the end of the list. >>> a = List() >>> b = [1, 2, 3] >>> a.extend(b) >>> print(a) [1, 2, 3] """ for item in iterable: self.append(item)
def is_empty(self)
-
Check if list is empty.
Expand source code
def is_empty(self): """ Check if list is empty. """ return len(self) == 0
def pop_back(self)
-
Remove the last element of the list.
>>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> a.pop_back() >>> print(a) [1, 2]
Expand source code
def pop_back(self): """ Remove the last element of the list. >>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> a.pop_back() >>> print(a) [1, 2] """ if self.is_empty(): raise EmptyError('List must not be empty') if self.size == 1: self._remove_last() else: self.tail = self.tail.prev_node self.tail.next_node = None self.size -= 1
def pop_front(self)
-
Remove the first element of the list.
>>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> a.pop_front() >>> print(a) [2, 3]
Expand source code
def pop_front(self): """ Remove the first element of the list. >>> a = List.from_iterable([1, 2, 3]) >>> print(a) [1, 2, 3] >>> a.pop_front() >>> print(a) [2, 3] """ if self.is_empty(): raise EmptyError('List must not be empty') if self.size == 1: self._remove_last() else: self.head = self.head.next_node self.head.prev_node = None self.size -= 1
def prepend(self, data)
-
Insert an element to the beginning of the list.
>>> a = List() >>> a.prepend(1) >>> a.prepend(0) >>> print(a) [0, 1]
Expand source code
def prepend(self, data): """ Insert an element to the beginning of the list. >>> a = List() >>> a.prepend(1) >>> a.prepend(0) >>> print(a) [0, 1] """ new_node = Node(data) if self.head: self._insert_before(self.head, new_node) else: self._insert_first(new_node)