Вычисление строки "3 * (4 + 2)" yield int 18

Существует ли в платформе .NET функция, которая может оценивать числовое выражение, содержащееся в строке, и возвращать результат? F.e .:

string mystring = "3*(2+4)";
int result = EvaluateExpression(mystring);
Console.Writeln(result); // Outputs 18

Есть ли стандартная функция фреймворка, которой вы можете заменить мой метод EvaluateExpression?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
103
0
181 938
13

Ответы 13

Вы можете довольно легко запустить это через CSharpCodeProvider с подходящей оберткой (тип и метод, в основном). Точно так же вы можете пройти через VB и т. д. Или JavaScript, как предложил другой ответ. На данный момент я не знаю ничего другого, что встроено во фреймворк.

Я ожидал, что .NET 4.0 с его поддержкой динамических языков может иметь лучшие возможности в этом отношении.

Это тоже невероятно плохая производительность.

Timothy Khouri 02.12.2008 15:44

Ага. Я надеюсь, что DLR хоть немного поможет с точки зрения производительности.

Jon Skeet 02.12.2008 16:27

Краткий ответ: я так не думаю. C# .Net скомпилирован (в байт-код) и, насколько мне известно, не может оценивать строки во время выполнения. Однако JScript .Net может; но я бы все же посоветовал вам самому написать код парсера и стекового оценщика.

Нет. Вам нужно будет использовать какую-то внешнюю библиотеку или написать собственный парсер. Если у вас есть на это время, я предлагаю написать свой собственный парсер, так как это довольно интересный проект. В противном случае вам нужно будет использовать что-то вроде bcParser.

Я знаю, что это интересно, я уже делал это дважды, один с c и один с C++. Мне просто было интересно, есть ли встроенная возможность с cs / .net, поскольку он поддерживает отражение, я подозревал, что это хорошая возможность.

sindre j 02.12.2008 16:30

Этот ответ необходимо удалить, потому что его можно упростить как комментарий. Голосование против и отметка внимания модератора.

Krythic 07.03.2017 02:30

Недавно мне нужно было сделать это для проекта, и в итоге я использовал 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

drzaus 05.12.2014 19:08

Вы можете посмотреть «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"]);
}

Может кто-нибудь объяснить, почему это работает?

Chris Trombley 17.08.2011 19:25

@ChrisTrombley: свойство DataColumn.Expression поддерживает небольшой язык, содержащий основные арифметические операторы и несколько полезных функций. Что делает @Petar, так это создание нового столбца со свойством Expression, установленным на указанное выражение. Впоследствии, когда он обращается к значению этого столбца, DataTable оценивает выражение и вычисляет значение, которое затем возвращается вызывающей стороне. Подробнее о поддерживаемых операторах и функциях см. msdn.microsoft.com/en-us/library/….

dotNET 16.06.2013 13:22

Использование компилятора для выполнения подразумевает утечку памяти, поскольку сгенерированные сборки загружаются и никогда не освобождаются. Это также менее эффективно, чем использование интерпретатора реальных выражений. Для этой цели вы можете использовать Ncalc, который является фреймворком с открытым исходным кодом исключительно с этой целью. Вы также можете определить свои собственные переменные и пользовательские функции, если уже включенных недостаточно.

Пример:

Expression e = new Expression("2 + 3 * 5");
Debug.Assert(17 == e.Evaluate());

Использование компилятора также представляет собой огромный риск для безопасности. Вы намереваетесь разрешить числовое выражение, но внезапно вы обнаружили нечто большее, чем PowerShell.

Joel Coehoorn 17.02.2013 04:06

Помогает ли это решение таким операторам, как Min, Max, Avg?

Imran Rizvi 09.12.2020 09:46

Это простой оценщик выражений, использующий стеки.

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();
    }
}

Что ж, это хорошо. Но он не следует арифметическому приоритету, как в квадратных скобках, он должен сначала вычислить деление, затем умножение, затем сложение и так далее.

Rick2047 23.11.2010 17:01

Заметил, что работает только с однозначными числами.

javydreamercsw 20.12.2015 01:44
(4+3)/2 не работает (ответ 2).
Dan W 20.12.2015 02:26

@DanW попробуйте: "((4 + 3) / 2)"

Tawani 21.12.2015 16:24

sqrt никогда не будет оцениваться, поскольку операция ограничена одним символом

The_Black_Smurf 24.04.2020 16:43

Также это не может обрабатывать 2-значные числа

John Williams 15.07.2020 12:28

Это выполняется справа налево, поэтому для выполнения выражения необходимо использовать правильный паратез.

    // 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());
    }

Этот работает нормально, другой похожий не работает с двухзначными числами.

javydreamercsw 20.12.2015 01:50

Обновлено: Понятно, что мне действительно нужно выделить сложение и вычитание отдельно, чтобы сделать его немного более совместимым с 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

Harm Salomons 31.01.2020 12:21

Я начал с этого поста в 2017 году, чтобы сделать свой ExpressionEvaluator. Это стало зрелой библиотекой. Код находится на гитхабе здесь CodingSeb.ExpressionEvaluator

Coding Seb 26.02.2020 13:00
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?

Eric 15.03.2017 00:12

.NET Core теперь поддерживает DataTable: blogs.msdn.microsoft.com/devfish/2017/05/15/…

Piedone 03.06.2017 18:20

Кажется грязным использовать таблицу данных для простых математических вычислений. NCalc или другая математическая библиотека будет лучшей идеей iMO.

Josh M. 08.06.2017 18:11

Интересно, что текущая реализация фреймворка также не полагается на существующие компоненты. Пространство имен System.Data имеет собственную (внутреннюю) реализацию, полноценный лексер / парсер с реляционные возможности, ориентированные на данные.

Cee McSharpface 10.09.2018 18:44

это, безусловно, лучшее, простое и быстрое решение с минимальными накладными расходами. Я бы +2, если бы мог

NappingRabbit 12.12.2019 15:39

Большое спасибо Рамешу. Я использовал версию его простого кода, чтобы вытащить строку из базы данных и использовать ее для выполнения логических операций в моем коде.

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  }

Другие вопросы по теме