Aún no tienes acceso a esta clase

Crea una cuenta y continúa viendo este curso

Double linked list

15/23
Recursos

Aportes 14

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Les comparto mi solución al reto.
Me lleve unas horas pero cree la clase con los métodos vistos en clases pasadas y agregue unos cuantos más.

Este es el código de la clase que cree:

# ! Circle Double Linked List
class circleDoubleLinkedList():
    def __init__(self):
        self.head = TwoWayNode(None,None,None)
        self.tail = self.head
        self.size = 0

        self.head.next = self.tail
        self.head.previous = self.tail


    def range(self, start, stop):
        # * Creación de los nodos enlazados (linked list)
        self.head = TwoWayNode(start, next=None, previous=None)
        self.tail = self.head
        self.size = 1

        for data in range(start+1, stop):
            self.tail.next = TwoWayNode(data, previous=self.tail)
            self.tail = self.tail.next
            self.size += 1

        self.tail.next = self.head
        self.head.previous = self.tail

    def __str__(self):
        # * Recorrer e imprimir valores de la lista
        probe = self.head
        result = ''
        while probe.next != self.head:
            result += f'{probe.data} '
            probe = probe.next
        result += f'{probe.data}'
        result = result.strip()
        return result

    def reverse(self):
        probe = self.tail
        result = ''
        while probe.previous != self.tail:
            result += f'{probe.data} '
            probe = probe.previous
        result += f'{probe.data}'
        result = result.strip()
        return result

    def str_by_steps(self, steps, direction='forward'):
        result = ''

        if direction == 'forward':
            probe = self.head
            while steps > 0:
                result += f'{probe.data} '
                probe = probe.next
                steps -= 1

        if direction == 'backward':
            probe = self.tail
            while steps > 0:
                result += f'{probe.data} '
                probe = probe.previous
                steps -= 1

        result = result.strip()
        return result

    def search(self, target_item):
        # * Busqueda de un elemento
        probe = self.head
        while probe.next != self.head and target_item != probe.data:
            probe = probe.next

        if probe.data == target_item:
            print(f'Target item {target_item} has been found')
            return probe
        else:
            print(f'Target item {target_item} is not in the linked list')
            return -1

    def replace(self, target_item, new_item):
        # * Remplazo de un elemento
        probe = self.head

        while probe.next != self.head and target_item != probe.data:
            probe = probe.next

        if probe.data == target_item:
            probe.data = new_item
            print(
                f"{new_item} replace the old value {target_item}")
        else:
            print(f"The target item {target_item} is not in the linked list")

    def unshift(self, new_item):
        # * Insertar un nuevo elemento/nodo al inicio(head)
        if self.size == 0:
            self.head.data = new_item
        else:
            self.head = TwoWayNode(new_item, previous=self.tail, next=self.head)
            self.head.next.previous = self.head
            self.tail.next = self.head
        self.size += 1

    def append(self, new_item):
        # * Insertar un nuevo elemento/nodo al final(tail)
        if self.size == 0:
            self.tail.data = new_item
        else:
            self.tail = TwoWayNode(new_item, previous=self.tail, next=self.head)
            self.tail.previous.next = self.tail
            self.head.previous = self.tail
        self.size += 1

    def shift(self):
        # * Eliminar un elmento/nodo al inicio(head)
        if self.size == 0:
            return None

        removed_item = self.head.data
        self.head = self.head.next

        self.head.previous = self.tail
        self.tail.next = self.head

        self.size -= 1

        print(f'Removed_item: {removed_item}')
        return removed_item

    def pop(self):
        # * Eliminar un elmento/nodo al final(tail)
        if self.size == 0:
            return None

        removed_item = self.tail.data
        self.tail = self.tail.previous

        self.head.previous = self.tail
        self.tail.next = self.head

        self.size -= 1

        print(f'Removed_item: {removed_item}')
        return removed_item

    def insert(self, new_item, index):
        # * Agregar un nuevo elemento/nodo por "indice" (Cuenta de Head - Tail)
        if self.size == 0:
            self.head.data = new_item
            self.size += 1
        elif index <= 0:
            self.unshift(new_item)
        elif index >= self.size - 1:
            self.append(new_item)
        else:
            probe = self.head
            while index > 1 and probe.next != self.head:
                probe = probe.next
                index -= 1
            probe.next = TwoWayNode(new_item, previous=probe, next=probe.next)
            probe.next.next.previous = probe.next
            self.size += 1

    def delete_by_index(self, index):
        # * Eliminar un nuevo elemento/nodo por "indice" (Cuenta de Head - Tail)
        if self.size == 0:
            return None
        if index <= 0:
            self.shift()
        elif index >= self.size - 1:
            self.pop()
        else:
            probe = self.head
            while index > 1 and probe.next.next != self.head:
                probe = probe.next
                index -= 1
            removed_item = probe.next.data
            probe.next = probe.next.next
            probe.next.previous = probe
            self.size -= 1

            print(f'Removed_item: {removed_item}')
            return removed_item

    def clear(self):
        self.__init__()

