#### Doubly Linked List Two

K
```"""
- A linked list is similar to an array, it holds values. However, links in a linked
list do not have indexes.
- This is an example of a double ended, doubly linked list.
- Each link references the next link and the previous one.
- A Doubly Linked List (DLL) contains an extra pointer, typically called previous
pointer, together with next pointer and data which are there in singly linked list.
- Advantages over SLL - It can be traversed in both forward and backward direction.
Delete operation is more efficient
"""

class Node:
def __init__(self, data: int, previous=None, next_node=None):
self.data = data
self.previous = previous
self.next = next_node

def __str__(self) -> str:
return f"{self.data}"

def get_data(self) -> int:
return self.data

def get_next(self):
return self.next

def get_previous(self):
return self.previous

def __iter__(self):
return self

def __next__(self):
if not self.current:
raise StopIteration
else:
value = self.current.get_data()
self.current = self.current.get_next()
return value

def __init__(self):
self.head = None  # First node in list
self.tail = None  # Last node in list

def __str__(self):
nodes = []
while current is not None:
nodes.append(current.get_data())
current = current.get_next()
return " ".join(str(node) for node in nodes)

def __contains__(self, value: int):
while current:
if current.get_data() == value:
return True
current = current.get_next()
return False

def __iter__(self):

return None

def get_tail_data(self):
if self.tail:
return self.tail.get_data()
return None

def set_head(self, node: Node) -> None:
if self.head is None:
self.tail = node
else:

def set_tail(self, node: Node) -> None:
if self.head is None:
else:
self.insert_after_node(self.tail, node)

def insert(self, value: int) -> None:
node = Node(value)
if self.head is None:
else:
self.set_tail(node)

def insert_before_node(self, node: Node, node_to_insert: Node) -> None:
node_to_insert.next = node
node_to_insert.previous = node.previous

if node.get_previous() is None:
else:
node.previous.next = node_to_insert

node.previous = node_to_insert

def insert_after_node(self, node: Node, node_to_insert: Node) -> None:
node_to_insert.previous = node
node_to_insert.next = node.next

if node.get_next() is None:
self.tail = node_to_insert
else:
node.next.previous = node_to_insert

node.next = node_to_insert

def insert_at_position(self, position: int, value: int) -> None:
current_position = 1
new_node = Node(value)
while node:
if current_position == position:
self.insert_before_node(node, new_node)
return
current_position += 1
node = node.next
self.insert_after_node(self.tail, new_node)

def get_node(self, item: int) -> Node:
while node:
if node.get_data() == item:
return node
node = node.get_next()

def delete_value(self, value):
if (node := self.get_node(value)) is not None:
if node == self.head:

if node == self.tail:
self.tail = self.tail.get_previous()

self.remove_node_pointers(node)

@staticmethod
def remove_node_pointers(node: Node) -> None:
if node.get_next():
node.next.previous = node.previous

if node.get_previous():
node.previous.next = node.next

node.next = None
node.previous = None

def is_empty(self):
return self.head is None

def create_linked_list() -> None:
"""
True
>>> new_linked_list.get_tail_data() is None
True
True
10
10
10
20
1000
20
1000
2000
>>> for value in new_linked_list:
...    print(value)
1000
10
20
2000
False
>>> for value in new_linked_list:
...    print(value)
1000
10
20
2000
>>> 10 in new_linked_list
True
>>> 10 in new_linked_list
False
20
20
20
>>> for value in new_linked_list:
...    print(value)
20
>>> for value in new_linked_list:
...    print(value)
>>> for value in range(1,10):
>>> for value in new_linked_list:
...    print(value)
1
2
3
4
5
6
7
8
9
"""

if __name__ == "__main__":
import doctest

doctest.testmod()
```