Пример #1
0
class BinaryTreeTuple:

    # Sólo para tenerlo en cuenta, un binary tree está compuesto de una raíz (root), ramas (branches) y hojas (leafs);
    # otra manera de llamarle a las hojas es nodos (nodes). He aquí algunas observaciones sobre binary trees:
    #   1. La profundidad de un árbol se refiere a todos los niveles que tiene el árbol.
    #   2. La cardinalidad de un árbol es el número de nodos que este tiene.
    #   3. Un nodo podría no tener hijos y a lo mucho 2.
    #   4. Un nodo siempre tiene un padre (y sólo uno), excepto root.
    #   5. El descenso en árbol se hace a través de las ramas del mismo.
    #   6. Evaluar un nodo, después de ser encontrado durante el descenso, también se le dice "visitar".
    #
    # En esta implementación de binary tree usaremos la estrategia "bottom to top", lo que quiere decir que para obtener
    # la respuesta partimos de lo general y procesamos hasta llegar a lo particular; una vez que llegamos al caso
    # particular se construye la respuesta a partir de ese caso hasta regresar a lo general. En el caso de un binary
    # tree usualmente se dice que bajamos hasta lo más profundo de un árbol (lo cual se hace a través de sus ramas) y
    # encontrado o no lo que se busca, se construye hacía arriba la respuesta.
    #
    # Esta implementación del árbol binario usará el tipo de dato "tuple" como la hoja o nodo. El constructor
    # inicializa la raíz del árbol ("root") a None porque al momento no hay nodos en el árbol. Por conveniencia,
    # también habrá un stack que nos servirá para buscar números en el árbol, el cual servirá para llevar memoria
    # de los nodos visitados durante la búsqueda; este stack nos servirá para agregar un nuevo nodo al árbol, en caso
    # de ser necesario.

    def __init__(self):
        self.root = None
        self.stack = Stack()

    # Método "clásico" para saber si hay elementos en el árbol.
    def tree_empty(self):
        return self.root is None if True else False

    # Método para buscar un número en el árbol. Recibe un número como parámetro. Si encuentra el número devolverá
    # True, de lo contrario False.
    def number_in_tree(self, number: int) -> bool:
        # El resultado por default del método. En caso de que el número se encuentre, tomará el valor True.
        number_found = False

        # Para buscar el número, creamos un árbol temporal, que será igual al árbol existente.
        search_root = self.root

        # Con esta declaración reseteamos el stack de los resultados de la búsqueda.
        self.stack = None

        print('Buscar número "%i" en árbol.' % number)

        # Mientras el árbol de búsqueda no esté vacío o no se haya encontrado el número, la búsqueda continuará.
        # La condición del while se podría también escribir así: search_root is not None and not number_found. ¿Por qué
        # la escribí diferente? En este caso "not" es un factor común de ambas evaluaciones, así que lo podemos sacar
        # para afectar ambas variables, en otras palabras, negar toda la expresión, en vez de hacerlo individualmente;
        # ahora, como estamos negando toda la expresión, el "and" debe cambiar a "or", es decir, la operación contraria.
        while not (search_root is None or number_found):

            # El tipo de dato tuple por si mismo puede descomponerse en sus partes, sin necesidad de hacer nada más.
            # En este caso, cada elemento del árbol es una tupla compuesta de 3 valores: el número, una rama
            # izquierda y una rama por la derecha. Python descompone (o explota) la tupla y asigna cada valor
            # a las variables de la asignación, lo cual hace muy conveniente. En este caso la tupla tiene 3 valores (en
            # este orden): nùmero, rama izquierda y rama derecha.
            tuple_num, left, right = search_root

            # Validar si el número que se busca es igual el que se obtuvo de la tupla.
            if not number == tuple_num:

                # Si el número no es igual al de la tupla, etonces guardamos la tupla para usarla después, si es que
                # agregaremos el número al árbol.
                self.stack.push(search_root)

                # Si el número que se busca es mayor al que se obtuvo de la tupla, entonces la búsqueda del
                # número continuará por la rama derecha, de lo contrario la búsqueda se irá por la izquierda.
                if number > tuple_num:

                    # El árbol de búsqueda será igual a la rama derecha de la tupla que validamos.
                    search_root = right
                else:

                    # De lo contario, el árbol de búsqueda será igual a la rama de la deracha de la tupla.
                    search_root = left
            else:

                # En caso de que el número se encuentre, entonces se devolverá True.
                print('El número %i ya existe en árbol.' % number)
                number_found = True

        # Devolver el resultado de la búsqueda.
        return number_found

    # Este método agregará al árbol el número que se pasa como parámetro, si es que este no existe ya en el árbol.
    def add_number(self, number: int):

        # Si el número no existe en el árbol, entonces agregarlo al árbol.
        if not self.number_in_tree(number):
            print('Add Number "%i".' % number)

            # Se crea una tupla para el número que se pasó como parámetro.
            new_tree = tuple([number, None, None])

            # El método "number_in_tree" crea lo que llamaremos el stack de la respuesta. Con ese stack podemos
            # construir el árbol que incluye el número que obtuvimos como parámentro.
            while not self.stack.stack_empty():

                # Se obtine la tupla y se descompone en sus valores atómicos, es decir, el número, rama izquierda y
                # rama derecha.
                node_value, node_left, node_right = self.stack.pop()

                # Si el número que se pasó como parámetro es mayor al valor de la tupla, entonces la nueva tupla
                # se asigna como la rama derecha de la tupla que se obtuvo del stack, de lo contrario se asignará
                # a la rama izquierda.
                if number > node_value:
                    node_right = new_tree
                else:
                    node_left = new_tree

                # Ahora el árbol nuevo será igual a la rama recién creada.
                new_tree = tuple([node_value, node_left, node_right])

            # Finalmente, una vez reconstruido el árbol con el número que recibimos, se asigna a la ráiz del árbol.
            self.root = new_tree

            # Mostrar el árbol final.
            print('Árbol: %s' % str(self.root))