y estas son las pruebas de su funcionamiento, en esta parte me lleve más tiempo solucionando los bugs.

from double_linked_list import circleDoubleLinkedList

circlue_doble_list = circleDoubleLinkedList()
print('Prueba para ver si se inicializo correctamente:')
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()


print('Prueba para agregar un elemento con la lista vacia:')
circlue_doble_list.unshift(4)
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()


print('Prueba de generación de elementos por range:')
circlue_doble_list.range(1, 6)
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()


print('Prueba de recorrido por pasos:')
print(circlue_doble_list.str_by_steps(12, 'forward'))
print(circlue_doble_list.str_by_steps(12, 'backward'))
print()

print('Prueba de busqueda:')
circlue_doble_list.search(2)
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()

print('Prueba de remplazar un elmento:')
circlue_doble_list.replace(3, 'replace_item')
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()

print('Prueba de insertar un nuevo elmento en head:')
circlue_doble_list.unshift('unshift')
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()


print('Prueba de insertar un nuevo elmento en tail:')
circlue_doble_list.append('append')
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()

print('Prueba de elminar el elemento en head: ')
circlue_doble_list.shift()
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()


print('Prueba de elminar el elemento en tail: ')
circlue_doble_list.pop()
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()


print('Prueba de insertar un elemento por indice: ')
circlue_doble_list.insert("index", 1)
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()

print('Prueba de elminar un elemento por indice: ')
circlue_doble_list.delete_by_index(3)
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()


print('Prueba de limpieza de la lista: ')
circlue_doble_list.clear()
print(circlue_doble_list)
print(circlue_doble_list.reverse())
print()

Y por ulimo los resultados

Prueba para ver si se inicializo correctamente:
None
None

Prueba para agregar un elemento con la lista vacia:
4
4

Prueba de generación de elementos por range:
1 2 3 4 5
5 4 3 2 1

Prueba de recorrido por pasos:
1 2 3 4 5 1 2 3 4 5 1 2
5 4 3 2 1 5 4 3 2 1 5 4

Prueba de busqueda:
Target item 2 has been found
1 2 3 4 5
5 4 3 2 1

Prueba de remplazar un elmento:
replace_item replace the old value 3
1 2 replace_item 4 5
5 4 replace_item 2 1

Prueba de insertar un nuevo elmento en head:
unshift 1 2 replace_item 4 5
5 4 replace_item 2 1 unshift

Prueba de insertar un nuevo elmento en tail:
unshift 1 2 replace_item 4 5 append
append 5 4 replace_item 2 1 unshift

Prueba de elminar el elemento en head:
Removed_item: unshift
1 2 replace_item 4 5 append
append 5 4 replace_item 2 1

Prueba de elminar el elemento en tail:
Removed_item: append
1 2 replace_item 4 5
5 4 replace_item 2 1

Prueba de insertar un elemento por indice:
1 index 2 replace_item 4 5
5 4 replace_item 2 index 1

Prueba de elminar un elemento por indice:
Removed_item: replace_item
1 index 2 4 5
5 4 2 index 1

Prueba de limpieza de la lista:
None
None

Double Linked List con operaciones y UnitTest

Linked List

from typing import Iterable
from typing import Optional
from typing import Union

from node import TwoWayNode


