c# - operadores como cadenas
(13)
Necesito evaluar una expresión mathmatical que se me presenta como una cadena en C #. Ejemplo noddy, pero entiende la cadena como la expresión.
Necesito la evaluación para llenar un int.
No hay Eval () en C # como en otros languges ...
String myString = "3*4";
Editar:
Estoy en VS2008
Probé el Microsoft.JScript. = Su método obsoleto (pero sigue cumpliendo - advertencia)
Sin embargo, el DLL de Microsoft.JScript que tengo docenas trabaja en
objeto público InvokeMember (nombre de la cadena, BindingFlags invokeAttr, Binder Binder, object target, object [] args);
Se queja de que falta un ";" Imagínate...
EDIT 2
Solución - fue el codeDom uno - funcionó ya que no hay problemas de seguridad - solo que alguna vez voy a ejecutar el código. Muchas gracias por las respuestas ...
Y el enlace al nuevo Dragon Book impresionante
EDIT 3
Matt dataTable.Compute () también funciona, incluso mejor para la seguridad. (se observa la verificación de parámetros)
¿Necesitarás acceder a los valores de otras variables al calcular una expresión?
Algunas otras sugerencias:
- Mono 2.0 (salió hoy) tiene un método eval.
- Puedes escribir fácilmente un pequeño dominio específico en boo.
- Puede crear un analizador EBNF de descendencia recursiva de la vieja escuela.
Cuando dice "como en otros idiomas", debería decir "como en los lenguajes dinámicos".
Para lenguajes dinámicos como python, ruby y muchos lenguajes interpretados , una función Eval () es un elemento natural. De hecho, probablemente sea incluso bastante trivial implementar la tuya.
Howver, .Net está en su núcleo una plataforma compilada estática, fuertemente tipada (al menos hasta que Dynamic Language Runtime obtenga más soporte). Esto tiene ventajas naturales como seguridad de inyección de código y verificación de tipos en tiempo de compilación que son difíciles de ignorar. Pero significa que una función Eval () no es tan buena, quiere poder compilar la expresión antes de tiempo. En este tipo de plataforma, generalmente hay otras maneras más seguras de llevar a cabo la misma tarea.
Después de buscar en Google, veo que existe la posibilidad de crear y compilar código sobre la marcha usando CodeDom. (Ver un tutorial ).
Personalmente, no creo que ese enfoque sea una muy buena idea, ya que el usuario puede ingresar el código que desee, pero ese puede ser un área para explorar (por ejemplo, solo validando la entrada, y solo permitiendo números y operaciones matemáticas simples) .
Echa un vistazo Huye
El intérprete de jscript puede hacerlo, o puede escribir su propio analizador si la expresión es simple (tenga cuidado, se complica muy rápido).
Estoy bastante seguro de que no hay un método directo "Eval (string)" en C # ya que no se interpreta.
Tenga en cuenta que la interpretación del código está sujeta a la inyección de código, tenga mucho cuidado :)
En un lenguaje interpretado, es posible que tenga la oportunidad de evaluar la cadena utilizando el intérprete. En C #, necesita un analizador sintáctico para el idioma en el que está escrita la cadena (el lenguaje de las expresiones matemáticas). Este es un ejercicio no trivial. Si quieres hacerlo, utiliza un analizador de descenso recursivo. Los primeros capítulos del "Libro del Dragón" (Compiladores: Diseño, etc., de Aho, Sethi y Ullman - 1st ed 1977 o 2nd ed 2007) tienen una buena explicación de lo que debe hacer.
Una alternativa podría ser incluir en su proyecto un componente escrito en Perl, que se supone que está disponible para .NET ahora, y usar Perl para hacer la evaluación.
Hice esto como un ejercicio personal en C # hace unas semanas.
Es bastante código y está mal comentado en algunos lugares. Pero funcionó con muchos casos de prueba.
¡Disfrutar!
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace
{
class Start
{
public static void Main(string[] args)
{
Evaluator ev;
string variableValue, eq;
Console.Write("Enter equation: ");
eq = Console.ReadLine();
while (eq != "quit")
{
ev = new Evaluator(eq);
foreach (Variable v in ev.Variables)
{
Console.Write(v.Name + " = ");
variableValue = Console.ReadLine();
ev.SetVariable(v.Name, Convert.ToDecimal(variableValue));
}
Console.WriteLine(ev.Evaluate());
Console.Write("Enter equation: ");
eq = Console.ReadLine();
}
}
}
class EvalNode
{
public virtual decimal Evaluate()
{
return decimal.Zero;
}
}
class ValueNode : EvalNode
{
decimal value;
public ValueNode(decimal v)
{
value = v;
}
public override decimal Evaluate()
{
return value;
}
public override string ToString()
{
return value.ToString();
}
}
class FunctionNode : EvalNode
{
EvalNode lhs = new ValueNode(decimal.Zero);
EvalNode rhs = new ValueNode(decimal.Zero);
string op = "+";
public string Op
{
get { return op; }
set
{
op = value;
}
}
internal EvalNode Rhs
{
get { return rhs; }
set
{
rhs = value;
}
}
internal EvalNode Lhs
{
get { return lhs; }
set
{
lhs = value;
}
}
public override decimal Evaluate()
{
decimal result = decimal.Zero;
switch (op)
{
case "+":
result = lhs.Evaluate() + rhs.Evaluate();
break;
case "-":
result = lhs.Evaluate() - rhs.Evaluate();
break;
case "*":
result = lhs.Evaluate() * rhs.Evaluate();
break;
case "/":
result = lhs.Evaluate() / rhs.Evaluate();
break;
case "%":
result = lhs.Evaluate() % rhs.Evaluate();
break;
case "^":
double x = Convert.ToDouble(lhs.Evaluate());
double y = Convert.ToDouble(rhs.Evaluate());
result = Convert.ToDecimal(Math.Pow(x, y));
break;
case "!":
result = Factorial(lhs.Evaluate());
break;
}
return result;
}
private decimal Factorial(decimal factor)
{
if (factor < 1)
return 1;
return factor * Factorial(factor - 1);
}
public override string ToString()
{
return "(" + lhs.ToString() + " " + op + " " + rhs.ToString() + ")";
}
}
public class Evaluator
{
string equation = "";
Dictionary<string, Variable> variables = new Dictionary<string, Variable>();
public string Equation
{
get { return equation; }
set { equation = value; }
}
public Variable[] Variables
{
get { return new List<Variable>(variables.Values).ToArray(); }
}
public void SetVariable(string name, decimal value)
{
if (variables.ContainsKey(name))
{
Variable x = variables[name];
x.Value = value;
variables[name] = x;
}
}
public Evaluator(string equation)
{
this.equation = equation;
SetVariables();
}
public decimal Evaluate()
{
return Evaluate(equation, new List<Variable>(variables.Values));
}
public decimal Evaluate(string text)
{
decimal result = decimal.Zero;
equation = text;
EvalNode parsed;
equation = equation.Replace(" ", "");
parsed = Parse(equation, "qx");
if (parsed != null)
result = parsed.Evaluate();
return result;
}
public decimal Evaluate(string text, List<Variable> variables)
{
foreach (Variable v in variables)
{
text = text.Replace(v.Name, v.Value.ToString());
}
return Evaluate(text);
}
private static bool EquationHasVariables(string equation)
{
Regex letters = new Regex(@"[A-Za-z]");
return letters.IsMatch(equation);
}
private void SetVariables()
{
Regex letters = new Regex(@"([A-Za-z]+)");
Variable v;
foreach (Match m in letters.Matches(equation, 0))
{
v = new Variable(m.Groups[1].Value, decimal.Zero);
if (!variables.ContainsKey(v.Name))
{
variables.Add(v.Name, v);
}
}
}
#region Parse V2
private Dictionary<string, string> parenthesesText = new Dictionary<string, string>();
/*
* 1. All the text in first-level parentheses is replaced with replaceText plus an index value.
* (All nested parentheses are parsed in recursive calls)
* 2. The simple function is parsed given the order of operations (reverse priority to
* keep the order of operations correct when evaluating).
* a. Addition (+), subtraction (-) -> left to right
* b. Multiplication (*), division (/), modulo (%) -> left to right
* c. Exponents (^) -> right to left
* d. Factorials (!) -> left to right
* e. No op (number, replaced parentheses)
* 3. When an op is found, a two recursive calls are generated -- parsing the LHS and
* parsing the RHS.
* 4. An EvalNode representing the root node of the evaluations tree is returned.
*
* Ex. 3 + 5 (3 + 5) * 8
* + *
* / / / /
* 3 5 + 8
* / /
* 3 + 5 * 8 3 5
* +
* / /
* 3 *
* / /
* 5 8
*/
/// <summary>
/// Parses the expression and returns the root node of a tree.
/// </summary>
/// <param name="eq">Equation to be parsed</param>
/// <param name="replaceText">Text base that replaces text in parentheses</param>
/// <returns></returns>
private EvalNode Parse(string eq, string replaceText)
{
int randomKeyIndex = 0;
eq = eq.Replace(" ", "");
if (eq.Length == 0)
{
return new ValueNode(decimal.Zero);
}
int leftParentIndex = -1;
int rightParentIndex = -1;
SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);
//remove extraneous outer parentheses
while (leftParentIndex == 0 && rightParentIndex == eq.Length - 1)
{
eq = eq.Substring(1, eq.Length - 2);
SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);
}
//Pull out all expressions in parentheses
replaceText = GetNextReplaceText(replaceText, randomKeyIndex);
while (leftParentIndex != -1 && rightParentIndex != -1)
{
//replace the string with a random set of characters, stored extracted text in dictionary keyed on the random set of chars
string p = eq.Substring(leftParentIndex, rightParentIndex - leftParentIndex + 1);
eq = eq.Replace(p, replaceText);
parenthesesText.Add(replaceText, p);
leftParentIndex = 0;
rightParentIndex = 0;
replaceText = replaceText.Remove(replaceText.LastIndexOf(randomKeyIndex.ToString()));
randomKeyIndex++;
replaceText = GetNextReplaceText(replaceText, randomKeyIndex);
SetIndexes(eq, ref leftParentIndex, ref rightParentIndex);
}
/*
* Be sure to implement these operators in the function node class
*/
char[] ops_order0 = new char[2] { ''+'', ''-'' };
char[] ops_order1 = new char[3] { ''*'', ''/'', ''%'' };
char[] ops_order2 = new char[1] { ''^'' };
char[] ops_order3 = new char[1] { ''!'' };
/*
* In order to evaluate nodes LTR, the right-most node must be the root node
* of the tree, which is why we find the last index of LTR ops. The reverse
* is the case for RTL ops.
*/
int order0Index = eq.LastIndexOfAny(ops_order0);
if (order0Index > -1)
{
return CreateFunctionNode(eq, order0Index, replaceText + "0");
}
int order1Index = eq.LastIndexOfAny(ops_order1);
if (order1Index > -1)
{
return CreateFunctionNode(eq, order1Index, replaceText + "0");
}
int order2Index = eq.IndexOfAny(ops_order2);
if (order2Index > -1)
{
return CreateFunctionNode(eq, order2Index, replaceText + "0");
}
int order3Index = eq.LastIndexOfAny(ops_order3);
if (order3Index > -1)
{
return CreateFunctionNode(eq, order3Index, replaceText + "0");
}
//no operators...
eq = eq.Replace("(", "");
eq = eq.Replace(")", "");
if (char.IsLetter(eq[0]))
{
return Parse(parenthesesText[eq], replaceText + "0");
}
return new ValueNode(decimal.Parse(eq));
}
private string GetNextReplaceText(string replaceText, int randomKeyIndex)
{
while (parenthesesText.ContainsKey(replaceText))
{
replaceText = replaceText + randomKeyIndex.ToString();
}
return replaceText;
}
private EvalNode CreateFunctionNode(string eq, int index, string randomKey)
{
FunctionNode func = new FunctionNode();
func.Op = eq[index].ToString();
func.Lhs = Parse(eq.Substring(0, index), randomKey);
func.Rhs = Parse(eq.Substring(index + 1), randomKey);
return func;
}
#endregion
/// <summary>
/// Find the first set of parentheses
/// </summary>
/// <param name="eq"></param>
/// <param name="leftParentIndex"></param>
/// <param name="rightParentIndex"></param>
private static void SetIndexes(string eq, ref int leftParentIndex, ref int rightParentIndex)
{
leftParentIndex = eq.IndexOf(''('');
rightParentIndex = eq.IndexOf('')'');
int tempIndex = eq.IndexOf(''('', leftParentIndex + 1);
while (tempIndex != -1 && tempIndex < rightParentIndex)
{
rightParentIndex = eq.IndexOf('')'', rightParentIndex + 1);
tempIndex = eq.IndexOf(''('', tempIndex + 1);
}
}
}
public struct Variable
{
public string Name;
public decimal Value;
public Variable(string n, decimal v)
{
Name = n;
Value = v;
}
}
}
Puedes usar el intérprete jscript. Un gran artículo para él está aquí: http://www.odetocode.com/Articles/80.aspx
MS tiene una muestra llamada Dynamic Query Library. Lo proporciona el equipo LINQ para construir dinámicamente consultas LINQ como: consulta Dim = Northwind.Products.Where ("CategoryID = 2") Puede verificar si ofrece capacidades de matemática rudimentarias.
Todas las demás respuestas son posibles en exceso.
Si todo lo que necesitas es aritmética simple, haz esto.
DataTable dummy = new DataTable();
Console.WriteLine(dummy.Compute("15 / 3",string.Empty));
EDITAR: un poco más de información. Consulte la documentación de MSDN para la propiedad Expression
de la clase System.Data.DataColumn
. El contenido de "Sintaxis de expresión" describe una lista de comandos que puede usar además de los operadores aritméticos. (por ejemplo, IIF, LEN, etc.). ¡Gracias a todos por votar mi primera respuesta publicada!
De la forma en que lo veo, tienes dos opciones: usa un evaluador de expresiones o constructo, compila y ejecuta código C # sobre la marcha.
Me gustaría ir con una biblioteca de evaluación de expresiones, ya que no tiene que preocuparse por ningún problema de seguridad. Es decir, es posible que no pueda usar la generación de código en entornos de confianza media, como la mayoría de los servidores de alojamiento compartido.
Aquí hay un ejemplo para generar código para evaluar expresiones: http://www.vbforums.com/showthread.php?t=397264
Publiqué una fuente para un evaluador matemático Java (1 clase, <10 KiB) ultracompacto en mi sitio web. Debe ser trivial para portar esto a C #. Hay otros que podrían hacer más, pero esto es muy capaz y es muy pequeño .