Ayuda Calculadora

Iniciado por vhh70, 1 Junio 2018, 23:01 PM

0 Miembros y 1 Visitante están viendo este tema.

vhh70

Buen dia, espero puedan ayudarme estoy haciendo un programa de Notacion Polaca, mi problema es que me hace falta una funcion que al leer la cadena esta funcion realice la operacion (como una calculadora).
Por ejemplo:
(2+6)*10=80

Tengo esta funcion pero solo es para numeros de un digito, tengo pensado usar apuntadores pero no se como.

//MOSTRAMOS EL RESUTALTADO DE LA OPERACION
void resultado(char cadena[100]){
char operador, operandoX, operandoY; //VARIABLES PARA PODER OPERAR
int resultado, pilaResultado[100]; //PILA PARA GUARDAR LOS RESULTADOS

printf("\n\nSolucion");

for(int i = 0, j = -1; i < strlen(cadena); i++){ //RECORREMOS LA CADENA
if((((int(cadena[i])) > 47) && ((int(cadena[i])) < 58))){ //SI SE LEE UN NUMERO ENTONCES SE AGREGA A LA PILA DE RESULTADOS
j++; //AUMETAMOS EL TOPE DE LA PILA
pilaResultado[j] = (int)cadena[i] - 48; //SE CASTEA EL CARECTER Y SE LE RESTAN 48 PARA PODER GUARDAR EL NUMERO QUE SE LEE COMO CARACTER
}else if(cadena[i] == '!'){ //SI SE LEE UNA OPERACION DE NEGACION
operandoX = pilaResultado[j]; //SE TOMA EL ULTIMO VALOR DE LA PILA DE OPERACION
resultado = operandoX * (-1); //SE MULTIPLICA POR -1
pilaResultado[j] = resultado; //SE GURADA EL RESULTADO EN EL TOPE DE LA PILA DE RESULTADOS
printf("\n\t!%i = %i", operandoX, resultado);
}else if(cadena[i] == '+'){ //SI SE LEE UNA OPERACION DE SUMA
operandoX = pilaResultado[j]; //SE EXTRAE EL TOPE DE LA PILA Y SE GUARDA EN OPERANDOX
j--; //SE DISMINUYE EL TOPE DE LA PILA
operandoY = pilaResultado[j]; //SE EXTRAE EL TOPE DE LA PILA Y SE GUARDA EN OPERANDOY
resultado = operandoX + operandoY; //RE OPERAN LAS VARIABLES OPERANDOX Y OPERANDOY
pilaResultado[j] = (int)resultado; //SE GUARDA EL RESULTADO
printf("\n\t%d + %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '-'){ //EN CASO DE QUE SE LEA UNA OPERACION DE RESTA
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
resultado = operandoY - operandoX;
pilaResultado[j] = resultado;
printf("\n\t%d - %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '*'){ //EN CASO DE QUE SE LEA UNA OPERACION DE MULTIPLICACION
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
resultado = operandoY * operandoX;
pilaResultado[j] = resultado;
printf("\n\t%d * %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '/'){ //EN CASO DE QUE SE LEA UNA OPERACION DE DIVISION
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
if(operandoX == 0)
resultado = 0;
else
resultado = operandoY / operandoX;
pilaResultado[j] = resultado;
printf("\n\t%d / %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '^'){ //EN CASO DE QUE SE LEA UNA OPERACION DE EXPONENTE
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
resultado = pow(operandoY,operandoX);
pilaResultado[j] = resultado;
printf("\n\t%d ^ %i = %i", operandoY, operandoX, resultado);
}
}
printf("\nResultado: %i", pilaResultado[0]); //SE MUESTRA EL RESULTADO FINAL
}



Nota:No puedo subir el codigo de notacion porque aun no me califican pero en cuanto me califique lo subire.

SrMcLister

Buenas!
Por favor, justifica mejor tu código, leer ese maraña de letras es imposible
Código (cpp) [Seleccionar]

return((u.areHappy() && u.knowIt()) ? u.clapYourHands() : u.goFuckYourself());

vhh70

Este es solo para una cifra, lo que necesito es que reciva mas de una cifra pero si lo hago con este necesiraria mas arreglos
/
void resultado(char cadena[100]){
char operador, operandoX, operandoY;
int resultado, pilaResultado[100];

printf("\n\nSolucion");

for(int i = 0, j = -1; i < strlen(cadena); i++){
if((((int(cadena[i])) > 47) && ((int(cadena[i])) < 58))){
j++; //AUMETAMOS EL TOPE DE LA PILA
pilaResultado[j] = (int)cadena[i] - 48;
}else if(cadena[i] == '!'){
operandoX = pilaResultado[j];
resultado = operandoX * (-1);
pilaResultado[j] = resultado;
printf("\n\t!%i = %i", operandoX, resultado);
}else if(cadena[i] == '+'){
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
resultado = operandoX + operandoY;
pilaResultado[j] = (int)resultado;
printf("\n\t%d + %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '-'){
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
resultado = operandoY - operandoX;
pilaResultado[j] = resultado;
printf("\n\t%d - %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '*'){
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
resultado = operandoY * operandoX;
pilaResultado[j] = resultado;
printf("\n\t%d * %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '/'){
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
if(operandoX == 0)
resultado = 0;
else
resultado = operandoY / operandoX;
pilaResultado[j] = resultado;
printf("\n\t%d / %i = %i", operandoY, operandoX, resultado);
}else if(cadena[i] == '^'){
operandoX = pilaResultado[j];
j--;
operandoY = pilaResultado[j];
resultado = pow(operandoY,operandoX);
pilaResultado[j] = resultado;
printf("\n\t%d ^ %i = %i", operandoY, operandoX, resultado);
}
}
printf("\nResultado: %i", pilaResultado[0]);
}

Serapis

#3
.... quedó duplicado, al tratar de modificarlo.

Serapis

#4
Es que has ido a lo fácil... operar con bytes (chars), resulta cómodo...

Bueno, la solución es que precisas una pequeña función que es un analizador léxico (en inglés lo suelen llamar scanner)... para ir leyendo la cadena de entrada y reconocer los números...

Te aproximo al resultado de lo que necesitas...


Se necesita unos estados para saber por donde andamos...y qué hacer a cada momento.
Nota como los valores constantes con más de 1 bit, su nombre cambia a ES_.... aunque luego no los usamos...

enumeracion AtributosDeCaracteres
   ATRIB_CHAR_DESCONOCIDO = 0
   ATRIB_CHAR_DIGITO = 1
   ATRIB_CHAR_OPERADOR_ASIGNA = 2
   ATRIB_CHAR_OPERADOR_ARIT = 4   ' aritmético... si incluyes buleanos, o comparadores, añade las constantes antes de parentesis (y desplaza esos con sus valores más abajo)
       ES_ATRIB_CHAR_OPERADOR = (ATRIB_CHAR_OPERADOR_ASIGNA or ATRIB_CHAR_OPERADOR_ARIT)
   ATRIB_CHAR_PARENTESIS_ABRE = 8
   ATRIB_CHAR_PARENTESIS_CIERRA = 16
       ES_ATRIB_CHAR_PARENTESIS = (ATRIB_CHAR_PARENTESIS_ABRE or ATRIB_CHAR_PARENTESIS_CIERRA)
   ATRIB_CHAR_ESPACIO = 99  // espacio o tabulador... espacio duro, va hacer difícil que aparezca.
fin enumeracion


Se precisa un array que contendrá los atributos (que nos interesan) de todos los caracteres (que nos interesan).

array bytes Atributos[0 a 255]


Y rellenar el array de atributos... (el resto de operadores te lo dejo para ti).

funcion Main
   // empezamos borrando la basura...
   bucle para k desde 0 a 255
       Atributos[k] = ATRIB_CHAR_DESCONOCIDO
   siguiente

   // los dígitos...
   bucle para k desde 48 a 57
       Atributos[k] = ATRIB_CHAR_DIGITO
   siguiente

   // operadores...
   Atributos[43] = ATRIB_CHAR_OPERADOR_ARIT    // '+'
   Atributos[45] = ATRIB_CHAR_OPERADOR_ARIT    // '-'
   Atributos[42] = ATRIB_CHAR_OPERADOR_ARIT    // '*'
      ... el resto de operadores a considerar

   Atributos[61] = ATRIB_CHAR_OPERADOR_ASIGNA  // '='

   // paréntesis...
   Atributos[40] = ATRIB_CHAR_PARENTESIS_ABRE  // '('
   Atributos[41] = ATRIB_CHAR_PARENTESIS_CIERRA  // ')'

   // espacios...
   Atributos[09] = ATRIB_CHAR_ESPACIO  // tab
   Atributos[32] = ATRIB_CHAR_ESPACIO  // spc
fin funcion


Ya tenemos la base, va faltando la función para obtener los tokens con el analizador léxico.

enumeracion TiposDeToken
   TOKEN_ERROR = 0
   TOKEN_NUMERO =1
   TOKEN_ASIGNA = 2
   TOKEN_OPERADOR =3
   TOKEN_PARENTESIS = 4
   
   TOKEN_IGNORA_ESPACIOS = 9
fin enumeracion

// devuelve el tipo de token hallado y por referencia un string con el valor del token, y el índice (se va actualizando)
// Parámetros:
//     Entrada: es el array de caracteres que se reciben...
//     Indice: es el índice actual siendo analizado. Es un parámetro por referencia.
//     Limite: es el tamaño del array (para no andar preguntando cada vez que se entra a la funcion).
//     Token: el valor a devolver para almacenar... puedes devolver un entero si lo prefieres...
// Devuelve: el tipo de token hallado. Error si el carácter no pertenece a ningún tipo de token que el programa reconoce.
TiposDeToken = funcion GetToken(array chars Entrada[], entero (ref) Indice, entero limite, string (ref) Token)
   AtributosDeCaracteres ch
   entero j, k

   Token = Entrada(indice)
   ch = Atributos[entrada[indice]]
   indice +=1
   Seleccionar caso ch
       caso ATRIB_CHAR_DIGITO            
           Hacer mientras (indice < limite)
               Si (Atributos[entrada[indice]] != ATRIB_CHAR_DIGITO )  // empieza otro token, devolver el número hallado.
                   devolver TOKEN_NUMERO
               fin si
               Token += Entrada(indice)  // concatena otro dígito...
               indice +=1
           repetir
           devolver TOKEN_NUMERO   // fin del array.
       caso ATRIB_CHAR_OPERADOR_ARIT
           devolver TOKEN_OPERADOR
       caso ATRIB_CHAR_OPERADOR_ASIGNA
           //token = "="
           devolver TOKEN_ASIGNA            
       caso ATRIB_CHAR_PARENTESIS_ABRE
           //token = "("
           devolver TOKEN_PARENTESIS
       caso ATRIB_CHAR_PARENTESIS_CIERRA
           //token = ")"
           devolver TOKEN_PARENTESIS
       caso ATRIB_CHAR_ESPACIO  // es idéntico a dígitos, salvando las distancias, y que aquí, no precisa devolver el string del Token
           Token = ""
           Hacer mientras (indice < limite)
               Si (Atributos[entrada[indice]] != ATRIB_CHAR_ESPACIO) // [s]TOKEN_IGNORA_ESPACIOS )[/s]  // fin de espacios...
                   devolver TOKEN_IGNORA_ESPACIOS
               fin si
               indice +=1
           repetir
           devolver TOKEN_IGNORA_ESPACIOS
       resto casos // ATRIB_CHAR_DESCONOCIDO
           Token = ""
           devolver TOKEN_ERROR
   fin seleccion
fin funcion


Y ahora falta la función general que recibe la entrada, genera los tokens partiendo de la entrada recibiday que manda almacenar...

buleano = funcion CalcularExpresion(char Cadena[100]
   entero j, k
   string Token
   TiposDeToken tTok

   k= tamaño de Cadena
   j=0
   Hacer mientras (j<K)
       tTok = GetToken(cadena, j, k,Token)
       Si (tTok = TOKEN_ERROR)
            mensaje de error
            devolver FALSE
       Sino
           Si (tTok != TOKEN_IGNORA_ESPACIOS)
               GuardarTokenEnPila(Token, tTok) // entrar Token para meter en la pila en la notación elegida (polaca, polaca inversa, etc...)
           fin si
       fin si
   Repetir

   // la función que toma la pila y evalúa la expresión... si la haces sobre la marcha,
   //   si no hay otra precedencia en operadores que el orden de entrada,
   //   esto sobraría... sino, no puede evaluarse hasta procesar toda la entrada.
   Calcular
   
   Devolver True
fin funcion


No he revisado tu código, pero a simple vista, parece subóptimo, de todos modos si dices que te funciona, pués listo...
Añade lo señalado y actualiza como convenga...
Ahora te llega un array de bytes-caracteres y el analizador va hallando los números (múltiples dígitos), operadores y parentesis, además ignora espacios y tabuladores y chequea por caracteres no admitidos...
Nota que es solo un analizador léxico, de expresiones aritméticas numéricas, si metes demasiados dígitos que superen el entero generará error, esa comprobación realmente corresponde al analizador semántico, pero para lo sencillo que es el programa, lo puedes verificar justo antes de llamar a almacenar en la pila... antes de "GuardarTokenEnPila", si es un token numérico.
Nota que te falta rellenar datos... en el array los valores de otros operadores... en Main




P.d.: Fe de erratas (la constante tachada no procede, si no la que se ha puesto a  en su sitio):
Si (Atributos[entrada[indice]] != ATRIB_CHAR_ESPACIO) TOKEN_IGNORA_ESPACIOS )  // fin de espacios...

vhh70

Muchas gracias.
Voy a checar pero como decia antes creo que seria mas sencillo si utilizo apuntadores o estoy equivocado.
Lo que se me complica con los apuntadores es como recorrerlo y luego hacer las operaciones.

Serapis

#6
Cita de: vhh70 en  2 Junio 2018, 21:53 PM
creo que seria mas sencillo si utilizo apuntadores o estoy equivocado.
mmm... no sabes lo que dices.
Tu dices sencillo, por ver 60 líneas de código, con las que no contabas?.
Es el código mínimo, escueto... 2 enumeraciones para saber con que se opera y qué se devuelve (aunque inicialmente son muy parecidas, cuando añades más tokens empieza a distanciarse el parecido) y 2 funciones (4 si se incluye la de meter tokens en la pila, y la última de efectuar el cálculo).

La primera función es la general, la que recibe la entrada chequea el carácter inicial y deriva hacia (la segunda función) el reconocimiento del token según dicho carácter (estado inicial). A su vuelta si no hubo errores y el reconocimiento fue efectivo, se invoca la 3ª función para almacenar el token, y finalmente cuando se termina de reconocer la entrada, toca realizar los cálculos.

Una calculadora la puedes hacer más simple si quieres:
Con la 'pantalla vacía', cuando empiece a escribir dígitos (basta 1 solo), está introduciendo el operando1, luego apenas el usuario introduzca un operador (pulsando la tecla, o escribiendo el carácter si no hay interfaz gráfica), almacenas el lo entrado como primer operando, cuando escriba otro dígito, almacenas el operador, y cuando de nuevo escriba otro operador efectúas la operación con los operandos previos, y el resultado se deja en el operando1... si el segundo operador era el símbolo "=", además muestras el resultado al usuario.
(nota que con la pantalla vacía ambos operandos tienen valor 0, luego si empieza introduciendo un operador, se considera almacenado como operando1 el valor 0).
Si no, cuando de nuevo introduzca un dígito, vuelves a guardar el operador, y tras un nuevo operador, los digitos recién metidos quedan almacenados como el 2º operando, y de nuevo se procesa ambos operandos con el operador introducido, y de nuevo el resultado se queda como el operando 1.

El defecto de este modelo, son 2:
- El primero defecto: el usuario debe introducir las operaciones en orden, ya que la precedencia seguida es 'se ejecuta inmediatamente', luego obliga al usuario a introducir ordenadamente o el resultado final puede ser errado... 5+2*3=, con este diseño de calculadora arrojaría:
opn1 = 5
opr = "+"
si opr = "=" mostrar opn1
opn2 = 2
oprtemp = "*"
opn1 = opn1 opr opn2 = 5 + 2 = 7
opr= oprtemp
si opr = "=" mostrar opn1
opn2 = 3
oprtemp = "="
opn1 = opn1 opr opn2 = 7 * 3 = 21
opr = oprtemp
si opr = "=" mostrar opn1   mostrar 21
Por comodidad al operando1 sería mejor llamarlo acumulador, y al operando2, simplemente operando.

Con precedencia de operadores sería opn1 = 5+(2*3) = 11

- El segundo defecto: Al ser ejecutado de modo inmediato, (carece de memoria, solo mantiene 3 datos). El usuario no puede ver que ha escrito más allá de lo último entrado, es fácil equivocarse y repetir un operando ya escrito con anterioridad, tampoco puede hacer nada más complejo, le exige pensarlo antes de introducir datos, ni puede hacer modificaciones sobre la marcha...

Luego proveer que pueda entrar una expresión completa, es mucho más efectivo, una línea de texto con una expresión donde pueda modificar, añadir, verificar antes de procesar, es un diseño más 'potente' de calculadora...
Al tiempo, si de nuevo te vas conformas con +-*/=, no te compliques más... Esto es, si es un tarea escolar...

...pero desde el momento en que a futuro quieras añadir operandos más complejos (números reales, en diferentes bases numéricas) ú operadores más complejos (como seno, coseno, tangente, random, media aritmética, raíz cuadrada, raíz cúbica, Xor, And, etc... tendrás que permitir operadores en la forma: 'sin', 'cos', 'atn', 'rnd', 'mod' 'sqr', 'sq3', 'xor', 'and'...), sí o sí, vas a necesitar un analizador de tokens... esto será tanto más cierto, cuanto más complejo haya de ser... y cuanto más complejo haya de ser, proveer el mecanismo (el diseño) más eficaz será lo mejor para poder ampliarlo, sin dejarse los sesos en ello... ergo, ahí te he mostrado el camino a seguir... listo para ampliar cuando se precise, falta básicamente proveer un analizador de "identificador" (cuyo código es casi idéntico al de número), junto con algún token alfabetico-alfanumerico, etc...


Cita de: vhh70 en  2 Junio 2018, 21:53 PM
Lo que se me complica con los apuntadores es como recorrerlo y luego hacer las operaciones.
Para manejar los datos, elige estructuras que sobretodo seas capaz de manejar, porque las conoces y entiendes, aunque no sea del todo óptimo. En primer lugar uno debe lograr que funcione, luego es tiempo de hacer optimizaciones... no tiene sentido que pretendas usar por ejemplo una tabla hash, si no sabes como usarla y menos como coinstruírla o mantenerla.
Primero logra que te funciones y luego ya mirarás de intentar optimizarlo...
Ten en cuenta que la entrada recibida es en un array de bytes-caracteres, realmente al caso es lo más óptimo.