class DoubleLinkedList:

    _head: Optional[TwoWayNode]

    _size: int

    def __init__(self, *items):
        """Initializes a double linked list."""

        self._head = None
        self._size = 0

        if len(items) > 0 and isinstance(items[0], Iterable):
            items = items[0]

        for item in items:
            self.append(item)

    @property
    def size(self) -> int:
        """Returns the size of the list."""

        return self._size

    def append(self, data: object):
        """Appends a node to the end of the list.

        Time complexity: O(n)
        """

        new_node = TwoWayNode(data)

        if self._head is None:
            self._head = new_node
        else:
            current = self._head

            while current.next is not None:
                current = current.next

            current.next = new_node
            new_node.previous = current

        self._size += 1

    def itervalues(self):
        """Iterates over the list."""

        current = self._head

        while current is not None:
            yield current.value
            current = current.next

    def iteritems(self):
        """Iterates over the list."""

        current = self._head

        while current is not None:
            yield current
            current = current.next

    def insert(self, index: int, value: object):
        """Inserts item before index.

        Time complexity: O(n)

        Args:
            index: The index to insert the item before.
            value: The value to insert.
        Returns:
            None
        """

        if index < 0:
            index = self._size + index

        if index > self._size - 1:
            raise IndexError('Index out of range')

        if index == 0:
            self._head = TwoWayNode(value, self._head)

        else:
            for idx, item in enumerate(self):
                if idx == index:
                    new_node = TwoWayNode(value)
                    new_node.next = item

                    item.previous.next = new_node
                    item.previous = new_node
                    break

        self._size += 1

        return None

    def index(self, target: object) -> int:
        """Returns the index of the value.

        Raises ValueError if the value is not in the list.

        Time complexity: O(n)

        Args:
            target: The value to find the index of.

        Returns:
            int: The index of the value.
        """

        item: TwoWayNode
        for index, item in enumerate(self):
            if item.value == target:
                return index

        raise ValueError('Value not found')

    def clear(self):
        """Clears the list."""

        self._head = None
        self._size = 0

    def replace(self, old_value: object, new_value: object):
        """Replaces the old value with the new value.

        Raises ValueError if the value is not in the list.

        Time complexity: O(n)

        Args:
            old_value: The value to replace.
            new_value: The new value.

        Returns:
            None
        """

        item: TwoWayNode
        for item in self:
            if item.value == old_value:
                item.value = new_value
                return None

        raise ValueError('Value not found')

    def pop(self, raw: bool = False) -> Union[object, TwoWayNode]:
        """Removes the last item and returns it.

        Time complexity: O(n)

        Args:
            raw: If True, returns the node instead of the value.

        Returns:
            object: The last item.
        """

        if self._head is None:
            raise IndexError('List is empty')

        item: TwoWayNode
        for item in self:
            pass

        if item.previous is not None:
            item.previous.next = None

        self._size -= 1

        if raw:
            return item

        return item.value

    def remove(self, target: object):
        """Removes the target from the list.

        Raises ValueError if the value is not in the list.

        Time complexity: O(n)

        Args:
            target: The value to remove.

        Returns:
            None
        """

        item: TwoWayNode
        for item in self:
            if item.value == target:
                if item.previous is not None:
                    item.previous.next = item.next

                if item.next is not None:
                    item.next.previous = item.previous

                self._size -= 1

                return None

        raise ValueError('Value not found')

    def reverse(self):
        """Reverses the double linked list.

        Time complexity: O(n)
        """

        temp = None
        current = self._head

        # Swap next and previous for all nodes of the list
        while current is not None:
            temp = current.previous
            current.previous = current.next
            current.next = temp
            current = current.previous

        # Before changing the head, check for the cases like empty list
        if temp is not None:
            self._head = temp.previous

    def __getitem__(self, index: int) -> object:
        """Returns the item at the index.

        Raises IndexError if the index is out of range.

        Time complexity: O(n)

        Args:
            index: The index to get the item at.

        Returns:
            object: The item at the index.
        """

        if index < 0:
            index = self._size + index

        if index > self._size - 1:
            raise IndexError('Index out of range')

        item: TwoWayNode
        for idx, item in enumerate(self):
            if idx == index:
                return item.value

        return None

    def __setitem__(self, index: int, value: object):
        """Sets the item at the index.

        Raises IndexError if the index is out of range.

        Time complexity: O(n)

        Args:
            index: The index to set the item at.
            value: The value to set.

        Returns:
            None
        """

        if index < 0:
            index = self._size + index

        if index > self._size - 1:
            raise IndexError('Index out of range')

        if index == 0:
            self._head.value = value

        else:

            item: TwoWayNode
            for idx, item in enumerate(self):
                if idx == index:
                    item.value = value

                    break

        return None

    def __iter__(self):
        return self.iteritems()

    def __len__(self) -> int:
        """Returns the size of the list."""

        return self._size

    def __delitem__(self, index: int):
        """Deletes the item at the index.

        Raises IndexError if the index is out of range.

        Time complexity: O(n)

        Args:
            index: The index to delete the item at.

        Returns:
            None
        """

        if index < 0:
            index = self._size + index

        if index > self._size - 1:
            raise IndexError('Index out of range')

        if index == 0:
            self._head = self._head.next

        else:

            item: TwoWayNode
            for idx, item in enumerate(self):
                if idx == index:
                    if item.previous is not None:
                        item.previous.next = item.next

                    if item.next is not None:
                        item.next.previous = item.previous

                    self._size -= 1

                    break

        return None

