https en wikipedia orgwikidoublylinkedlist linkedlist doublylinkedlist linkedlist insertathead b linkedlist insertathead a linkedlist insertattail c tuplelinkedlist a b c linkedlist doublylinkedlist linkedlist insertattail a linkedlist insertattail b linkedlist insertattail c strlinkedlist abc linkedlist doublylinkedlist for i in range0 5 linkedlist insertatnthi i 1 lenlinkedlist 5 true linkedlist doublylinkedlist linkedlist insertatnth1 666 traceback most recent call last indexerror list index out of range linkedlist insertatnth1 666 traceback most recent call last indexerror list index out of range linkedlist insertatnth0 2 linkedlist insertatnth0 1 linkedlist insertatnth2 4 linkedlist insertatnth2 3 strlinkedlist 1234 linkedlist insertatnth5 5 traceback most recent call last indexerror list index out of range linkedlist doublylinkedlist linkedlist deleteatnth0 traceback most recent call last indexerror list index out of range for i in range0 5 linkedlist insertatnthi i 1 linkedlist deleteatnth0 1 true linkedlist deleteatnth3 5 true linkedlist deleteatnth1 3 true strlinkedlist 24 linkedlist deleteatnth2 traceback most recent call last indexerror list index out of range linkedlist doublylinkedlist linkedlist isempty true linkedlist insertattail1 linkedlist isempty false testdoublylinkedlist linked_list doublylinkedlist linked_list insert_at_head b linked_list insert_at_head a linked_list insert_at_tail c tuple linked_list a b c linked_list doublylinkedlist linked_list insert_at_tail a linked_list insert_at_tail b linked_list insert_at_tail c str linked_list a b c linked_list doublylinkedlist for i in range 0 5 linked_list insert_at_nth i i 1 len linked_list 5 true linked_list doublylinkedlist linked_list insert_at_nth 1 666 traceback most recent call last indexerror list index out of range linked_list insert_at_nth 1 666 traceback most recent call last indexerror list index out of range linked_list insert_at_nth 0 2 linked_list insert_at_nth 0 1 linked_list insert_at_nth 2 4 linked_list insert_at_nth 2 3 str linked_list 1 2 3 4 linked_list insert_at_nth 5 5 traceback most recent call last indexerror list index out of range linked_list doublylinkedlist linked_list delete_at_nth 0 traceback most recent call last indexerror list index out of range for i in range 0 5 linked_list insert_at_nth i i 1 linked_list delete_at_nth 0 1 true linked_list delete_at_nth 3 5 true linked_list delete_at_nth 1 3 true str linked_list 2 4 linked_list delete_at_nth 2 traceback most recent call last indexerror list index out of range default first node find the position to delete we have reached the end an no value matches before 1 2 current 3 1 3 1 3 linked_list doublylinkedlist linked_list is_empty true linked_list insert_at_tail 1 linked_list is_empty false test_doubly_linked_list this should not happen this should happen this should not happen this should happen
class Node:
    def __init__(self, data):
        self.data = data
        self.previous = None
        self.next = None

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


class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    def __iter__(self):
        
        node = self.head
        while node:
            yield node.data
            node = node.next

    def __str__(self):
        
        return "->".join([str(item) for item in self])

    def __len__(self):
        
        return sum(1 for _ in self)

    def insert_at_head(self, data):
        self.insert_at_nth(0, data)

    def insert_at_tail(self, data):
        self.insert_at_nth(len(self), data)

    def insert_at_nth(self, index: int, data):
        
        length = len(self)

        if not 0 <= index <= length:
            raise IndexError("list index out of range")
        new_node = Node(data)
        if self.head is None:
            self.head = self.tail = new_node
        elif index == 0:
            self.head.previous = new_node
            new_node.next = self.head
            self.head = new_node
        elif index == length:
            self.tail.next = new_node
            new_node.previous = self.tail
            self.tail = new_node
        else:
            temp = self.head
            for _ in range(index):
                temp = temp.next
            temp.previous.next = new_node
            new_node.previous = temp.previous
            new_node.next = temp
            temp.previous = new_node

    def delete_head(self):
        return self.delete_at_nth(0)

    def delete_tail(self):
        return self.delete_at_nth(len(self) - 1)

    def delete_at_nth(self, index: int):
        
        length = len(self)

        if not 0 <= index <= length - 1:
            raise IndexError("list index out of range")
        delete_node = self.head  
        if length == 1:
            self.head = self.tail = None
        elif index == 0:
            self.head = self.head.next
            self.head.previous = None
        elif index == length - 1:
            delete_node = self.tail
            self.tail = self.tail.previous
            self.tail.next = None
        else:
            temp = self.head
            for _ in range(index):
                temp = temp.next
            delete_node = temp
            temp.next.previous = temp.previous
            temp.previous.next = temp.next
        return delete_node.data

    def delete(self, data) -> str:
        current = self.head

        while current.data != data:  
            if current.next:
                current = current.next
            else:  
                raise ValueError("No data matching given value")

        if current == self.head:
            self.delete_head()

        elif current == self.tail:
            self.delete_tail()

        else:  
            current.previous.next = current.next  
            current.next.previous = current.previous  
        return data

    def is_empty(self):
        
        return len(self) == 0


def test_doubly_linked_list() -> None:
    
    linked_list = DoublyLinkedList()
    assert linked_list.is_empty() is True
    assert str(linked_list) == ""

    try:
        linked_list.delete_head()
        raise AssertionError  
    except IndexError:
        assert True  

    try:
        linked_list.delete_tail()
        raise AssertionError  
    except IndexError:
        assert True  

    for i in range(10):
        assert len(linked_list) == i
        linked_list.insert_at_nth(i, i + 1)
    assert str(linked_list) == "->".join(str(i) for i in range(1, 11))

    linked_list.insert_at_head(0)
    linked_list.insert_at_tail(11)
    assert str(linked_list) == "->".join(str(i) for i in range(12))

    assert linked_list.delete_head() == 0
    assert linked_list.delete_at_nth(9) == 10
    assert linked_list.delete_tail() == 11
    assert len(linked_list) == 9
    assert str(linked_list) == "->".join(str(i) for i in range(1, 10))


if __name__ == "__main__":
    from doctest import testmod

    testmod()