Существует ли в платформе .NET функция, которая может оценивать числовое выражение, содержащееся в строке, и возвращать результат? F.e .:
string mystring = "3*(2+4)";
int result = EvaluateExpression(mystring);
Console.Writeln(result); // Outputs 18
Есть ли стандартная функция фреймворка, которой вы можете заменить мой метод EvaluateExpression?





Вы можете довольно легко запустить это через CSharpCodeProvider с подходящей оберткой (тип и метод, в основном). Точно так же вы можете пройти через VB и т. д. Или JavaScript, как предложил другой ответ. На данный момент я не знаю ничего другого, что встроено во фреймворк.
Я ожидал, что .NET 4.0 с его поддержкой динамических языков может иметь лучшие возможности в этом отношении.
Ага. Я надеюсь, что DLR хоть немного поможет с точки зрения производительности.
Краткий ответ: я так не думаю. C# .Net скомпилирован (в байт-код) и, насколько мне известно, не может оценивать строки во время выполнения. Однако JScript .Net может; но я бы все же посоветовал вам самому написать код парсера и стекового оценщика.
Нет. Вам нужно будет использовать какую-то внешнюю библиотеку или написать собственный парсер. Если у вас есть на это время, я предлагаю написать свой собственный парсер, так как это довольно интересный проект. В противном случае вам нужно будет использовать что-то вроде bcParser.
Я знаю, что это интересно, я уже делал это дважды, один с c и один с C++. Мне просто было интересно, есть ли встроенная возможность с cs / .net, поскольку он поддерживает отражение, я подозревал, что это хорошая возможность.
Этот ответ необходимо удалить, потому что его можно упростить как комментарий. Голосование против и отметка внимания модератора.
Недавно мне нужно было сделать это для проекта, и в итоге я использовал IronPython для этого. Вы можете объявить экземпляр движка, а затем передать любое допустимое выражение Python и получить результат. Если вы просто делаете простые математические выражения, этого будет достаточно. В итоге мой код выглядел примерно так:
IronPython.Hosting.PythonEngine pythonEngine = new IronPython.Hosting.PythonEngine();
string expression = "3*(2+4)";
double result = pythonEngine.EvaluateAs<double>(expression);
Вероятно, вы не захотите создавать движок для каждого выражения. Также понадобится ссылка на IronPython.dll
Я только что наткнулся на нечто подобное - движок javascript в .NET, от того же парня, который написал NCalc - github.com/sebastienros/jint
Вы можете посмотреть «XpathNavigator.Evaluate». Я использовал его для обработки математических выражений для своего GridView, и у меня он отлично работает.
Вот код, который я использовал для своей программы:
public static double Evaluate(string expression)
{
return (double)new System.Xml.XPath.XPathDocument
(new StringReader("<r/>")).CreateNavigator().Evaluate
(string.Format("number({0})", new
System.Text.RegularExpressions.Regex(@"([\+\-\*])")
.Replace(expression, " ${1} ")
.Replace("/", " div ")
.Replace("%", " mod ")));
}
Попробуй это:
static double Evaluate(string expression) {
var loDataTable = new DataTable();
var loDataColumn = new DataColumn("Eval", typeof (double), expression);
loDataTable.Columns.Add(loDataColumn);
loDataTable.Rows.Add(0);
return (double) (loDataTable.Rows[0]["Eval"]);
}
Может кто-нибудь объяснить, почему это работает?
@ChrisTrombley: свойство DataColumn.Expression поддерживает небольшой язык, содержащий основные арифметические операторы и несколько полезных функций. Что делает @Petar, так это создание нового столбца со свойством Expression, установленным на указанное выражение. Впоследствии, когда он обращается к значению этого столбца, DataTable оценивает выражение и вычисляет значение, которое затем возвращается вызывающей стороне. Подробнее о поддерживаемых операторах и функциях см. msdn.microsoft.com/en-us/library/….
Использование компилятора для выполнения подразумевает утечку памяти, поскольку сгенерированные сборки загружаются и никогда не освобождаются. Это также менее эффективно, чем использование интерпретатора реальных выражений. Для этой цели вы можете использовать Ncalc, который является фреймворком с открытым исходным кодом исключительно с этой целью. Вы также можете определить свои собственные переменные и пользовательские функции, если уже включенных недостаточно.
Пример:
Expression e = new Expression("2 + 3 * 5");
Debug.Assert(17 == e.Evaluate());
Использование компилятора также представляет собой огромный риск для безопасности. Вы намереваетесь разрешить числовое выражение, но внезапно вы обнаружили нечто большее, чем PowerShell.
Помогает ли это решение таким операторам, как Min, Max, Avg?
Это простой оценщик выражений, использующий стеки.
public class MathEvaluator
{
public static void Run()
{
Eval("(1+2)");
Eval("5*4/2");
Eval("((3+5)-6)");
}
public static void Eval(string input)
{
var ans = Evaluate(input);
Console.WriteLine(input + " = " + ans);
}
public static double Evaluate(String input)
{
String expr = "(" + input + ")";
Stack<String> ops = new Stack<String>();
Stack<Double> vals = new Stack<Double>();
for (int i = 0; i < expr.Length; i++)
{
String s = expr.Substring(i, 1);
if (s.Equals("(")){}
else if (s.Equals("+")) ops.Push(s);
else if (s.Equals("-")) ops.Push(s);
else if (s.Equals("*")) ops.Push(s);
else if (s.Equals("/")) ops.Push(s);
else if (s.Equals("sqrt")) ops.Push(s);
else if (s.Equals(")"))
{
int count = ops.Count;
while (count > 0)
{
String op = ops.Pop();
double v = vals.Pop();
if (op.Equals("+")) v = vals.Pop() + v;
else if (op.Equals("-")) v = vals.Pop() - v;
else if (op.Equals("*")) v = vals.Pop()*v;
else if (op.Equals("/")) v = vals.Pop()/v;
else if (op.Equals("sqrt")) v = Math.Sqrt(v);
vals.Push(v);
count--;
}
}
else vals.Push(Double.Parse(s));
}
return vals.Pop();
}
}
Что ж, это хорошо. Но он не следует арифметическому приоритету, как в квадратных скобках, он должен сначала вычислить деление, затем умножение, затем сложение и так далее.
Заметил, что работает только с однозначными числами.
(4+3)/2 не работает (ответ 2).
@DanW попробуйте: "((4 + 3) / 2)"
sqrt никогда не будет оцениваться, поскольку операция ограничена одним символом
Также это не может обрабатывать 2-значные числа
Это выполняется справа налево, поэтому для выполнения выражения необходимо использовать правильный паратез.
// 2+(100/5)+10 = 32
//((2.5+10)/5)+2.5 = 5
// (2.5+10)/5+2.5 = 1.6666
public static double Evaluate(String expr)
{
Stack<String> stack = new Stack<String>();
string value = "";
for (int i = 0; i < expr.Length; i++)
{
String s = expr.Substring(i, 1);
char chr = s.ToCharArray()[0];
if (!char.IsDigit(chr) && chr != '.' && value != "")
{
stack.Push(value);
value = "";
}
if (s.Equals("(")) {
string innerExp = "";
i++; //Fetch Next Character
int bracketCount=0;
for (; i < expr.Length; i++)
{
s = expr.Substring(i, 1);
if (s.Equals("("))
bracketCount++;
if (s.Equals(")"))
if (bracketCount == 0)
break;
else
bracketCount--;
innerExp += s;
}
stack.Push(Evaluate(innerExp).ToString());
}
else if (s.Equals("+")) stack.Push(s);
else if (s.Equals("-")) stack.Push(s);
else if (s.Equals("*")) stack.Push(s);
else if (s.Equals("/")) stack.Push(s);
else if (s.Equals("sqrt")) stack.Push(s);
else if (s.Equals(")"))
{
}
else if (char.IsDigit(chr) || chr == '.')
{
value += s;
if (value.Split('.').Length > 2)
throw new Exception("Invalid decimal.");
if (i == (expr.Length - 1))
stack.Push(value);
}
else
throw new Exception("Invalid character.");
}
double result = 0;
while (stack.Count >= 3)
{
double right = Convert.ToDouble(stack.Pop());
string op = stack.Pop();
double left = Convert.ToDouble(stack.Pop());
if (op == "+") result = left + right;
else if (op == "+") result = left + right;
else if (op == "-") result = left - right;
else if (op == "*") result = left * right;
else if (op == "/") result = left / right;
stack.Push(result.ToString());
}
return Convert.ToDouble(stack.Pop());
}
Этот работает нормально, другой похожий не работает с двухзначными числами.
Обновлено: Понятно, что мне действительно нужно выделить сложение и вычитание отдельно, чтобы сделать его немного более совместимым с BODMAS.
Большое спасибо Раджешу Джинаге за его подход, основанный на стеке. Я нашел это действительно полезным для моих нужд. Следующий код представляет собой небольшую модификацию метода Раджеша, который сначала обрабатывает деление, затем умножение, а затем завершается сложением и вычитанием. Это также позволит использовать логические значения в выражениях, где истина трактуется как 1, а ложь 0. Это позволяет использовать логическую логику в выражениях.
public static double Evaluate(string expr)
{
expr = expr.ToLower();
expr = expr.Replace(" ", "");
expr = expr.Replace("true", "1");
expr = expr.Replace("false", "0");
Stack<String> stack = new Stack<String>();
string value = "";
for (int i = 0; i < expr.Length; i++)
{
String s = expr.Substring(i, 1);
// pick up any doublelogical operators first.
if (i < expr.Length - 1)
{
String op = expr.Substring(i, 2);
if (op == "< = " || op == "> = " || op == "= = ")
{
stack.Push(value);
value = "";
stack.Push(op);
i++;
continue;
}
}
char chr = s.ToCharArray()[0];
if (!char.IsDigit(chr) && chr != '.' && value != "")
{
stack.Push(value);
value = "";
}
if (s.Equals("("))
{
string innerExp = "";
i++; //Fetch Next Character
int bracketCount = 0;
for (; i < expr.Length; i++)
{
s = expr.Substring(i, 1);
if (s.Equals("(")) bracketCount++;
if (s.Equals(")"))
{
if (bracketCount == 0) break;
bracketCount--;
}
innerExp += s;
}
stack.Push(Evaluate(innerExp).ToString());
}
else if (s.Equals("+") ||
s.Equals("-") ||
s.Equals("*") ||
s.Equals("/") ||
s.Equals("<") ||
s.Equals(">"))
{
stack.Push(s);
}
else if (char.IsDigit(chr) || chr == '.')
{
value += s;
if (value.Split('.').Length > 2)
throw new Exception("Invalid decimal.");
if (i == (expr.Length - 1))
stack.Push(value);
}
else
{
throw new Exception("Invalid character.");
}
}
double result = 0;
List<String> list = stack.ToList<String>();
for (int i = list.Count - 2; i >= 0; i--)
{
if (list[i] == "/")
{
list[i] = (Convert.ToDouble(list[i - 1]) / Convert.ToDouble(list[i + 1])).ToString();
list.RemoveAt(i + 1);
list.RemoveAt(i - 1);
i -= 2;
}
}
for (int i = list.Count - 2; i >= 0; i--)
{
if (list[i] == "*")
{
list[i] = (Convert.ToDouble(list[i - 1]) * Convert.ToDouble(list[i + 1])).ToString();
list.RemoveAt(i + 1);
list.RemoveAt(i - 1);
i -= 2;
}
}
for (int i = list.Count - 2; i >= 0; i--)
{
if (list[i] == "+")
{
list[i] = (Convert.ToDouble(list[i - 1]) + Convert.ToDouble(list[i + 1])).ToString();
list.RemoveAt(i + 1);
list.RemoveAt(i - 1);
i -= 2;
}
}
for (int i = list.Count - 2; i >= 0; i--)
{
if (list[i] == "-")
{
list[i] = (Convert.ToDouble(list[i - 1]) - Convert.ToDouble(list[i + 1])).ToString();
list.RemoveAt(i + 1);
list.RemoveAt(i - 1);
i -= 2;
}
}
stack.Clear();
for (int i = 0; i < list.Count; i++)
{
stack.Push(list[i]);
}
while (stack.Count >= 3)
{
double right = Convert.ToDouble(stack.Pop());
string op = stack.Pop();
double left = Convert.ToDouble(stack.Pop());
if (op == "<") result = (left < right) ? 1 : 0;
else if (op == ">") result = (left > right) ? 1 : 0;
else if (op == "< = ") result = (left <= right) ? 1 : 0;
else if (op == "> = ") result = (left >= right) ? 1 : 0;
else if (op == "= = ") result = (left == right) ? 1 : 0;
stack.Push(result.ToString());
}
return Convert.ToDouble(stack.Pop());
}
Я знаю, что, вероятно, будет более чистый способ сделать это, подумал, что id просто поделится первым взглядом на это, если кто-то сочтет это полезным.
будьте осторожны: по мере роста стека деление и вычитание должны быть проиндексированы как list [i + 1] operator list [i - 1]. Этот код имеет ответ на выражение '5-3' = -2 вместо 2
Я начал с этого поста в 2017 году, чтобы сделать свой ExpressionEvaluator. Это стало зрелой библиотекой. Код находится на гитхабе здесь CodingSeb.ExpressionEvaluator
static double Evaluate(string expression) {
var loDataTable = new DataTable();
var loDataColumn = new DataColumn("Eval", typeof (double), expression);
loDataTable.Columns.Add(loDataColumn);
loDataTable.Rows.Add(0);
return (double) (loDataTable.Rows[0]["Eval"]);
}
Объяснение, как это работает:
Сначала мы создаем таблицу в части var loDataTable = new DataTable();, как в движке базы данных (например, MS SQL).
Затем столбец с некоторыми конкретными параметрами (var loDataColumn = new DataColumn("Eval", typeof (double), expression);).
Параметр "Eval" - это имя столбца (атрибут ColumnName).
typeof (double) - это тип данных, которые будут храниться в столбце, что эквивалентно помещению вместо этого System.Type.GetType("System.Double");.
expression - это строка, которую получает метод Evaluate, и которая хранится в атрибуте Expression столбца. Этот атрибут предназначен для действительно конкретной цели (очевидной), заключающейся в том, что каждая строка, помещенная в столбец, будет заполнена «выражением», и он практически принимает все, что можно поместить в SQL-запрос. Обратитесь к http://msdn.microsoft.com/en-us/library/system.data.datacolumn.expression(v=vs.100).aspx, чтобы узнать, что можно поместить в атрибут Expression и как он оценивается.
Затем loDataTable.Columns.Add(loDataColumn); добавляет столбец loDataColumn в таблицу loDataTable.
Затем в таблицу добавляется строка с персонализированным столбцом с атрибутом Expression, что выполняется с помощью loDataTable.Rows.Add(0);. Когда мы добавляем эту строку, ячейка столбца «Eval» таблицы loDataTable автоматически заполняется ее атрибутом «Expression», и, если в ней есть операторы, запросы SQL и т. д., Она оценивается и затем сохраняется в ячейке, поэтому , здесь происходит «волшебство», строка с операторами вычисляется и сохраняется в ячейке ...
Наконец, просто верните значение, хранящееся в ячейке столбца «Eval» в строке 0 (это индекс, отсчет начинается с нуля), и выполните преобразование в двойное значение с помощью return (double) (loDataTable.Rows[0]["Eval"]);.
И все ... работа сделана!
И вот код, который нужно понять, который делает то же самое ... Это не внутри метода, и это тоже объясняется.
DataTable MyTable = new DataTable();
DataColumn MyColumn = new DataColumn();
MyColumn.ColumnName = "MyColumn";
MyColumn.Expression = "5+5/5"
MyColumn.DataType = typeof(double);
MyTable.Columns.Add(MyColumn);
DataRow MyRow = MyTable.NewRow();
MyTable.Rows.Add(MyRow);
return (double)(MyTable.Rows[0]["MyColumn"]);
Сначала создайте таблицу с DataTable MyTable = new DataTable();
Затем колонка с DataColumn MyColumn = new DataColumn();
Затем мы даем имя столбцу. Это позволяет нам искать его содержимое, когда оно хранится в таблице. Сделано через MyColumn.ColumnName = "MyColumn";
Затем Выражение, здесь мы можем поместить переменную типа string, в этом случае есть предопределенная строка "5 + 5/5", результатом которой будет 6.
Тип данных, сохраняемых в столбце MyColumn.DataType = typeof(double);
Добавьте столбец в таблицу ... MyTable.Columns.Add(MyColumn);
Сделайте строку для вставки в таблицу, которая копирует структуру таблицы DataRow MyRow = MyTable.NewRow();
Добавьте строку в таблицу с MyTable.Rows.Add(MyRow);
И вернуть значение ячейки в строке 0 столбца MyColumn таблицы MyTable с return (double)(MyTable.Rows[0]["MyColumn"]);
Урок сделан !!!
Если вы хотите оценить строковое выражение, используйте приведенный ниже фрагмент кода.
using System.Data;
DataTable dt = new DataTable();
var v = dt.Compute("3 * (2+4)","");
Я получил много пользы от этого ответа в применении к старой школе .Net, но в новой структуре Core нет DataTables. Есть ли что-нибудь, что может сделать это в .Net Core?
.NET Core теперь поддерживает DataTable: blogs.msdn.microsoft.com/devfish/2017/05/15/…
Кажется грязным использовать таблицу данных для простых математических вычислений. NCalc или другая математическая библиотека будет лучшей идеей iMO.
Интересно, что текущая реализация фреймворка также не полагается на существующие компоненты. Пространство имен System.Data имеет собственную (внутреннюю) реализацию, полноценный лексер / парсер с реляционные возможности, ориентированные на данные.
это, безусловно, лучшее, простое и быстрое решение с минимальными накладными расходами. Я бы +2, если бы мог
Большое спасибо Рамешу. Я использовал версию его простого кода, чтобы вытащить строку из базы данных и использовать ее для выполнения логических операций в моем коде.
x - это число вроде 1500 или 2100 или что-то еще.
функция будет хранимой оценкой, например x> 1400 и x <1600
function = relation[0].Replace("and","&&").Replace("x",x);
DataTable f_dt = new DataTable();
var f_var = f_dt.Compute(function,"");
if (bool.Parse(f_var.ToString()) { do stuff }
Это тоже невероятно плохая производительность.