UnitTest

# Unittest
import unittest

# Utils
from double_linked_list import DoubleLinkedList


class DoubleLinkedListTestCase(unittest.TestCase):
    """Test case for DoubleLinkedList"""

    def test_initialize_empty_list(self):
        """Test initialize empty list"""
        dll = DoubleLinkedList()
        self.assertEqual(dll.size, 0)

    def test_initialize_list_with_values(self):
        """Test initialize list with values"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        self.assertEqual(dll.size, 3)

        for a, b in zip(['a', 'b', 'c'], dll):
            self.assertEqual(a, b)

    def test_append_item(self):
        """Test append item"""
        dll = DoubleLinkedList()
        dll.append('a')
        self.assertEqual(dll.size, 1)

    def test_insert_item_at_first_position(self):
        """Test insert item at first position"""
        dll = DoubleLinkedList(1)
        dll.insert(0, 'a')
        self.assertEqual(dll.size, 2)

        for a, b in zip(['a', 1], dll):
            self.assertEqual(a, b)

    def test_insert_item(self):
        """Test insert item"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        dll.insert(1, 'd')
        self.assertEqual(dll.size, 4)

        for a, b in zip(['a', 'd', 'b', 'c'], dll):
            self.assertEqual(a, b)

    def test_index_item(self):
        """Test index item"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        self.assertEqual(dll.index('b'), 1)

    def test_index_item_not_found(self):
        """Test index item not found"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        self.assertRaises(ValueError, dll.index, 'd')

    def test_clear_list(self):
        """Test clear list"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        dll.clear()
        self.assertEqual(dll.size, 0)

    def test_replace_item(self):
        """Test replace item"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        dll.replace('b', 'd')
        self.assertEqual(dll.size, 3)

        for a, b in zip(['a', 'd', 'c'], dll):
            self.assertEqual(a, b)

    def test_replace_item_not_found(self):
        """Test replace item not found"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        self.assertRaises(ValueError, dll.replace, 'd', 'e')

    def test_replace_by_index(self):
        """Test replace by index"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        dll[1] = 'd'
        self.assertEqual(dll.size, 3)

        for a, b in zip(['a', 'd', 'c'], dll):
            self.assertEqual(a, b)

    def test_pop_item(self):
        """Test pop item"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        self.assertEqual(dll.pop(), 'c')
        self.assertEqual(dll.size, 2)

        for a, b in zip(['a', 'b'], dll):
            self.assertEqual(a, b)

    def test_remove_item_by_index(self):
        """Test remove item by index"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        del dll[1]
        self.assertEqual(dll.size, 2)

        for a, b in zip(['a', 'c'], dll):
            self.assertEqual(a, b)

    def test_reverse_list(self):
        """Test reverse list"""
        dll = DoubleLinkedList(['a', 'b', 'c'])
        dll.reverse()
        self.assertEqual(dll.size, 3)

        for a, b in zip(['c', 'b', 'a'], dll):
            self.assertEqual(a, b)

No me esta gustando el curso, todo lo está dejando como reto, más bien digan que les faltó contenido y fin

Cómo utilizar listas doblemente enlazadas

Las listas doblemente enlazadas se diferencian de las listas enlazadas individualmente en que tienen dos referencias:

  • El previous campo hace referencia al nodo anterior.
  • El next campo hace referencia al siguiente nodo.

El resultado final se ve así:

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

Este tipo de implementación le permitiría atravesar una lista en ambas direcciones en lugar de solo atravesar usando next. Puede utilizar next para avanzar y previous retroceder.

En términos de estructura, así es como se vería una lista doblemente enlazada:

Tal vez ayude a comprender:

Pense que iba a ser muchisimo mas dificil hacer el reto de double circular linked list. Pero en realidad hizo hacer los metodos mas facil y simple.

