La famosa calculadora portátil HP-35 (de 1972) popularizó la notación polaca inversa (o notación prefijo) para hacer cálculos sin necesidad de usar paréntesis. Esa notación, inventa- da por el lógico polaco Jan Lukasiewicz en 1920, se basa en el principio de que un operador siempre se escribe a continuación de sus operandos. La operación (5 − 3) + 8 se escribirá como 5 3 - 8 +, que se interpretará como: “restar 3 de 5, y al resultado sumarle 8”.
Es posible implementar esta notación de manera sencilla usando una pila de la siguiente manera, a partir de una cadena de entrada de valores separados por blancos:
Mientras se lean números, se apilan.
En el momento en el que se detecta una operación binaria +, -, *, / o % se desapilan los dos últimos números apilados, se ejecuta la operación indicada, y el resultado de esa operación se apila.
Si la expresión está bien formada, tiene que quedar al final un único número en la pila (el resultado).
Los posibles errores son:
• Queda más de un número al final (por ejemplo si la cadena de entrada fue "5 3"), • Ingresa algún caracter que no se puede interpretar ni como número ni como una de
las cinco operaciones válidas (por ejemplo si la cadena de entrada fue "5 3 &") • No hay suficientes operandos para realizar la operación (por ejemplo si la cadena de
entrada fue "5 3 - +").
182 Unidad 17. Pilas y colas
Dada una cadena con la expresión a evaluar, podemos separar sus componentes utilizando el método split(). Recorreremos luego la lista de componentes realizando las acciones indi- cadas en el párrafo anterior, utilizando una pila auxiliar para operar. Si la expresión está bien formada devolveremos el resultado, de lo contrario levantaremos una excepción (devolvere- mos None).
En el Código 17.1 está la implementación de la calculadora descripta. Veamos algunos casos de prueba:
El caso de una expresión que es sólo un número (es correcta): >>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 5 DEBUG: 5
DEBUG: apila 5.0 5.0
El caso en el que sobran operandos: >>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 4 5 DEBUG: 4
DEBUG: apila 4.0 DEBUG: 5
DEBUG: apila 5.0
DEBUG: error pila sobran operandos Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "calculadora_polaca.py", line 64, in main
print calculadora_polaca(elementos)
File "calculadora_polaca.py", line 59, in calculadora_polaca
raise ValueError("Sobran operandos") ValueError: Sobran operandos
El caso en el que faltan operandos: >>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 4 % DEBUG: 4
DEBUG: apila 4.0 DEBUG: %
DEBUG: desapila 4.0
DEBUG: error pila faltan operandos Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "calculadora_polaca.py", line 64, in main
print calculadora_polaca(elementos)
File "calculadora_polaca.py", line 37, in calculadora_polaca
raise ValueError("Faltan operandos") ValueError: Faltan operandos
17.1. Pilas 183 Código 17.1calculadora_polaca.py:Una calculadora polaca inversa
1 #!/usr/bin/env python 2 #encoding: latin1 3
4 from clasePila import Pila 5
6 def calculadora_polaca(elementos):
7 """ Dada una lista de elementos que representan las componentes de 8 una expresión en notacion polaca inversa, evalúa dicha expresión. 9 Si la expresion está mal formada, levanta ValueError. """
10
11 p = Pila()
12 for elemento in elementos: 13 print "DEBUG:", elemento
14 # Intenta convertirlo a número
15 try:
16 numero = float(elemento)
17 p.apilar(numero)
18 print "DEBUG: apila ", numero
19 # Si no se puede convertir a número, debería ser un operando 20 except ValueError:
21 # Si no es un operando válido, levanta ValueError
22 if elemento not in "+-*/ %" or len(elemento) != 1: 23 raise ValueError("Operando inválido")
24 # Si es un operando válido, intenta desapilar y operar
25 try:
26 a1 = p.desapilar()
27 print "DEBUG: desapila ",a1
28 a2 = p.desapilar()
29 print "DEBUG: desapila ",a2
30 # Si hubo problemas al desapilar
31 except ValueError:
32 print "DEBUG: error pila faltan operandos" 33 raise ValueError("Faltan operandos")
34 35 if elemento == "+": 36 resultado = a2 + a1 37 elif elemento == "-": 38 resultado = a2 - a1 39 elif elemento == "*": 40 resultado = a2 * a1 41 elif elemento == "/": 42 resultado = a2 / a1 43 elif elemento == " %": 44 resultado = a2 % a1
45 print "DEBUG: apila ", resultado
46 p.apilar(resultado)
47 # Al final, el resultado debe ser lo único en la Pila 48 res = p.desapilar()
49 if p.esPilaVacia(): 50 return res 51 else:
52 print "DEBUG: error pila sobran operandos" 53 raise ValueError("Sobran operandos")
54
55 def main():
56 expresion = raw_input("Ingrese la expresion a evaluar: ") 57 elementos = expresion.split()
184 Unidad 17. Pilas y colas El caso de un operador inválido:
>>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 4 5 & DEBUG: 4
DEBUG: apila 4.0 DEBUG: 5
DEBUG: apila 5.0 DEBUG: &
Traceback (most recent call last): File "<stdin>", line 1, in <module>
File "calculadora_polaca.py", line 64, in main
print calculadora_polaca(elementos)
File "calculadora_polaca.py", line 26, in calculadora_polaca
raise ValueError("Operando inválido") ValueError: Operando inválido
4 % 5
>>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 4 5 % DEBUG: 4 DEBUG: apila 4.0 DEBUG: 5 DEBUG: apila 5.0 DEBUG: % DEBUG: desapila 5.0 DEBUG: desapila 4.0 DEBUG: apila 4.0 4.0 (4 + 5) * 6: >>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 4 5 + 6 * DEBUG: 4 DEBUG: apila 4.0 DEBUG: 5 DEBUG: apila 5.0 DEBUG: + DEBUG: desapila 5.0 DEBUG: desapila 4.0 DEBUG: apila 9.0 DEBUG: 6 DEBUG: apila 6.0 DEBUG: * DEBUG: desapila 6.0 DEBUG: desapila 9.0 DEBUG: apila 54.0 54.0
17.1. Pilas 185
4 * (5 + 6):
>>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 4 5 6 + * DEBUG: 4 DEBUG: apila 4.0 DEBUG: 5 DEBUG: apila 5.0 DEBUG: 6 DEBUG: apila 6.0 DEBUG: + DEBUG: desapila 6.0 DEBUG: desapila 5.0 DEBUG: apila 11.0 DEBUG: * DEBUG: desapila 11.0 DEBUG: desapila 4.0 DEBUG: apila 44.0 44.0 (4 + 5) * (3 + 8): >>> calculadora_polaca.main()
Ingrese la expresion a evaluar: 4 5 + 3 8 + * DEBUG: 4 DEBUG: apila 4.0 DEBUG: 5 DEBUG: apila 5.0 DEBUG: + DEBUG: desapila 5.0 DEBUG: desapila 4.0 DEBUG: apila 9.0 DEBUG: 3 DEBUG: apila 3.0 DEBUG: 8 DEBUG: apila 8.0 DEBUG: + DEBUG: desapila 8.0 DEBUG: desapila 3.0 DEBUG: apila 11.0 DEBUG: * DEBUG: desapila 11.0 DEBUG: desapila 9.0 DEBUG: apila 99.0 99.0
Ejercicio 17.1. Si se oprime la tecla <BACKSPACE>(o <←>) del teclado, se borra el último caracter ingresado. Construir una función visualizar para modelar el tipeo de una cadena de caracteres desde un teclado:
186 Unidad 17. Pilas y colas
La función recibe una cadena de caracteres con todo lo que el usuario ingresó por tecla- do (incluyendo <BACKSPACE>que se reconoce como \b), y devuelve el texto tal como debe presentarse (por ejemplo, visualizar("Holas\b chau") debe devolver ’Hola chau’).
Atención, que muchas veces la gente aprieta de más la tecla de <BACKSPACE>, y no por eso hay que cancelar la ejecución de toda la función.
17.1.3. ¿Cuánto cuestan los métodos?
Al elegir de una representación debemos tener en cuenta cuánto nos costarán los métodos implementados. En nuestro caso, el tope de la pila se encuentra en la última posición de la lista, y cada vez que se apila un nuevo elemento, se lo agregará al final.
Por lo tanto se puede implementar el método apilar mediante un append de la lista, que se ejecuta en tiempo constante. También el método desapilar, que se implementa mediante pop de lista, se ejecuta en tiempo constante.
Vemos que la alternativa que elegimos fue barata.
Otra alternativa posible hubiera sido agregar el nuevo elemento en la posición 0 de la lista, es decir implementar el método apilar mediante self.items.insert(0,x) y el método desapilarmediante del self.items[0]. Sin embargo, ésta no es una solución inteligente, ya que tanto insertar al comienzo de la lista como borrar al comienzo de la lista consumen tiempo proporcional a la longitud de la lista.
Ejercicio 17.2. Diseñar un pequeño experimento para verificar que la implementación elegida es mucho mejor que la implementación con listas en la cual el elemento nuevo se inserta al principio de la lista.
Ejercicio 17.3. Implementar pilas mediante listas enlazadas. Analizar el costo de los métodos a utilizar.