iterate through all nodes in the circular linked list yielding their data yields the data of each node in the linked list get the length number of nodes in the circular linked list generate a string representation of the circular linked list returns a string of the format 12 n insert a node with the given data at the end of the circular linked list insert a node with the given data at the beginning of the circular linked list insert the data of the node at the nth pos in the circular linked list args index the index at which the data should be inserted data the data to be inserted raises indexerror if the index is out of range delete and return the data of the node at the front of the circular linked list raises indexerror if the list is empty delete and return the data of the node at the end of the circular linked list returns any the data of the deleted node raises indexerror if the index is out of range delete and return the data of the node at the nth pos in circular linked list args index int the index of the node to be deleted defaults to 0 returns any the data of the deleted node raises indexerror if the index is out of range check if the circular linked list is empty returns bool true if the list is empty false otherwise test cases for the circularlinkedlist class testcircularlinkedlist reference to the head first node reference to the tail last node iterate through all nodes in the circular linked list yielding their data yields the data of each node in the linked list get the length number of nodes in the circular linked list generate a string representation of the circular linked list returns a string of the format 1 2 n insert a node with the given data at the end of the circular linked list insert a node with the given data at the beginning of the circular linked list insert the data of the node at the nth pos in the circular linked list args index the index at which the data should be inserted data the data to be inserted raises indexerror if the index is out of range first node points to itself insert at the head list is not empty tail exists insert at the tail delete and return the data of the node at the front of the circular linked list raises indexerror if the list is empty delete and return the data of the node at the end of the circular linked list returns any the data of the deleted node raises indexerror if the index is out of range delete and return the data of the node at the nth pos in circular linked list args index int the index of the node to be deleted defaults to 0 returns any the data of the deleted node raises indexerror if the index is out of range just one node delete head node delete at tail check if the circular linked list is empty returns bool true if the list is empty false otherwise test cases for the circularlinkedlist class test_circular_linked_list this should not happen this should happen this should not happen this should happen
from __future__ import annotations

from collections.abc import Iterator
from dataclasses import dataclass
from typing import Any


@dataclass
class Node:
    data: Any
    next_node: Node | None = None


@dataclass
class CircularLinkedList:
    head: Node | None = None  
    tail: Node | None = None  

    def __iter__(self) -> Iterator[Any]:
        
        node = self.head
        while node:
            yield node.data
            node = node.next_node
            if node == self.head:
                break

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

    def __repr__(self) -> str:
        
        return "->".join(str(item) for item in iter(self))

    def insert_tail(self, data: Any) -> None:
        
        self.insert_nth(len(self), data)

    def insert_head(self, data: Any) -> None:
        
        self.insert_nth(0, data)

    def insert_nth(self, index: int, data: Any) -> None:
        
        if index < 0 or index > len(self):
            raise IndexError("list index out of range.")
        new_node: Node = Node(data)
        if self.head is None:
            new_node.next_node = new_node  
            self.tail = self.head = new_node
        elif index == 0:  
            new_node.next_node = self.head
            assert self.tail is not None  
            self.head = self.tail.next_node = new_node
        else:
            temp: Node | None = self.head
            for _ in range(index - 1):
                assert temp is not None
                temp = temp.next_node
            assert temp is not None
            new_node.next_node = temp.next_node
            temp.next_node = new_node
            if index == len(self) - 1:  
                self.tail = new_node

    def delete_front(self) -> Any:
        
        return self.delete_nth(0)

    def delete_tail(self) -> Any:
        
        return self.delete_nth(len(self) - 1)

    def delete_nth(self, index: int = 0) -> Any:
        
        if not 0 <= index < len(self):
            raise IndexError("list index out of range.")

        assert self.head is not None
        assert self.tail is not None
        delete_node: Node = self.head
        if self.head == self.tail:  
            self.head = self.tail = None
        elif index == 0:  
            assert self.tail.next_node is not None
            self.tail.next_node = self.tail.next_node.next_node
            self.head = self.head.next_node
        else:
            temp: Node | None = self.head
            for _ in range(index - 1):
                assert temp is not None
                temp = temp.next_node
            assert temp is not None
            assert temp.next_node is not None
            delete_node = temp.next_node
            temp.next_node = temp.next_node.next_node
            if index == len(self) - 1:  
                self.tail = temp
        return delete_node.data

    def is_empty(self) -> bool:
        
        return len(self) == 0


def test_circular_linked_list() -> None:
    
    circular_linked_list = CircularLinkedList()
    assert len(circular_linked_list) == 0
    assert circular_linked_list.is_empty() is True
    assert str(circular_linked_list) == ""

    try:
        circular_linked_list.delete_front()
        raise AssertionError  
    except IndexError:
        assert True  

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

    try:
        circular_linked_list.delete_nth(-1)
        raise AssertionError
    except IndexError:
        assert True

    try:
        circular_linked_list.delete_nth(0)
        raise AssertionError
    except IndexError:
        assert True

    assert circular_linked_list.is_empty() is True
    for i in range(5):
        assert len(circular_linked_list) == i
        circular_linked_list.insert_nth(i, i + 1)
    assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6))

    circular_linked_list.insert_tail(6)
    assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 7))
    circular_linked_list.insert_head(0)
    assert str(circular_linked_list) == "->".join(str(i) for i in range(7))

    assert circular_linked_list.delete_front() == 0
    assert circular_linked_list.delete_tail() == 6
    assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6))
    assert circular_linked_list.delete_nth(2) == 3

    circular_linked_list.insert_nth(2, 3)
    assert str(circular_linked_list) == "->".join(str(i) for i in range(1, 6))

    assert circular_linked_list.is_empty() is False


if __name__ == "__main__":
    import doctest

    doctest.testmod()