Solución al reto:

from node import Node, TwoWayNode

class TwoWayListSingle:

    POSITION = 1
    DATA = 2

    def __init__(self, node = None, size = 0):
        self.__tail = node
        self.__size = 0

    def load_node(self, init, end):
        node = None
        for count in range(init, end):
            if node is None:
                node = TwoWayNode(count)
                node.previous = node
                node.next = node
                first_node = node
            else:
                previous_node = node
                node = TwoWayNode(count, previous_node, node.next)
                previous_node.next = node
            self.__size += 1
        
        node.next = first_node
        first_node.previous = node

        self.__tail = first_node
        
    def __str__(self):
        node = self.__tail
        ret = ""
        if self.__size > 0:
            position = 1
            while position <= self.__size:
                ret += f'Posición {position} dato {node.data}\n\r'
                node = node.next
                position += 1
        return ret

    def print_double(self, register):
        node = self.__tail
        ret = ""
        if self.__size > 0:
            position = 1
            while register > position:
                ret += f'Posición {position} dato {node.data}\n\r'
                node = node.next
                position += 1
        print(ret)

    #Agregamos nodos en cualquier posición permitida
    def add(self, position, node):
        if self.__tail is None:
            self.__tail = node
            node.next = node
            node.previous = node
            self.__size += 1
        elif position <= 0:
            #Se registra al principio
            first_node = self.__tail 
            node.next = first_node
            first_node.previous = node
            self.__tail = node
            #Adquirimos el último nodo
            last_node = self.get_last_node()
            last_node.next = self.__tail
            self.__tail.previous = last_node
            self.__size += 1
        else:
            try:
                #Obtenemos el nodo en la posición previa a donde insertar
                find_previous_node_position = self.__find_by_position(position - 1)
                print(f'find_previous_node_position: {find_previous_node_position.data}')
                assert not find_previous_node_position is None, f'The position {position} is not allowed'
                #Al nuevo nodo le agregamos el siguiente nodo del nodo encontrado, si tiene dato, es decir, no es el último
                find_previous_node_position.previous.next = node
                node.previous = find_previous_node_position.previous
                node.next = find_previous_node_position
                find_previous_node_position.previous = node
                self.__size += 1
            except AssertionError as ae:
                print(ae)

    def get_last_node(self):
        return self.__find_last_node()

    def __find_last_node(self):
        node = self.__tail

        if self.__size < 2:
            return self.__tail

        first_node = self.__tail
        position = 1
            
        while node.next != first_node:
            node = node.next
            position += 1
        return node

    def __find_by_position(self, position):
        node_ret = self.__tail
        item = 1
        while node_ret != None and item != position:
            node_ret = node_ret.previous
            item += 1

        return node_ret

    def __find_by_data(self, data):
        node_ret = self.__tail
        while node_ret != None and node_ret.data != data:
            node_ret = node_ret.next
        return node_ret

    def __find_position_by_data(self, data):
        position_ret = 1
        node = self.__tail
        while node != None and node.data != data:
            position_ret += 1
            node = node.next
        return position_ret

    def update(self, by, search, data):
        try:
            assert by in (self.POSITION, self.DATA), f'{by} no es un tipo de dato mi_singlylistlinked_simple.mi_singlylistlinked_simple_by'
            if by == self.POSITION:
                node = self.__find_by_position(search)
            if by == self.DATA:
                node = self.__find_by_data(search)
            assert not node is None, f'The node {search} not found'
            node.data = data
        except AssertionError as ae:
            print(ae)


    def remove(self, by, search):
        try:
            assert by in (self.POSITION, self.DATA), f'{by} no es un tipo de dato mi_singlylistlinked_simple.mi_singlylistlinked_simple_by'
            if by == self.POSITION:
                #Si es el primer nodo
                if search == 1:
                    #Movemos una posición
                    self.__tail = self.__tail.next
                    last_node = self.__find_last_node()
                    last_node.next = self.__tail
                    self.__size -= 1
                    return True
                #Buscamos el nodo en la posición
                node = self.__find_by_position(search - 1)
            if by == self.DATA:
                #Obtenemos la posición del dato del nodo y llamamos a la misma función
                return self.remove(self.POSITION, self.__find_position_by_data(search))

            assert not node is None, f'The node {search} not found'
            node.next = node.next.next
            node.previous = node.previous
            self.__size -= 1
            return True
        except AssertionError as ae:
            print(ae)
            return False
    

    

