A class to represent a Linked List. Use a tail pointer to speed up the append operation. Initialize a LinkedList with the head node set to None.  linkedlist  LinkedList  linkedlist.head, linkedlist.tail None, None Iterate the LinkedList yielding each Node's data.  linkedlist  LinkedList  items  1, 2, 3, 4, 5  linkedlist.extenditems  tuplelinkedlist  items True Returns a string representation of the LinkedList.  linkedlist  LinkedList  strlinkedlist ''  linkedlist.append1  strlinkedlist '1'  linkedlist.extend2, 3, 4, 5  strlinkedlist '1  2  3  4  5' Appends a new node with the given data to the end of the LinkedList.  linkedlist  LinkedList  strlinkedlist ''  linkedlist.append1  strlinkedlist '1'  linkedlist.append2  strlinkedlist '1  2' Appends each item to the end of the LinkedList.  linkedlist  LinkedList  linkedlist.extend  strlinkedlist ''  linkedlist.extend1, 2  strlinkedlist '1  2'  linkedlist.extend3,4  strlinkedlist '1  2  3  4' Creates a Linked List from the elements of the given sequence listtuple and returns the head of the Linked List.  makelinkedlist Traceback most recent call last: ... Exception: The Elements List is empty  makelinkedlist7 7  makelinkedlist'abc' abc  makelinkedlist7, 25 7  25 Prints the elements of the given Linked List in reverse order  inreverseLinkedList ''  inreversemakelinkedlist69, 88, 73 '73  88  69'
from __future__ import annotations

from collections.abc import Iterable, Iterator
from dataclasses import dataclass


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


class LinkedList:
    """A class to represent a Linked List.
    Use a tail pointer to speed up the append() operation.
    """

    def __init__(self) -> None:
        """Initialize a LinkedList with the head node set to None.
        >>> linked_list = LinkedList()
        >>> (linked_list.head, linked_list.tail)
        (None, None)
        """
        self.head: Node | None = None
        self.tail: Node | None = None  # Speeds up the append() operation

    def __iter__(self) -> Iterator[int]:
        """Iterate the LinkedList yielding each Node's data.
        >>> linked_list = LinkedList()
        >>> items = (1, 2, 3, 4, 5)
        >>> linked_list.extend(items)
        >>> tuple(linked_list) == items
        True
        """
        node = self.head
        while node:
            yield node.data
            node = node.next_node

    def __repr__(self) -> str:
        """Returns a string representation of the LinkedList.
        >>> linked_list = LinkedList()
        >>> str(linked_list)
        ''
        >>> linked_list.append(1)
        >>> str(linked_list)
        '1'
        >>> linked_list.extend([2, 3, 4, 5])
        >>> str(linked_list)
        '1 -> 2 -> 3 -> 4 -> 5'
        """
        return " -> ".join([str(data) for data in self])

    def append(self, data: int) -> None:
        """Appends a new node with the given data to the end of the LinkedList.
        >>> linked_list = LinkedList()
        >>> str(linked_list)
        ''
        >>> linked_list.append(1)
        >>> str(linked_list)
        '1'
        >>> linked_list.append(2)
        >>> str(linked_list)
        '1 -> 2'
        """
        if self.tail:
            self.tail.next_node = self.tail = Node(data)
        else:
            self.head = self.tail = Node(data)

    def extend(self, items: Iterable[int]) -> None:
        """Appends each item to the end of the LinkedList.
        >>> linked_list = LinkedList()
        >>> linked_list.extend([])
        >>> str(linked_list)
        ''
        >>> linked_list.extend([1, 2])
        >>> str(linked_list)
        '1 -> 2'
        >>> linked_list.extend([3,4])
        >>> str(linked_list)
        '1 -> 2 -> 3 -> 4'
        """
        for item in items:
            self.append(item)


def make_linked_list(elements_list: Iterable[int]) -> LinkedList:
    """Creates a Linked List from the elements of the given sequence
    (list/tuple) and returns the head of the Linked List.
    >>> make_linked_list([])
    Traceback (most recent call last):
        ...
    Exception: The Elements List is empty
    >>> make_linked_list([7])
    7
    >>> make_linked_list(['abc'])
    abc
    >>> make_linked_list([7, 25])
    7 -> 25
    """
    if not elements_list:
        raise Exception("The Elements List is empty")

    linked_list = LinkedList()
    linked_list.extend(elements_list)
    return linked_list


def in_reverse(linked_list: LinkedList) -> str:
    """Prints the elements of the given Linked List in reverse order
    >>> in_reverse(LinkedList())
    ''
    >>> in_reverse(make_linked_list([69, 88, 73]))
    '73 <- 88 <- 69'
    """
    return " <- ".join(str(line) for line in reversed(tuple(linked_list)))


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

    testmod()
    linked_list = make_linked_list((14, 52, 14, 12, 43))
    print(f"Linked List:  {linked_list}")
    print(f"Reverse List: {in_reverse(linked_list)}")