class UnaCalculadoraMuySimple(Queue):
    # Esta es una implementación MUY simple de una calculadora... Y a pesar de ello, como sea quedó un código
    # medio largo, pero espero que no sea dificil de comprender. No estoy validando la precedencia de operadores,
    # tampoco si hay parentesis. Los dígitos y operadores se evaluaran en el orden que estén en la cadena y el
    # resultado de la operación anterior se acumulará para ser utilizado en la siguiente operación.

    # Método constructor que podría recibir una expresión para inicializar el objeto.
    def __init__(self, calculation=None):
        # Inicialización de la clase Queue con la cadena que se pase como parámetro.

        # Tal como pasa con la declaración de Queue (donde inicializamos la clase "list" de la que se hereda)
        # hacemos lo mismo en esta clase al inicializar Queue.
        Queue.__init__(self, calculation)

        # Declaración de stacks para llevar control de las operaciones y dígitos.
        self.opers = Stack()
        self.numbers = Stack()

    # Método para pasar una expresión al objeto.
    def set_calculation(self, calculation):
        self.__init__(calculation)

    # Muestra la cadena que se pasó para evaluar.
    def show_calculation(self):
        self.show_queue()

    # Evaluar y resolver expresión.
    def sort_calculation(self):
        rslt = None  # Con esta variable se devolverá el mensaje de error, en caso de haber alguno.
        calculation = 0  # Variable con la que se "acumulará" el resultado de las operaciones.

        # Primero revisar que se haya pasado alguna cadena para evaluarse.
        if not self.queue_empty():
            numero = ''

            # Dado que la cadena no está vacía, vamos a leerla mientras haya valores.
            while not self.queue_empty():
                # Obtenemos el siguiente elemento de la cadena.
                element = self.dequeue()

                # Si el elemento es un espacio, entonces ignorarlo.
                if not element == ' ':

                    # Si es una operación, meterla al stack de operaciones.
                    if element in ('+', '-', '*', '/'):

                        # Cuando se encuentra una operación quiere decir que no hay más números a concatenar, así que
                        # el número completo se mete al stack de números, luego se reinicia la variable.
                        if not numero == '':
                            self.numbers.push(float(numero))
                            numero = ''
                        else:
                            # En caso de haber una operación, pero no un número, entonces la expresión es inválida.
                            break

                        self.opers.push(element)
                    elif element.isdigit() or (element == '.'
                                               and not numero == ''):

                        # Dado que la expresión se evalua elemento por elemento, se obtendrá dígito por dígito,
                        # entonces según aparezcan se irán concatenado para formar el número completo. En caso de que
                        # el número tenga decimales, concatenar el punto al número completo.
                        numero = element + numero
                    else:

                        # En caso de que haya un elemento que no sea una operación o dígito, devolver error.
                        rslt = 'La cadena del cálculo es inválida.'
                        break  # Este comando es para terminar (o salir) el ciclo.

            if not numero == '':
                print('Número completo: %s.' % numero)
                self.numbers.push(float(numero))

            # Si no hay un mensaje de error, hay operandores que procesar y al menos 2 digitos,
            # se procede a evaluar la expresión.
            if rslt is None and not (self.opers.stack_empty()
                                     or self.numbers.stack_size() < 2):
                # Tomamos el primer valor a evaluar. Esto podría interprestarse como el primer
                # resultado de evaluar la expresión.
                calculation = self.numbers.pop()

                # Ciclarse mientras haya dígitos en el stack de dígitos.
                while not self.numbers.stack_empty():

                    # Proceder sólo si hay operaciones disponible.
                    if not self.opers.stack_empty():
                        # Sacar la siguiente operación disponible.
                        oper = self.opers.pop()

                        # Validar que haya otro dígito a considerar.
                        if not self.numbers.stack_empty():
                            # Tomar el siguiente dígito a evaluar.
                            digit = self.numbers.pop()

                            # Este es un mensaje de seguimiento para ver qué sucede durante la evaluación
                            # de la expresión.
                            print('Operación: %f %s %f' %
                                  (calculation, oper, digit))

                            # Ejecutar operación con el valor acumulado y el último dígito obtenido.
                            # El resultado de la operación se acumula en la variable "calculation".
                            if oper == '+':
                                calculation += digit
                            elif oper == '-':
                                calculation -= digit
                            elif oper == '*':
                                calculation *= digit
                            elif oper == '/':

                                # Esto es para evitar una excepción por dividir entre 0.
                                if not digit == 0:
                                    calculation /= digit
                                else:
                                    # En caso de que el dígito sea un cero, el valor acumulado al momento será cero.
                                    calculation = 0
                        else:
                            # Si falta un dígito para continuar con la evaluación de la expresión, devolver error.
                            rslt = 'Falta al menos un dígito para la operación %s.' % oper
                            break  # Este comando es para terminar el ciclo.
                    else:
                        # En caso que falté algún operador para seguir evaluando la expresión, devolver error.
                        rslt = 'Falta operando.'
                        break  # Este comando es para terminar el ciclo.
            else:
                # En caso de que haya un error previo, no haya operaciones o al menos 2 dígitos, devolver error.
                rslt = 'La cadena del cálculo es inválida.'
        else:
            # En caso de que no haya una expresión a evaluar, devolver error.
            rslt = 'No hay una expresión a procesar.'

        # Si no hubo algún error durante la evaluación de la expresión, mostrar el resultado...
        if rslt is None:
            print('El resultado del cálculo es: %s.' % str(calculation))
        else:  # ... De lo contrario mostrar el mensaje de error.
            print(rslt)
