import typing
class Node:
def __init__(self, value):
self.value = value
self.prev = None
self.next = None
def __repr__(self):
return f'Node({self.value})'
def __eq__(self, other):
if isinstance(other, Node):
return self.value == other.value
return False
class DoubleLinkedList:
def __init__(self):
self.head: Node = None
self.tail: Node = None
def append(self, value):
node = Node(value)
if self.head is None:
self.head = node
else:
self.tail.next = node
node.prev = self.tail
self.tail = node
def iter(self, reverse=False):
current = self.head if not reverse else self.tail
while current:
yield current
current = current.next if not reverse else current.prev
def __iter__(self):
current = self.head
while current:
yield current
current = current.next
def pop(self, index: int = None):
if self.head is None:
raise IndexError('pop from empty list')
length = len(self) # > 0
if index is None:
index = -1
if index < 0:
if index < -length:
raise IndexError('pop index out of range')
if length == 1: # only have one node, node.prev == node.next == None, no need to care about ref
node = self.head
self.head = self.tail = None
return node.value
# below code situation: have more than one node
if index == -1: # the last node
tail = self.tail
tail.prev.next = None
self.tail = tail.prev
tail.prev = None # remove ref
return tail.value
elif index == -length: # the first node
head = self.head
self.head = head.next
self.head.prev = None
head.next = None
return head.value
else: # node in the middle position
node = self[index] # we support negative index
prev = node.prev
next = node.next
prev.next = next
next.prev = prev
node.prev = node.next = None
return node.value
else:
if index > length - 1:
raise IndexError('pop index out of range')
if length == 1:
node = self.head
self.head = self.tail = None
return node.value
# below code situation: have more than one node
if index == 0: # the first node
head = self.head
self.head = head.next
self.head.prev = None
head.next = None
return head.value
elif index == length - 1: # the last node
tail = self.tail
self.tail = tail.prev
self.tail.next = None
tail.prev = None
return tail.value
else: # node in the middle position
node = self[index]
prev = node.prev
next = node.next
prev.next = next
next.prev = prev
node.prev = node.next = None
return node.value
# __iter__ = iter
def __getitem__(self, item):
# for i, node in enumerate(self.iter(False if i > 0 else True), start=0 if i > 0 else 1):
# if i == abs(item):
# return node.value
if not isinstance(item, int | slice):
raise TypeError(f'LinkedList indices must be integers or slices, not{type(item)}') # sequence
if isinstance(item, int):
if item < 0: # index = -i - 1
for i, v in enumerate(self.iter(reverse=True)):
if item == -i - 1:
return v
raise IndexError('Index Out of Bounds') # for loops detect the end of sequence
else:
for i, v in enumerate(self.iter()):
if item == i:
return v
raise IndexError('Index Out of Bounds') # for loops detect the end of sequence
else: # item is slice object
dll = self.__class__() # construct a new instance
item: slice
indices = list(range(*item.indices(len(self))))
print('indices', indices, len(self))
# step > 0 indices [2, 4, 6]
# step < 0 indices [6, 4, 2]
for i in indices:
dll.append(self[i].value)
return dll
# if item.start > item.stop: # [3,2,1] step<0
# for i,v in enumerate
# else: # step>0
# for i, v in enumerate(self, start=0):
# if i in indices:
# dll.append(v.value) # append need value, not node
# return dll
def __setitem__(self, key, value):
if not isinstance(key, int | slice):
raise TypeError(f'LinkedList indices must be integers or slices, not{type(key)}') # sequence
if isinstance(key, int):
self[key] = Node(value)
if isinstance(key, slice):
if not isinstance(value, typing.Iterable): # slice require value to be an iterable, even if slice has one item
raise TypeError('can only assign an iterable')
if key.step == 0:
raise ValueError('slice step cannot be zero') # slice does not judge step
length = len(self)
if not length:
for v in value:
self.append(v)
indices = list(range(*key.indices(len(self))))
start = key.start
stop = key.stop
step = key.step
step = 1 if step is None else step # step default to 1
if step > 0:
if start is None:
start = 0
if stop is None:
stop = length
else:
if start is None:
start = -1
if stop is None:
stop = -length - 1
if not len(indices): # slice is null, turn into insert behavior, start position is key.start
if start == 0 or start == -length: # the first node
flag = False
_tail = None # temp tail node
for v in value:
node = Node(v)
if not flag:
head = self.head
self.head = node
flag = True
_tail = node # store temp tail node
else:
_tail.next = node
node.prev = _tail
_tail = node
else: # concatenate self.tail and head
if _tail is None: # len(value) == 0
return
_tail.next = head
head.prev = _tail
elif start >= length: # append the value
for v in value:
self.append(Node(v))
else: # middle position
_head = self[start]
_tail = _head.prev
for v in value:
node = Node(v)
_tail.next = node
node.prev = _tail
_tail = node
_tail.next = _head
_head.prev = _tail
if step > 0:
print('__setitem__', key, indices, start, stop, step)
if step == 1: # adjacent, no need to care about numbers, -1 is not this case
if start == 0: # the first node
if stop >= length: # override the whole LinkedList
for v in self:
v.prev = v.next = None
self.head = self.tail = None
for v in value:
node = Node(v)
if self.head is None:
self.head = node
self.tail = node
else:
self.append(node)
if step > 1: # not adjacent, require len(range(*key.indices(len(self)))) == len(value)
...
def __len__(self):
flag = False
for i, _ in enumerate(self.iter()):
flag = True
if flag:
return i + 1
return 0
def __reversed__(self): # it should return a new iterator object in reverse order, fall back to sequence __len__ __getitem__
print('__reversed__')
for i in range(len(self) - 1, -1, -1):
yield self[i]
def remove(self, value, reverse=False): # enhancement, because we are DoubleLinkedList
length = len(self)
if not length:
raise ValueError('empty!')
node = Node(value)
flag = False
try:
if reverse:
for i, v in enumerate(self.iter(reverse=True), start=0):
if node == v: # found
self.pop(-i - 1) # convert to negative index
break
else:
raise IndexError
else:
for i, v in enumerate(self.iter()):
if node == v:
self.pop(i)
break
else:
raise IndexError
except IndexError:
raise ValueError(f'{value} not in list') from None
def insert(self, index, value):
length = len(self)
node = Node(value)
if length == 0:
self.head = self.tail = node
else:
if index < 0:
if index <= -length: # the first position
head = self.head
self.head = node
node.next = head
head.prev = node
else:
for i, v in enumerate(self.iter(reverse=True)):
if index == -i - 1:
prev = v.prev
prev.next = node
node.prev = prev
node.next = v
v.prev = node
else:
if index == 0: # the first position
head = self.head
self.head = node
node.next = head
head.prev = node
elif index >= length: # the last position
tail = self.tail
self.tail = node
tail.next = node
node.prev = tail
else: # the middle position
for i, v in enumerate(self.iter()):
if index == i:
prev = v.prev
prev.next = node
node.prev = prev
node.next = v
v.prev = node
def __repr__(self):
# print(11, list(map(str, self)))
# return ', '.join(map(str, self))
# print(22, list(self))
return f'[{", ".join(list(map(str, self)))}]'
l = DoubleLinkedList()
l.append(3)
l.append(4)
l.append(5)
l.append(6)
l.append(7)
l.append(8)
# l[0:0] = 'ab'
# l[-len(l):0] = 'AB'
l[3:3] = ''
print(l)
# dd = l[2:88:2]
dd = l[-2:-20:-2]
print('dd =', type(dd), dd)
print('l =', l)
print(list(reversed(l)))
# print(l.pop(-2))
# print(l.pop())
# l.remove(4, reverse=True)
# print(l.pop())
# l.insert(-10, 'a')
# l.insert(-len(l), 'aa')
# l.insert(-1, 'bb')
# l.insert(len(l) - 1, 'bb')
for n in l.iter():
print(n, type(n))
# for n in l.iter(reverse=True):
# print(n, type(n))
# print(l[0], l[-1], l[3], l[-4])
# print(l[8])
# print(l[-5])
print(len(l))
ll = DoubleLinkedList()
print(len(ll))
print(ll.__module__)