Como implementarlo:

from mi_twowaynodelistlinked_simple import TwoWayListSingle
from node import Node

def run():
    linked_list = TwoWayListSingle()
    linked_list.load_node(1, 6)
    print(linked_list)
    linked_list.add(3, Node('W'))
    print(linked_list)
    linked_list.add(6, Node('Z'))
    print(linked_list)
    linked_list.add(9, Node('CX'))
    print(linked_list)
    linked_list.update(TwoWayListSingle.POSITION, 5, 'Actualizar')
    print(linked_list)
    linked_list.update(TwoWayListSingle.DATA, 'Actualizar', 'Nuevamente actualziado')
    print(linked_list)

if __name__ == '__main__':
    run()

Mi aporte:

Prometo hacer mejoras. Por ahora hice esto:

class Node(object):
    def __init__(self, data, next = None):
        self.data = data
        self.next = next


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


    def add_after(self, node):
        node = TwoWayNode(node)
        if self.next == None:
            self.next = node
            node.previous = self
        else:
            probe = self
            while probe.next != None:
                probe = probe.next
            probe.next = node
            node.previous = probe


    def find_node(self, data):
        if self.data == data:
            return (f'Se ha encontrado {self.data}')
        elif self.next == None:
            return (f'No existe ese dato')
        else:
            return self.next.find_node(data)

Doubly*

Les comparto mi reto de la clase .

Salida:

5
4
3
2
1
target item 3 has been found
target item 10 is not in the linked list
the final item "F" aim: I
8 replaced the old value in the node number 4
we remove: F
Now the final item "5" aim: I
we remove: 3
5
8
2
9
1
I
from node import Node

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


class CircleDoublelinkedlist:
    def __init__(self):
        self.head = TwoWayNode(1)
        self.tail = self.head
    
        
    def ranger(self):
        for data in range(2, 6):
            self.tail.next = TwoWayNode(data, self.tail)
            self.tail = self.tail.next
            
        

    def travel(self):
        probe = self.tail
        while probe != None:
            print(probe.data)
            probe = probe.previous


    def add_elements(self, new_final, new_start):
        self.tail = TwoWayNode(new_final, self.tail)
        self.tail.previous.next = self.tail
        new_node = TwoWayNode(new_start)
        if self.tail is None:
            self.tail = new_node
        else:
            probe = self.tail
            while probe.previous != None:
                probe = probe.previous
            probe.previous = new_node
            new_node.next = probe
            

    def circular(self):
        probe = self.tail
        while probe.previous != None:
            probe = probe.previous 
        self.tail.next = probe
        print(f'the final item "{self.tail.data}" aim: {self.tail.next.data}')

        
    def search(self, data):
        probe = self.tail

        target_item = data

        while probe != None and target_item != probe.data:
            probe = probe.previous

        if probe != None:
            print(f'target item {target_item} has been found')
        else:
            print(f'target item {target_item} is not in the linked list')
            

    def replace(self, data, new_item):
        probe = self.tail
        target_item = data

        while probe != None and target_item != probe.data:
            probe = probe.previous

        if probe != None:
            probe.data = new_item
            print(f'{new_item} replaced the old value in the node number {target_item}')
        else:
            print(f"the target item {target_item} is not in the linked list")

    def romove_element_of_tail(self):
        removed_item = self.tail.data
        
        try: 
            if self.tail.next.previous is None:
                removed_item = self.tail.data
                self.tail = self.tail.previous 
                self.tail.next = self.tail.next.next
                print(f'we remove: {removed_item}')
                print(f'Now the final item "{self.tail.data}" aim: {self.tail.next.data}')
        except AttributeError:
            probe = self.tail
            while probe.next != None:
                probe = probe.next
            removed_item = probe.data
            self.tail = self.tail.previous 
            self.tail.next = None
            print(f'we remove: {removed_item}')

    def add_item(self, new_item, index):
        try:
            index = int(index)
        except ValueError:
            print(f'El indice "{index}" no es un numero')
            return False

        if self.tail is None or index < 0:
            self.tail = TwoWayNode('py', self.tail)
        else:
            probe = self.tail.next
            while index > 1 and probe.next != None:
                probe = probe.next
                index -= 1 
            if probe.next.previous is None:
                probe.next = TwoWayNode(new_item, probe, probe.next)
                self.tail = probe.next  
                print(f'the final item "{self.tail.data}" aim: {self.tail.next.data}')  
            else:
                probe.next = TwoWayNode(new_item, probe, probe.next)
                probe.next.next.previous = probe.next

    def remove_element_in_index(self, index):
        if index <= 0 or self.tail.next is None: 
            remove_item = self.tail.data
            self.tail = self.tail.next
            print(remove_item)
        else:
            probe = self.tail
            while index > 1 and probe.next.next != None:
                probe = probe.next
                index -= 1
            if probe.next.next.previous is None:
                remove_item = self.tail.data
                self.tail = self.tail.previous
                self.tail.next = self.tail.next.next
                print(f'we remove: {remove_item}')
                print(f'Now the final item "{self.tail.data}" aim: {self.tail.next.data}') 
            else:   
                remove_item = probe.next.data
                probe.next = probe.next.next
                probe.next.previous = probe
                print(f'we remove: {remove_item}')

            
