Autor
|
Tema: Proyecto calculadora: Convertir infijo a posfijo. (Leído 31,877 veces)
|
Yoel Alejandro
|
Hola a todos. Mi idea a mediano plazo es construir una calculadora "en linea", o sea por consola. No funcionaría con botones, sino que el usuario debe escribir una expresión algebraica, e.g. 2 + 3*(3-6)^2 la cual será evaluada. En el futuro, incluir funciones trigonométricas, exponenciales, etc., lo cual no debe ser difícil una vez se tenga la estructura básica funcionando. Quizá hasta con la opción de graficar. Es un proyecto bonito, porque al estar desarrollado en C estándar, se pudiera incluso compilar en un microprocesador de bajo costo (compatible con C) y hacer así una calculadora científica. Claro, esto sería bien en el futuro pero con algo hay que comenzar. El primer reto a que debemos enfrentarnos es el tema de conversión de la notación "infija a posfija". Explico a continuación (es un poco extenso, los impacientes favor ir a dar una vuelta, jeje): Para los humanos es natural la notación 'infija', donde el operador va en medio de los operandos: "5 + 2*3". En cambio para la máquina es natural la notación 'posfija' donde primero van los operandos y luego el operador. Esto es así porque a nivel de máquina primero deben copiarse los argumentos en los registros adecuados del procesador, y luego invocar la instrucción de suma (binaria). En posfijo, la anterior expresión se escribiría como: 5 2 3 * +
y obśervese como primero debe ejecutarse el operador de mayor prioridad *, y luego +, para cumplir adecuadamente con la asociatividad implícita 5 + (2 * 3). Ya encontré un algoritmo que explica cómo hacer el paso de una notación a la otra usando pilas, pero más que repetirlo textualmente quisiera encontrarle la lógica. Supóngase que tenemos una expresión del tipo: A op1 B op2 C
donde A, B, C son los operandos, mientras op1 y op2 son operadores. A menos que existan paréntesis explícitos, la asociatividad viene determinada por la precedencia de dichos operadores. Ahora, disponemos de uns string para la notación infija, un string para la posfija (que debemos ir rellenando) y una pila para ir depositando algunos elementos transitorimente. Caso 1. Si op1 tiene mayor precedencia que op2, ejemplo "A * B + C", primero debe evaluarse el resultado de op1, luego el de op2. Entonces, copiamos A al string de posfijo, metemos op1 en la pila, copiamos B al string posfijo, sacamos op1 de la pila y lo pasamos al posfijo, metemos op2 en la pila, copiamos C al string posfijo, sacamos op2 de la pila y lo pasamos al string. Para explicarlo más claramente en forma de tabla. Paso | Pila | Posfijo ------------------------------------- 1 | | A 2 | op1 | 3 | | A B 4 | op2 | A B op1 <-- sacamos op1 de la pila y lo pasamos al string 5 | op2 | A B op1 C 6 | | A B op1 C op2
Caso 2. Cuando op1 y op2 tienen igual precedencia, ejemplo "A + B + C". En este caso, la asociatividad debe ser de izquierda a derecha "(A + B) + C", por lo que se evalúa primero op1 y luego op2. Se procede exactamente de la misma manera que en el caso 1. Caso 3. Cuando op1 es de menor prioridad que op2, ejemplo "A + B * C". Aquí debe evaluarse "A + (B * C)". Esto significa que op1 debe esperar por el resultado de op2, el string posfijo deberá quedar "A B C op2 op1". En forma de tabla: Paso | Pila | Posfijo ------------------------------------- 1 | | A 2 | op1 | 3 | | A B 4 | op1, op2 | A B <-- no se saca op1 al momento de colocar op2 5 | | A B op2 op1 <-- vaciamos la pila
La diferencia con el caso 1 es que al momento de introducir op2 en la pila, no se saca antes op1. Esto es porque op2 tiene mayor precedencia que op1 entonces debe evaluarse primero, es decir, debe quitarse primero op2 de la pila y luego op1. Uno puede resumir diciendo que al momento de colocar un nuevo operador en la pila, se sacan todos los operadores anteriormente depositados y que tengan mayor o igual jerarquía a la suya (y que deben evaluarse antes), estos operadores son pasados al string posfijo y el nuevo operador es depositado en la pila. Con esta explicación precedente, se puede presentar el algoritmo para convertir infijo a posfijo. Debemos leer la expresión infija de izquierda a derecha y luego * Los espacios son despreciados. * Si el argumento leido es un operando (número), pasarlo al string posfijo. * Si el argumento leido es un operador entonces: - Si la pila no está vacía, retirar todos los operadores con una precedencia mayor o igual al operador actual y pasarlos al posfijo, en el orden en que son retirados. - Colocar el nuevo operador en la pila. * Si se lee un paréntesis izquierdo '(', depositarlo en la pila. * Si se lee un paréntesis derecho ')', retirar de la pila todos los elementos hasta encontrar un '('. Dichos elementos son pasados al posfijo, menos el paréntesis '(' que es descartado. * Al finalizar el proceso, pasar al posfijo cualquier elemento que haya quedado en la pila. Entonces me puse manos a la obra. El programa está hecho en C++, que al usar clases STL se simplifica bastante (estoy pensando una versión en C puro, con código más elemental, útil quizás si se piensa migrar a un hardware más básico como un uC). El programa recibe una cadena de notación infija y la convierte a posfija. Para propósitos de depuración se dan mensajes en pantalla sobre los pasos que van efectuando (funcionalidad que se debe quitar más adelante). Al final, evalúa el resultado y lo imprime. Por ejemplo, una cadena sencilla como: 5 + 2 * 3
produce la salida: Intro expresion infija: 5 + 2 * 3
Analizando token: '5' es numero: pasado a posfijo
Analizando token: '+' es operador: colocar '+' en la pila
Analizando token: '2' es numero: pasado a posfijo
Analizando token: '*' es operador: colocar '*' en la pila
Analizando token: '3' es numero: pasado a posfijo
Pasado operador '*' de la pila a posfijo
Pasado operador '+' de la pila a posfijo
Posfijo es: 5 2 3 * +
Evaluando la expresion ... operar 2*3 = 6 operar 5+6 = 11 El resultado es: 11
y un caso más complicado: 5 + 3*(4 - 7)^2 - 1
produce: Analizando token: '5' es numero: pasado a posfijo
Analizando token: '+' es operador: colocar '+' en la pila
Analizando token: '3' es numero: pasado a posfijo
Analizando token: '*' es operador: colocar '*' en la pila
Analizando token: '(' pasado a posfijo
Analizando token: '4' es numero: pasado a posfijo
Analizando token: '-' es operador: colocar '-' en la pila
Analizando token: '7' es numero: pasado a posfijo
Analizando token: ')' pasado operador '-' de la pila a posfijo
Analizando token: '^' es operador: colocar '^' en la pila
Analizando token: '2' es numero: pasado a posfijo
Analizando token: '-' es operador: pasado operador '^' de la pila a posfijo pasado operador '*' de la pila a posfijo pasado operador '+' de la pila a posfijo colocar '-' en la pila
Analizando token: '1' es numero: pasado a posfijo
Pasado operador '-' de la pila a posfijo
Posfijo es: 5 3 4 7 - 2 ^ * + 1 -
Evaluando la expresion ... operar 4-7 = -3 operar -3^2 = 9 operar 3*9 = 27 operar 5+27 = 32 operar 32-1 = 31 El resultado es: 31
A continuación el código fuente, que es el trabajo de unas horas repartidas entre ayer y hoy: #include <cstdlib> #include <iostream> #include <string> #include <stack> #include <cmath> using namespace std; /* Operadores matematicos */ #define N_operators 6 const string operators[N_operators] = {"+", "-", "*", "/", "%", "^"}; int precedences[N_operators] = {1, 1, 2, 2, 2, 3}; bool is_operator( const string ); int precedence( const string ); /* Mis sugerencias personales a este programa: * * - Considerar el cambio del nombre standby por aux_stack, u otro. * - Incorporar el analizador lexico para expresiones numericas de punto * flotante, o que ocupen mas de un caracter. * - Eliminar la cadena intermedia postfix, pasando directamente los * resultados de la pila de operadores extraidos de infix, a una pila * de evaluacion. * - Incorporar operadores unarios, como sin(), cos(), exp(), log(), etc. * - Detectar errores de sintaxis en la expresion infija. * - Otros que sean recomendados. */ int main() { string infix, postfix, token; stack <string> standby; stack <double> result; size_t i; char c; double A, B; /* Cadena de entrada */ cout << "Intro expresion infija: "> getline( cin, infix ); cout << endl; /************************************************************* PRIMERA PARTE: Procesar la cadena infijo, y crear posfijo *************************************************************/ for ( i = 0; i < infix.size(); i++ ) { /* esto debe cambiar luego a un token o palabra devuelta por * el analizador léxico */ c = infix[i]; token.clear(); token += c; /* parece burdo, pero no conozco mejor manera de * crear un string a partir de un unico caracter */ /* es un espacio: despreciar */ if ( c == ' ' ) continue; cout << "Analizando token: '" << c << "'" << endl; /* es un carácter numérico: pasar al posfijo */ if ( c >= '0' && c <= '9' ) { cout << "\tes numero: pasado a posfijo" << endl << endl; postfix = postfix + " " + c; continue; } /* si se lee un operador: sacar de la pila y pasar al postfijo * todos los operadores con una precedencia mayor o igual a la * suya, y depositar el mismo en la pila */ if ( is_operator( token ) ) { cout << "\tes operador:" << endl; while ( !standby.empty() && precedence( standby.top() ) >= precedence( token ) ) { cout << "\tpasado operador '" + standby.top() + "' de la pila a posfijo" << endl; postfix = postfix + " " + standby.top(); standby.pop(); } standby.push( token ); cout << "\tcolocar '" << token << "' en la pila" << endl << endl; continue; } /* si se lee "(": colocar en la pila */ if ( token == "(" ) { cout << "pasado a posfijo" << endl << endl; standby.push( token ); continue; } /* si se lee ")": retirar de la pila hasta encontrar '(', y pasar * los elementos retirados a posfijo, luego descartar el "(" */ if ( token == ")" ) { while ( !standby.empty() && standby.top() != "(" ) { cout << "\tpasado operador '" + standby.top() + "' de la pila a posfijo" << endl << endl; postfix = postfix + " " + standby.top(); standby.pop(); } if ( !standby.empty() ) standby.pop(); /* descartar el "(" */ } } /* extraer de la pila cualquier operador restante y pasarlo a la cadena posfijo */ while ( !standby.empty() ) { cout << "Pasado operador '" + standby.top() + "' de la pila a posfijo" << endl << endl; postfix = postfix + " " + standby.top(); standby.pop(); } /* Imprimir el posfijo */ cout << "Posfijo es: \n\t" << postfix << endl << endl; /**************************************************************** SEGUNDA PARTE: Procesar la cadena posfijo, y devolver resultado ****************************************************************/ A = 0; cout << "Evaluando la expresion ..." << endl; for ( i = 0; i < postfix.size(); i++ ) { c = postfix[i]; token.clear(); token += c; /* si se lee un operando (caracter numerico), depositar en la pila */ if ( c >= '0' && c <= '9' ) { result.push( c - '0' ); continue; } /* si se lee un operador binario, poner en A y B los últimos dos argumentos * de la pila y operarlos, guardando el resultado en la pila */ if ( is_operator( token ) ) { if ( !result.empty() ) { B = result.top(); result.pop(); } else { cout << "Argumentos insuficientes para '" << c << "'" << endl; return -1; } if ( !result.empty() ) { A = result.top(); result.pop(); } else { cout << "Argumentos insuficientes para '" << c << "'" << endl; return -1; } cout << "\toperar " << A << token << B << " = "; if ( token == "+" ) { A += B; result.push( A ); } else if ( token == "-" ) { A -= B; result.push( A ); } else if ( token == "*" ) { A *= B; result.push( A ); } else if ( token == "/" ) { A /= B; result.push( A ); } else if ( token == "%" ) { A = (int )A % (int )B; result.push( A ); } else if ( token == "^" ) { A = pow(A, B); result.push( A ); } cout << A << endl; } } if ( !result.empty() ) cout << endl << "El resultado es: " << result.top() << endl; return 0; } /* Verdadero si el token corresponde a un operador. */ bool is_operator( const string token ) { for ( int i = 0; i < N_operators; i++ ) if ( operators[i] == token ) return true; return false; } /* Devuelve la precedencia del operador descrito por el * string token (-1 si no es un operador) */ int precedence( const string token ) { for ( int i = 0; i < N_operators; i++ ) if ( operators[i] == token ) return precedences[i]; return -1; }
|
|
|
En línea
|
Saludos, Yoel. P.D..- Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
|
|
|
amchacon
Desconectado
Mensajes: 1.211
|
¡Hey! ¡Que recuerdos! Yo también hize una calculadora de esas en mis tiempos, la mía además hacía funciones matematicas (sin,cos,tan,log,sqrt... todas recreadas con la libreria estandar y a correr xD). También tenía algunas macros como el número pi o el numero de euler. Lo que más me costo son los anidamientos así: sin(sin(2)) Aunque yo no usaba pila, sino un enfoque más simplista: "Divide y venceras". Si tenía la siguiente expresión: 2 + 3 * 2 La transformaba: 2 + 6 Y la volvía a reinterpretar. El código es de hace 1 año casi, mucho ha llovido desde entonces. Hay muchísimas cosas que haría de otra forma (como la función convertirnumero, que tiene bastantes aberraciones ). #include <iostream> #include <cmath> #include <list> #include <sstream> #include <map> /** Fecha: 05/07/2013 Analiza una expressión en formato texto y resuelve las operaciones pertinentes */ const int MAX =5; using namespace std; inline void ResolverParentesis(string &Entrada,short &i); double Ejecucion(string &Entrada); double ConvertirNumero(string &Entrada,int &Index); double RealizarOperacion(string &Entrada); int ObtenerOperadorAnterior(string &Entrada,int Index); double RealizarOperacion(string &Entrada); double ConvertirAngulo(double Entrada) { return Entrada *3.141592/180; } inline void ResolverFunciones(string& Entrada); const char Operaciones[MAX] = {'^','/','*','+','-'}; typedef double (*Puntero_A_Funcion)(double); map<string,Puntero_A_Funcion> Punteros; map<string,double> Macros; int main() { Punteros["sin"] = sin; Punteros["cos"] = cos; Punteros["tan"] = tan; Punteros["sinh"] = sinh; Punteros["cosh"] = cosh; Punteros["tanh"] = tanh; Punteros["asin"] = asin; Punteros["acos"] = acos; Punteros["atan"] = atan; Punteros["asinh"] = asinh; Punteros["acosh"] = acosh; Punteros["atanh"] = atanh; Punteros["cbrt"] = cbrt; Punteros["sqrt"] = sqrt; Punteros["log"] = log; Punteros["log10"] = log10; Punteros["abs"] = abs; Punteros["rad"] = ConvertirAngulo; Macros["pi"] = 3.141592654; Macros["euler"] = 2.7182818; Macros["aureo"] = 1.61803398; Macros["ans"] = 0; string Expresion; cout<<"Calculadora parser por amchacon"<<endl<<endl; cout<<"Soporte para las siguientes funciones matematicas: "<<endl<<endl; short Contador = 0; for (auto it = Punteros.begin(); it != Punteros.end(); it++) { cout<<it->first<<"\t"; Contador++; if (Contador == 3) { Contador = 0; cout<<endl; } } cout<<endl<<endl<<"Constantes definidas: "<<endl; Contador = 0; for (auto it = Macros.begin(); it != Macros.end(); it++) { cout<<it->first<<"\t"; Contador++; if (Contador == 3) { Contador = 0; cout<<endl; } } cout<<endl<<endl; while(1) { cout << "Introduce tu expresion: " << endl; getline(cin,Expresion); try { cout<<endl<<"El resultado es: "<<RealizarOperacion(Expresion); } catch(const char* c) { cout<<endl<<c; } cout<<endl<<endl<<endl; } return 0; } double RealizarOperacion(string &Entrada) { int Index; if (Entrada.empty()) throw "Error, expresion vacia"; // Macros for (auto it = Macros.begin();it != Macros.end();it++) { Index = Entrada.find(it->first); if (Index != -1) { Entrada.erase(Index,it->first.size()); stringstream Cosita; Cosita<<it->second; Entrada.insert(Index,Cosita.str()); } } // Funciones matemáticas ResolverFunciones(Entrada); Macros["ans"] = Ejecucion(Entrada); return Macros["ans"]; } /** Función principal que resuelve la operación **/ double Ejecucion(string &Entrada) { // Comprobando paréntesis for (short i = 0; i < Entrada.size(); i++) { if (Entrada[i] == '(') // Si hay un parentesis { ResolverParentesis(Entrada,i); } } /** Empezamos a resolver las operaciones **/ for (short j = 0; j < MAX; j++) // Recorremos las 4 operaciones for (short i = 0; i < Entrada.size(); i++) { if (Entrada[i] == Operaciones[j]) // Si encontramos nuestra operación { int Inicio = ObtenerOperadorAnterior(Entrada,i); // Buscamos el operador que le precede (o el 0) if (Inicio == -1) continue; int Index = Inicio; // El index es nuestra posicion actual int Tam = Index; // El tamanyo de la expresion que contiene nuestra operacion double Operacion = ConvertirNumero(Entrada,Index); // Obtenemos el primer numero // Eliminando espacios en blanco... while(Entrada[Index] == ' ') { Index++; } Index++; // Eliminamos el operador double Numerito = ConvertirNumero(Entrada,Index); // Obtenemos el segundo numero de la operacion switch (j) // Dependiendo de la operacion que estabamos buscando hacemos una cosa o otra { case 0: Operacion = pow(Operacion,Numerito); break; case 1: if (!Numerito) throw "Error, division por cero"; Operacion /= Numerito; break; case 2: Operacion *= Numerito; break; case 3: Operacion += Numerito; break; case 4: Operacion -= Numerito; } // El tamanyo de nuestra expresión es final (Index) menos inicio Tam = Index-Inicio; // Actualizamos nuestra expression Entrada.erase(Inicio,Tam); // Borramos la antigua // Obtenemos la expression númerica de la nueva stringstream Buffer; Buffer<<Operacion; // Y la insertamos Entrada.insert(Inicio,Buffer.str()); // Volvemos al principio i = 0; } } /** Ahora unicamente nos queda una expresión númerica, la pasamos a número y la devolvemos **/ int Index = 0; return ConvertirNumero(Entrada,Index); } inline void ResolverFunciones(string& Entrada) { int Index; for (auto it = Punteros.begin(); it != Punteros.end(); it++) { Index = Entrada.find(it->first); if (Index != -1) { int Inicio = Index; Index = Inicio+it->first.size(); while(Entrada[Index] == ' ') { Index++; } short aux = Index; if (Entrada[Index] == '(') { ResolverParentesis(Entrada,aux); } if ((Entrada[Index] >= '0' && Entrada[Index] <= '9') || (Entrada[Index] == '-') || Entrada[Index] == '+') { stringstream Cosita; Cosita<<it->second(ConvertirNumero(Entrada,Index)); Entrada.erase(Inicio,Index-Inicio); Entrada.insert(Inicio,Cosita.str()); } } } } inline void ResolverParentesis(string &Entrada,short &i) { short Toque = 1; // Buscamos el siguiente parentesis string Buffer; // Aquí guardaremos la expresión inscrita en el paréntesis bool Correcto = false; // Variable que comprueba si se llego al final del parentesis // for (auto it = Punteros.begin(); it != Punteros.end(); it++) // { // if (Entrada) // } for (short j = (i+1); j < Entrada.size(); j++) { Buffer+= Entrada[j]; // Vamos guardando la expresion... /** Si encontramos un paréntesis anidado, tendremos que encontrar el siguiente cierre de paréntesis **/ if (Entrada[j] == '(') Toque++; if (Entrada[j] == ')') // Cierre de parentesis, un parentesis menos que buscar Toque--; if (Toque == 0) // Si ya hemos terminado { Buffer.erase(Buffer.size()-1,1); // Borramos el ')' ResolverFunciones(Buffer); /** Aplicamos recursividad, resolvemos la operación inscrita en el paréntesis **/ Ejecucion(Buffer); /** Borramos nuestro paréntesis y lo sustituimos por nuestro nuevo valor **/ Entrada.erase(i,j-i+1); Entrada.insert(i,Buffer); i = 0; Correcto = true; // Finalizo correctamente break; } } if (!Correcto) // Si no se encontró el final del paréntesis { throw "No se encontro el fin del parentesis \n"; } } // Dada la posición de un operador, devuelve la posición contigua del operador anterior (o 0) int ObtenerOperadorAnterior(string &Entrada,int Index) { Index--; // nos saltamos nuestro operador if (Index < 0) return -1; // Vamos recorriendo nuestra entrada hasta que lo encontramos while (Entrada[Index] != '+' && Entrada[Index] != '-' && Entrada[Index] != '*' && Entrada[Index] != '/' && Index != 0) { Index--; } if (Index != 0) return Index+1; // Devolvemos la posición contigua else return 0; } /** El santo grial de todo, convierte una expressión de texto en un número **/ double ConvertirNumero(string &Entrada,int &Index) { list<double> Enteros; list<double> Decimales; short aux; short signo = 1; // Nos saltamos los espacios en blanco while(Entrada[Index] == ' ') { Index++; } if (Entrada[Index] == '-') { signo = -1; Index++; } /** Ahora debería haber números, de lo contrario tenemos un error **/ if (Entrada[Index] < '0' || Entrada[Index] > '9') { stringstream buffer; buffer<<"Error de sintaxis, desconocido elemento: '"<<Entrada[Index]<<"' en la posicion: "<<Index+1; throw buffer.str().c_str(); } /** Añadimos los enteros **/ while (Entrada[Index] >= '0' && Entrada[Index] <= '9' && Index < Entrada.size()) { Enteros.push_back(Entrada[Index]-48); //cout<<Cosa[i]<<endl; Index++; } /** Si a continuación encontramos un punto o una coma, esque hay numeros a continuacion **/ if (Entrada[Index] == '.' || Entrada[Index] == ',') { bool Activado = false; Index++; while (Entrada[Index] >= '0' && Entrada[Index] <= '9' && Index < Entrada.size()) { Decimales.push_back(Entrada[Index]-48); Index++; Activado = true; } if (!Activado) { throw "Error de sintaxis, se insertó un punto pero no se añadió ningun decimal"; } } double Numero = 0; // El valor que devolveremos aux = 0; /** Recorremos los enteros y lo pasamos a número. Como tenemos las cifras simplemente es sumarlas **/ for (auto it = Enteros.begin(); it != Enteros.end(); it++) { Numero += *it*pow(10,Enteros.size()-aux-1); aux++; } aux = 0; /** Idem, pero para los decimales **/ if (!Decimales.empty()) for (auto it = Decimales.begin(); it != Decimales.end(); it++) { Numero += *it/(pow(10,(aux))*10); aux++; } Numero *= signo; return Numero; }
El código en cuestión usa el estándar C++11. Tendrás que activar ese estandar en el compilador. Creo que tu sistema es mejor, pero podrías utilizar de este código el objeto map que uso para hacer las funciones matemáticas (sin,cos,tan...). ¿Críticas a tú código? Que es muy poco modular.
|
|
|
En línea
|
|
|
|
Yoel Alejandro
|
Por ahora la mayor debilidad del programa anterior (inspirado en un ejercicio propuesto en un libro) es que procesa números formados por un solo dígito. Sucede que dicho programa, aunque en una versión muy reducida, implementa funciones de un compilador. Debe pasar por una fase de análisis léxico, y una de análisis sintáctico. El análisis léxico consiste en identificar los tokens, elementos léxicos o lexemas, que son las palabras básicas en que se escribe el lenguaje. En nuestro caso, los tokens por los momentos serían: - paréntesis
- números
- operadores aritméticos binarios: + - * / % ^
- operadores unarios (por incluir en el futuro): sin(), cos(), exp(), log(), etc.
- caracteres de espacio, mismos que son ignorados
El análizador léxico pasa sus resultados al analizador sintáctico o parser que en el programa que presenté es el que manejas las pilas de conversión de infijo a postfijo, y la pila de evaluación de resultados. Lo que requiero por los momentos es una idea para construir un analizador léxico más completo, que reconozca los tokens anteriormente descritos, y sea capaz de entregarlos al parser para su procesamiento. Estos tokens pueden ser monocarácter o multicarácter. El problema mayor se presenta al reconocer los números. Definiríamos un número como cualquier cadena de caracteres que empiece opcionalmente con un signo '+' ó '-', luego una cadena de caracteres numéricos comprendidos entre '0' y '9', luego opcionalmente un carácter de punto decimal '.', y opcionalmente una cadena de caracteres numéricos para la parte decimal. Queda a criterio del diseñador si quiere considerar expresiones en notación exponencial como 12.34E+5Serían errores de sintaxis duplicar el carácter de signo, o el carácter de punto decimal, e.g. ++15.7, ó -18.71.3Obsérvese que la expresión: 3 + +10
se considera correcta, porque "+10" se interpreta como "10". Si embargo, el primer "+" debe ser interpretado como símbolo de suma, mientras el segundo es un signo de positivo. Así que un mismo carácter puede tener distintos significados. En contraposición, la expresión 3 + + 10
es incorrecta porque el segundo '+' está separado del 10 por un espacio, entonces no se considera signo de positivo, sino símbolo de suma por lo que hay doble suma. Esta parte entonces, y para lo que invoco la ayuda de la comunidad, debe recibir una cadena en infijo, reconocer los distintos tokens según las reglas ya explicadas, y presentarlos por la salida estándar indicando su tipo y valor. Ejemplo (3.5 + 4.15) * -2^3 debe presentar: token: (, tipo parentesis token: 3.5, tipo numero token: +, tipo suma token: 4.25, tipo numero token: ), tipo parentesis token: *, tipo producto token: -2, tipo numero token: ^, tipo potenciacion token: 3, tipo numero
Favor código claro y legible, no construcciones crípticas. Favor abstenerse personas con una actitud orgullosa que vienen a demostrar que pueden programar de una manera tan incomprensible que sólo se entienden ellos mimos, jaja Yo mientras, trataré de trabajar en el analizador sintáctico para incorporar los operadores unarios y eliminar la cadena intermedia postfix !!!
Hola amchacon!!! Saludos como siempre! Investigué un poco sobre el tema y parece que la manera en que ésto se resuelve más comúnmente es con pilas. De hecho, así trabajan los compiladores para traducir código fuente a lenguaje máquina, y casi aseguría que así lo hacen todas las calculadoras (lo aseguro porque es el método que enseñan todos los libros, jeje). Pero te cuento que hace tiempo y como desconocía el método de las pilas, ideé una solución ad-hoc parecida a la tuya, ... pero como que así resulta más complicado. Fíjate que mi programa no tiene problema con uno o varios niveles de anidamiento, por ejemplo: 2*(1 + 3*( 4 + 5) )
y después de mucho bla bla te dice: Posfijo es: 2 1 3 4 5 + * + *
Evaluando la expresion ... operar 4+5 = 9 operar 3*9 = 27 operar 1+27 = 28 operar 2*28 = 56 El resultado es: 56 Yo creo que incorporar los operadores unarios sin(), cos(), abs(), log(), y las macros que por fortuna vienen definidas en la propia biblioteca de C como M_PI, M_E será una buena idea y no tan difícil. En mi caso creo que ahorita lo más difícil será analizar léxicamente la expresión para detectar números decimales, signados o no, y por eso acabo de proponer un reto a la comunidad, espero me ayudes con un aporte ...
|
|
« Última modificación: 23 Marzo 2014, 06:10 am por Eternal Idol »
|
En línea
|
Saludos, Yoel. P.D..- Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
|
|
|
amchacon
Desconectado
Mensajes: 1.211
|
La solución es más fácilita de lo que parece: /* es un carácter numérico: pasar al posfijo */ if ( c >= '0' && c <= '9' ) { cout << "\tes numero: pasado a posfijo" << endl << endl; postfix = postfix + " " + c; continue; }
Has detectado un número, bien. Aquí debes leer EL NUMERO ENTERO, un ejemplo sería reutilizar mi función: /* es un carácter numérico: pasar al posfijo */ if ( c >= '0' && c <= '9' ) { double aux = ConvertirNumero(infix,i); cout << "\tes numero: pasado a posfijo" << endl << endl; result.push(aux); continue; }
La función ConvertirNumero() te actualiza la posición de tu indice i, asi que no deberías preocuparte por eso. También te lee los decimales. El código completo sería: #include <cstdlib> #include <iostream> #include <string> #include <stack> #include <cmath> #include <list> #include <sstream> using namespace std; /* Operadores matematicos */ #define N_operators 6 const string operators[N_operators] = {"+", "-", "*", "/", "%", "^"}; int precedences[N_operators] = {1, 1, 2, 2, 2, 3}; bool is_operator( const string ); int precedence( const string ); /* Mis sugerencias personales a este programa: * * - Considerar el cambio del nombre standby por aux_stack, u otro. * - Incorporar el analizador lexico para expresiones numericas de punto * flotante, o que ocupen mas de un caracter. * - Eliminar la cadena intermedia postfix, pasando directamente los * resultados de la pila de operadores extraidos de infix, a una pila * de evaluacion. * - Incorporar operadores unarios, como sin(), cos(), exp(), log(), etc. * - Detectar errores de sintaxis en la expresion infija. * - Otros que sean recomendados. */ double ConvertirNumero(string &Entrada,int &Index) { list<double> Enteros; list<double> Decimales; short aux; short signo = 1; // Nos saltamos los espacios en blanco while(Entrada[Index] == ' ') { Index++; } if (Entrada[Index] == '-') { signo = -1; Index++; } /** Ahora debería haber números, de lo contrario tenemos un error **/ if (Entrada[Index] < '0' || Entrada[Index] > '9') { stringstream buffer; buffer<<"Error de sintaxis, desconocido elemento: '"<<Entrada[Index]<<"' en la posicion: "<<Index+1; throw buffer.str().c_str(); } /** Añadimos los enteros **/ while (Entrada[Index] >= '0' && Entrada[Index] <= '9' && Index < Entrada.size()) { Enteros.push_back(Entrada[Index]-48); //cout<<Cosa[i]<<endl; Index++; } /** Si a continuación encontramos un punto o una coma, esque hay numeros a continuacion **/ if (Entrada[Index] == '.' || Entrada[Index] == ',') { bool Activado = false; Index++; while (Entrada[Index] >= '0' && Entrada[Index] <= '9' && Index < Entrada.size()) { Decimales.push_back(Entrada[Index]-48); Index++; Activado = true; } if (!Activado) { throw "Error de sintaxis, se insertó un punto pero no se añadió ningun decimal"; } } double Numero = 0; // El valor que devolveremos aux = 0; /** Recorremos los enteros y lo pasamos a número. Como tenemos las cifras simplemente es sumarlas **/ for (auto it = Enteros.begin(); it != Enteros.end(); it++) { Numero += *it*pow(10,Enteros.size()-aux-1); aux++; } aux = 0; /** Idem, pero para los decimales **/ if (!Decimales.empty()) for (auto it = Decimales.begin(); it != Decimales.end(); it++) { Numero += *it/(pow(10,(aux))*10); aux++; } Numero *= signo; Index--; return Numero; } int main() { string infix, postfix, token; stack <string> standby; stack <double> result; int i; char c; double A, B; /* Cadena de entrada */ cout << "Intro expresion infija: "> getline( cin, infix ); cout << endl; /************************************************************* PRIMERA PARTE: Procesar la cadena infijo, y crear posfijo *************************************************************/ for ( i = 0; i < infix.size(); i++ ) { /* esto debe cambiar luego a un token o palabra devuelta por * el analizador léxico */ c = infix[i]; token.clear(); token += c; /* parece burdo, pero no conozco mejor manera de * crear un string a partir de un unico caracter */ /* es un espacio: despreciar */ if ( c == ' ' ) continue; cout << "Analizando token: '" << c << "'" << endl; if ( c >= '0' && c <= '9' ) { cout<<"Init: "<<i<<endl; double aux = ConvertirNumero(infix,i); cout<<"finit: "<<i<<endl; cout << "\tes numero: pasado a posfijo:" << aux<<endl << endl; result.push(aux); // esto es solo para la depuracion stringstream cosa; cosa<<aux; postfix = postfix + " " + cosa.str(); continue; } /* si se lee un operador: sacar de la pila y pasar al postfijo * todos los operadores con una precedencia mayor o igual a la * suya, y depositar el mismo en la pila */ if ( is_operator( token ) ) { cout << "\tes operador:" << endl; while ( !standby.empty() && precedence( standby.top() ) >= precedence( token ) ) { cout << "\tpasado operador '" + standby.top() + "' de la pila a posfijo" << endl; postfix = postfix + " " + standby.top(); standby.pop(); } standby.push( token ); cout << "\tcolocar '" << token << "' en la pila" << endl << endl; continue; } /* si se lee "(": colocar en la pila */ if ( token == "(" ) { cout << "pasado a posfijo" << endl << endl; standby.push( token ); continue; } /* si se lee ")": retirar de la pila hasta encontrar '(', y pasar * los elementos retirados a posfijo, luego descartar el "(" */ if ( token == ")" ) { while ( !standby.empty() && standby.top() != "(" ) { cout << "\tpasado operador '" + standby.top() + "' de la pila a posfijo" << endl << endl; postfix = postfix + " " + standby.top(); standby.pop(); } if ( !standby.empty() ) standby.pop(); /* descartar el "(" */ } } /* extraer de la pila cualquier operador restante y pasarlo a la cadena posfijo */ while ( !standby.empty() ) { cout << "Pasado operador '" + standby.top() + "' de la pila a posfijo" << endl << endl; postfix = postfix + " " + standby.top(); standby.pop(); } /* Imprimir el posfijo */ cout << "Posfijo es: \n\t" << postfix << endl << endl; /**************************************************************** SEGUNDA PARTE: Procesar la cadena posfijo, y devolver resultado ****************************************************************/ A = 0; cout << "Evaluando la expresion ..." << endl; for ( i = 0; i < postfix.size(); i++ ) { c = postfix[i]; token.clear(); token += c; /* si se lee un operando (caracter numerico), depositar en la pila */ if ( c >= '0' && c <= '9' ) { continue; } /* si se lee un operador binario, poner en A y B los últimos dos argumentos * de la pila y operarlos, guardando el resultado en la pila */ if ( is_operator( token ) ) { if ( !result.empty() ) { B = result.top(); result.pop(); } else { cout << "Argumentos insuficientes para '" << c << "'" << endl; return -1; } if ( !result.empty() ) { A = result.top(); result.pop(); } else { cout << "Argumentos insuficientes para '" << c << "'" << endl; return -1; } cout << "\toperar " << A << token << B << " = "; if ( token == "+" ) { A += B; result.push( A ); } else if ( token == "-" ) { A -= B; result.push( A ); } else if ( token == "*" ) { A *= B; result.push( A ); } else if ( token == "/" ) { A /= B; result.push( A ); } else if ( token == "%" ) { A = (int )A % (int )B; result.push( A ); } else if ( token == "^" ) { A = pow(A, B); result.push( A ); } cout << A << endl; } } if ( !result.empty() ) cout << endl << "El resultado es: " << result.top() << endl; return 0; } /* Verdadero si el token corresponde a un operador. */ bool is_operator( const string token ) { for ( int i = 0; i < N_operators; i++ ) if ( operators[i] == token ) return true; return false; } /* Devuelve la precedencia del operador descrito por el * string token (-1 si no es un operador) */ int precedence( const string token ) { for ( int i = 0; i < N_operators; i++ ) if ( operators[i] == token ) return precedences[i]; return -1; }
Esto debería funcionarte con número de x cifras y con números decimales/enteros. El separador decimal puede ser ',' o '.' resulta totalmente indiferente. Por supuesto, hay cosas que se pueden optimizar de ahí. Yo solo te he copypasteado la función y he tocado 1-2 cosas para que funcione (por ejemplo, que el deposito de números en la pila sea en la primera lectura y no en la segunda). Por cierto: token.clear(); token += c;
Esto es equivalente a: token = c;
De todas formas, si solo vas a tener un caracter podrías usar un char a secas
|
|
|
En línea
|
|
|
|
Yoel Alejandro
|
Gracias por el token = c !!!
Ahora dos detallitos:
(1) Si podrías hacer el código más portable, que no requiera necesariamente C++11. Yo uso Ubuntu 10.04, de 2010, y g++ no está actualizado a 2011. Tengo problemas con el "auto it", en el otro código tive que cambiar todas las expresiones de ese tipo al tipo "class<T> :: it". Quiero que sea portable porque la idea de ésto es primero usarlo como objeto académico-pedagógico en la Universidad por ejemplo, y no se si todos los estudiantes están actualizado a la última versión del software.
(2) La invocación de la conversión a número empieza cuando se detecta un carácter entre '0' y '9', no con el signo '+' ó '-'. ¿Eso no hará que por ejemplo al recibir 2 * -3, lo interprete como 2 * 3?
|
|
|
En línea
|
Saludos, Yoel. P.D..- Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
|
|
|
amchacon
Desconectado
Mensajes: 1.211
|
Gracias por el token = c !!!
Ahora dos detallitos:
(1) Si podrías hacer el código más portable, que no requiera necesariamente C++11. Yo uso Ubuntu 10.04, de 2010, y g++ no está actualizado a 2011. Tengo problemas con el "auto it", en el otro código tive que cambiar todas las expresiones de ese tipo al tipo "class<T> :: it". Quiero que sea portable porque la idea de ésto es primero usarlo como objeto académico-pedagógico en la Universidad por ejemplo, y no se si todos los estudiantes están actualizado a la última versión del software. No solo necesitas un compilador que soporte C++11 sino que también tienes que activar ese modo en la compilación (creo que era std=C++11 o algo así). Lo más fácil esque quites los auto y listo. No hay nada más que use de C++11 en ese código. (2) La invocación de la conversión a número empieza cuando se detecta un carácter entre '0' y '9', no con el signo '+' ó '-'. ¿Eso no hará que por ejemplo al recibir 2 * -3, lo interprete como 2 * 3?
Efective wonder. Puedes empezar también con un '-', conviertes el número y a falta de operadores asumes una suma. Por cierto: const string operators[N_operators] = {"+", "-", "*", "/", "%", "^"};
Mucho mejor es lo siguiente: const char operators[N_operators] = {'+','-','*','/','%','^'};
No me gusta usar strings para cadenas constantes, es un desperdicio de recursos (tanto en espacio como en tiempo).
|
|
« Última modificación: 23 Marzo 2014, 13:21 pm por amchacon »
|
En línea
|
|
|
|
Yoel Alejandro
|
Bueno, revisé la función ConvertirNumero(), pero veo que usa objetos complejos como listas. Sin embargo me parece excelente la filosofía usada para reconocer la sintaxis de la cadena, y obtener el número representado. Así que me tomé la libertad de redactarla en un código algo más elemental. Lo que hago es no formar un conjunto con los dígitos que conforman la parte entera y la parte fraccionaria, sino reconocer los índices inicial y de dichos elementos en la cadena original. Luego voy tomando los dígitos y multiplicándolos por una potencia de 10. Adicionalmente, reemplazar el pow() por una variable base que en cada ciclo se multiplica por 10. Esa era la idea original de la función, creo que se mantiene igual pero ahora compila más rápido porque no necesita enlazar con bibliotecas de clases, jeje. double str_to_dbl( const char *s, int *index_ptr ) { char c; int i; int int_start, int_end; /* indices de inicio y fin de parte entera */ int frac_start,frac_end; /* indices de inicio y fin de parte fraccionaria */ char sign = 0; /* flag de signo, 1 para negativo */ double number, base; /* descartamos espacios en blanco iniciales */ i = 0; while ( s[i] == ' ' ) i++; /* reconocemos el carácter de signo (opcional) */ if ( ( c = s[i] ) == '+' || c == '-' ) { if ( c == '-' ) sign = 1; i++; } /* reconocemos la parte entera, si la hay */ int_start = -1; while ( ( c = s[i] ) >= '0' && c <= '9' ) { if ( int_start == -1 ) int_start = i; i++; } if ( int_start != -1 ) int_end = i - 1; /* caracter de punto decimal */ if ( s[i] == '.' ) { i++; /* reconocemos la parte decimal, si la hay */ frac_start = -1; while ( ( c = s[i] ) >= '0' && c <= '9' ) { if ( frac_start == -1 ) frac_start = i; i++; } if ( frac_start != -1 ) frac_end = i - 1; } /* calcular el número */ number = 0; if ( int_start != -1 ) { base = 1; while ( int_end >= int_start ) { number += (s[int_end] - '0') * base; int_end--; base *= 10; } } if ( frac_start != -1 ) { base = 10; while ( frac_start <= frac_end ) { number += (s[frac_start] - '0') / base; frac_start++; base *= 10; } } if ( sign ) number *= -1; if ( int_start != -1 || frac_start != -1 ) *index_ptr = i; else *index_ptr = -1; return number; }
Un pequeño detalle en la función ConvertirNumero es que arrojaba error si el número carecía de parte entera, e.g. -.35. Esto se corrige disponiendo que tanto la parte entera como la decimal sean opcionales, pero deba estar presente una de las dos: if ( int_start != -1 || frac_start != -1 ) /* ... */
¿Pero sabes qué? Desempolvando los papeles veo que la función de biblioteca strtod(const char *s, char **endptr) ya nos hace este trabajo, y además actualiza endptr al primer caracter de la cadena no reconocible como parte de un número, y si coincide con el primero de la cadena significa que no se pudo reconocer ningún número. Es de biblioteca, es estándar y sobre todo, ya está hecha, jajaja. Sin embargo, habría una razón para que nuestras funciones pudieran preferibles a strtod(): que podrían avisar sobre error de sintaxis. Pero para que las mismas puedan competir contra la función de biblioteca les hace falta que reconozcan números en formato de notación exponencial o científica.
|
|
|
En línea
|
Saludos, Yoel. P.D..- Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
|
|
|
amchacon
Desconectado
Mensajes: 1.211
|
Hehe. Ya te dije que mi función era una chapuzilla ^^ Yo ahora lo haría con el objeto stringstream: if ( c >= '0' && c <= '9' ) { double aux; stringstream cosa(infix.substr(i)); // crea un substring a partir de la posicion i cosa >> aux; result.push(aux); continue; }
De hecho podría ir sacando todos los números de ese modo: cout << "Intro expresion infija: "> getline( cin, infix ); cout << endl; stringstream cosa(infix); double aux; cosa>>aux; do { result.push(aux); cosa>>aux; }while (cosa.good());
|
|
|
En línea
|
|
|
|
Yoel Alejandro
|
Por cierto, y un comentario fuera de lugar, ¿por qué no compartes en el foro algún algoritmo de esteganografía de ficheros elaborado en/para C? Con teoría básica de cómo funciona, etc.
¿o ya lo has hecho?
|
|
|
En línea
|
Saludos, Yoel. P.D..- Para mayores dudas, puedes enviarme un mensaje personal (M.P.)
|
|
|
amchacon
Desconectado
Mensajes: 1.211
|
¿Estenografía en ficheros? ¿Dependerá del formato del fichero en cuestión no? La idea es buscar los fallos del formato en cuestión. El de archivos rar es una implementación en C++ de esta técnica: http://neobits.net/NeobitsOrg/560.html
|
|
|
En línea
|
|
|
|
|
Mensajes similares |
|
Asunto |
Iniciado por |
Respuestas |
Vistas |
Último mensaje |
|
|
convertir proyecto activex de C++ a vb
Programación Visual Basic
|
el_c0c0
|
0
|
2,012
|
1 Marzo 2009, 22:04 pm
por el_c0c0
|
|
|
Prefijo – Postfijo – Infijo (C#)
.NET (C#, VB.NET, ASP)
|
ahome31
|
0
|
6,895
|
2 Noviembre 2009, 06:37 am
por ahome31
|
|
|
Convertir Proyecto Windows Forms a Web
.NET (C#, VB.NET, ASP)
|
seba123neo
|
4
|
15,127
|
9 Noviembre 2011, 01:51 am
por seba123neo
|
|
|
convertir un proyecto en Opensource?
GNU/Linux
|
Kase
|
0
|
1,954
|
27 Marzo 2012, 09:36 am
por Kase
|
|
|
La calculadora de Windows es el proyecto más popular de todo GitHub desde ....
Noticias
|
wolfbcn
|
0
|
1,613
|
21 Marzo 2019, 02:15 am
por wolfbcn
|
|