Пример #3
0
class BinaryTreeNode:
    # En función esta clase funciona igual que "BinaryTreeTuple", con la diferencia que usamos el objeto Node en vez
    # de tuple para almacenar el número y las ramas que salen de cada nodo. De ahí que no hondaré en explicaciones
    # porque los métodos hacen exactamente lo mismo que en "BinaryTreeTuple".
    def __init__(self):
        self.root = None
        self.stack = Stack()

    # Este método busca un número en el binary tree. Durante la búsqueda guardará los nodos que visita en caso de
    # este número se vaya a agregar al árbol.
    def is_num_tree(self, number: int) -> bool:
        # El valor default de la respuesta; dado que una posibilidad es que árbol esté vacío, entonces inmediatamente
        # brincará al return del método.
        node_found = False

        # Primero validar que haya nodos en el árbol.
        print('  Search %i in the tree.' % number)
        if self.root is not None:
            # Si no hay nodos en el árbol obtenemos la raíz del árbol; recordar que esta raíz podría tener más nodos
            # abajo de ella.
            print('  * Root is not empty, then proceed with the search.')
            root = self.root

            # Con esta declaración reseteamos el stack de búsqueda.
            print('  * Reset search stack.')
            self.stack = Stack()

            while not (root is None or node_found):
                print('  * Is %i equal to %i?' % (number, root.get_value()))
                if not number == root.get_value():

                    print('  * No, then add current node to the stack.')
                    self.stack.push(root)

                    print('  * Is %i less than %i?' % (number, root.get_value()))
                    if number < root.get_value():
                        print('  --> Yes, then search for it in the LEFT branch.')
                        root = root.get_left_node()
                    else:
                        print('  --> No, then search for it in the RIGHT branch.')
                        root = root.get_right_node()
                else:
                    print('  --> Yes, then the number exists in the tree.')
                    node_found = True

        if not node_found:
            print('  --> The number does ** NOT ** exists in the tree.')

        return node_found

    # Tal como lo hicimos en la clase "BinaryTreeTuple" este método agregará el número al binary tree.
    def add_number(self, number: int):
        # Buscamos el número en el árbol para saber si existe. Tal como en "BinaryTreeTuple" el stack que se genere
        # durante la búsqueda nos servirá para armar el nuevo árbol con el número que entra como parámetro.

        print('* First, check if %i exists in the tree.' % number)
        if not self.is_num_tree(number):

            # De entrada este nodo será el que tenga el valor que queremos agregar al árbol, pero reutilizaremos
            # este objeto para construir el árbol que incluya el nodo nuevo.
            print('* Add %i to the tree.' % number)
            new_tree = Node(number)

            # Ahora vamos a traer los nodos que se guardaron durante la búsqueda para hacerlos parte del nuevo
            # árbol que incluye el nodo nuevo.
            while not self.stack.stack_empty():
                stack_node = self.stack.pop()

                # Vemos en qué rama debe ir el árbol con la respuesta.
                if new_tree.get_value() > stack_node.get_value():
                    stack_node.set_right_node(new_tree)
                else:
                    stack_node.set_left_node(new_tree)

                # Asignamos el árbol recién armado al nodo que trae el resultado de agregar el nuevo nodo.
                new_tree = stack_node

            # Una vez que todos los nodos han sido considerados, el árbol final que quedó en "new_tree" es el árbol
            # anterior, pero incluyendo el nodo nuevo, así que "new_tree" se convierte en root.
            self.root = new_tree
            print('--> Number added to the tree.')