if __name__ == '__main__':
    words = CircleDoublelinkedlist()
    words.ranger()
    words.travel()
    words.add_elements('F', 'I')
    words.search(3)
    words.search(10)
    words.circular()
    words.replace(4, 8)
    words.romove_element_of_tail()
    words.add_item(9, 2)
    words.remove_element_in_index(5)
    
    words.travel()

Agregué varios métodos más a la clase:

Aquí mi solución al reto!

class DoubleCircularLinkedList():
    def __init__(self):
        self.head = None
        self.tail = None
        self.size = 0

    
    def append(self, data):
        node = TwoWayNode(data)
        if self.head == None:
            self.head = node
            self.head.next = self.head
            self.tail = self.head
            self.size += 1
        else:
            probe = self.head
            while probe.next != self.head:
                probe = probe.next
            probe.next = node
            node.next = self.head
            node.previous = probe
            self.tail = node
            self.size += 1

    
    def inverse_iter(self):
        probe = self.tail
        while probe != None:
            val = probe.data
            probe = probe.previous
            yield val


    def iter(self):
        probe = self.head
        size = self.size
        while size > 0:
            val = probe.data
            probe = probe.next
            size -= 1
            yield val

    
    def clear(self):
        self.head = None
        self.tail = None
        self.size = 0


    def delete(self, data):
        probe = self.tail
        while probe.previous != None and probe.data != data:
            probe = probe.previous
        if probe.data == data and self.head == self.tail:
            self.clear()
        elif probe.data == data:
            if probe == self.tail:
                self.tail = probe.previous
            probe.previous.next = probe.next
            if probe.next != self.head:
                probe.next.previous = probe.previous
            self.size -= 1

reto de doubly circular linked list con algunos de los metodos, no todos

from node import TwoWayNode



class DoublyCircularLinkedList:
    def __init__(self):
        self.head = None
        # self.tail = None
        self.size = 0

    
    def append(self, data):
        
        if self.size == 0:
            self.head = TwoWayNode(data)
            self.head.next = self.head
            self.head.previous = self.head
        else:
            probe = self.head
            while probe.next != self.head:
                probe = probe.next
            probe.next = TwoWayNode(data, probe, self.head)
            self.head.previous = probe.next
        self.size += 1


    def size(self):
        return str(self.size)
    

    def iter(self):
        current = self.head
        size = self.size
        while size != 0:
            val = current.data
            current = current.next
            size -= 1
            yield val


    def delete(self, data):
        current = self.head
        previous = self.head

        while current.next != self.head:
            if current.data == data:
                if current == self.head:
                    self.head = current.next
                    self.size -= 1
                    return current.data
                else:
                    previous.next = current.next
                    self.size -= 1
                    return current.data
            
            previous = current
            current = current.next


    def clear(self):
        self.head = None
        self.size = 0




if __name__ == "__main__":

    #creating list

    dclist = DoublyCircularLinkedList()

    #adding nodes
    dclist.append(1)
    dclist.append(2)
    dclist.append(3)
    dclist.append(4)
    dclist.append(5)
    # testing methods
    
    print("list size ",dclist.size)
    print("*" * 50)
    for data in dclist.iter():
        print(data)
    dclist.delete(3)
    print("*" * 50)
    for data in dclist.iter():
        print(data)
    print("list size ",dclist.size)
    print("*" * 50)
    dclist.clear()
    for data in dclist.iter():
        print(data)
    print("list size ",dclist.size)