Pull to refresh

Вычисление логического выражения из строки в C# (.NET)

Level of difficultyMedium
Reading time5 min
Views5.9K

В этой статье я продемонстрирую, как динамически вычислять логические математические выражения из строк в C#, с высокой производительностью. Решение, реализованное с использованием библиотеки .NET MathEvaluator, поддерживает логические операции в различных математических контекстах, включая программирование, научные вычисления и C#. Кроме того, библиотека позволяет расширять эти контексты, а также добавлять пользовательские переменные и функции.

Библиотека MathEvaluator и ее документация доступны на GitHub.

В предыдущей статье я уже подробно описал реализацию и методы быстрого вычисления математических выражений в C#. Здесь я сосредоточусь на конкретном случае вычисления логических выражений и сравню возможности и производительность MathEvaluator с известной библиотекой NCalc.

Поддерживаемые математические функции, операторы и константы

Библиотека MathEvaluator включает встроенные контексты для оценки сложных научных, программных и C# математических выражений. Она предлагает набор функций, операторов и констант, адаптированных к этим специфическим контекстам. Для получения полного списка поддерживаемых функций и возможностей, обратитесь к документации. При оценке логического выражения, если функция возвращает значение 0.0, оно интерпретируется как false, а любое другое значение считается true, также ведет себя функция Convert.ToBoolean предоставляемая .NET.

Сравнение с NCalc

Мы будем использовать BenchmarkDotNet для сравнения производительности. Подробности окружения:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.4112/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11800H 2.30GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.400
  [Host]   : .NET 6.0.33 (6.0.3324.36610), X64 RyuJIT AVX2
  .NET 6.0 : .NET 6.0.33 (6.0.3324.36610), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI

Пример 1: Вычисление логического программного выражения.

Сравним производительность вычисления логического выражения
A or not B and (C or B):

BenchmarkRunner.Run<Benchmarks>();

[SimpleJob(RuntimeMoniker.Net80)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class Benchmarks
{
    private readonly ProgrammingMathContext _context = new();

    private int _count;

    [Benchmark(Description = "MathEvaluator")]
    public bool MathEvaluator_EvaluateBoolean()
    {
        _count++;
        bool a = _count % 2 == 0; //randomizing values

        var parameters = new MathParameters();
        parameters.BindVariable(a, "A");
        parameters.BindVariable(!a, "B");
        parameters.BindVariable(a, "C");

        return "A or not B and (C or B)"
            .EvaluateBoolean(parameters, _context);
    }

    [Benchmark(Description = "NCalc")]
    public bool NCalc_Evaluate()
    {
        _count++;
        bool a = _count % 2 == 0; //randomizing values

        var expression = new Expression("A or not B and (C or B)", ExpressionOptions.NoCache);
        expression.Parameters["A"] = a;
        expression.Parameters["B"] = !a;
        expression.Parameters["C"] = a;

        return (bool)expression.Evaluate();
    }
}

Ниже приведены результаты сравнения производительности:

Пример 2: Вычисление логического алгебраического выражения.

NCalc не поддерживает логические алгебраические выражения, такие как A∨¬B∧(C∨B). В отличие от неё, MathEvaluator может вычислить такие выражения и может быть расширен с помощью пользовательских математических контекстов, так как MathEvaluator следует простым математическим правилам основанным на приоритете операторов добавление других контекстов не вызывает сложности.

Рассмотрим пример создания контекста, поддерживающего логические алгебраические выражения:

public class BooleanAlgebraMathContext : MathContext
{
    public BooleanAlgebraMathContext()
    {
        static double andFn(double left, double right)
            => left != default && right != default ? 1.0 : default;

        BindOperator(andFn, '∧', (int)EvalPrecedence.LogicalAnd);

        static double orFn(double left, double right)
            => left != default || right != default ? 1.0 : default;

        BindOperator(orFn, '∨', (int)EvalPrecedence.LogicalOr);

        static double xorFn(double left, double right)
            => left != default ^ right != default ? 1.0 : default;

        BindOperator(xorFn, '⊕', (int)EvalPrecedence.LogicalXor);

        static double logicalNegationFn(double right)
            => right == default ? 1.0 : default;

        BindOperandOperator(logicalNegationFn, '¬');
    }
}

Измерим производительность вычисления выражения A∨¬B∧(C∨B) с использованием вновь созданного класса BooleanAlgebraMathContext:

BenchmarkRunner.Run<Benchmarks>();

[SimpleJob(RuntimeMoniker.Net80)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class Benchmarks
{
    private readonly BooleanAlgebraMathContext _context = new();

    private int _count;

    [Benchmark(Description = "MathEvaluator")]
    public bool MathEvaluator_EvaluateBoolean()
    {
        _count++;
        bool a = _count % 2 == 0; //randomizing values

        var parameters = new MathParameters();
        parameters.BindVariable(a, "A");
        parameters.BindVariable(!a, "B");
        parameters.BindVariable(a, "C");

        return "A∨¬B∧(C∨B)"
            .EvaluateBoolean(parameters, _context);
    }
}

Ниже приведены результаты измерения производительности:

Пример 3: Вычисление математического выражения на C#.

Сравним производительность вычисления сложного логического выражения
A!= !B || A == C && false ^ -2.9 >= -12.9 + 0.1 / 0.01:

BenchmarkRunner.Run<Benchmarks>();

[SimpleJob(RuntimeMoniker.Net80)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class Benchmarks
{
    private readonly DotNetStandartMathContext _context = new();

    private int _count;

    [Benchmark(Description = "MathEvaluator")]
    public bool MathEvaluator_EvaluateBoolean()
    {
        _count++;
        bool a = _count % 2 == 0; //randomizing values

        var parameters = new MathParameters();
        parameters.BindVariable(a, "A");
        parameters.BindVariable(!a, "B");
        parameters.BindVariable(a, "C");

        return "A != !B || A == C && false ^ -2.9 >= -12.9 + 0.1 / 0.01"
            .EvaluateBoolean(parameters, _context);
    }

    [Benchmark(Description = "NCalc")]
    public bool NCalc_Evaluate()
    {
        _count++;
        bool a = _count % 2 == 0; //randomizing values

        var str = "A != !B || A == C && false ^ -2.9 >= -12.9 + 0.1 / 0.01";
        var expression = new Expression(str, ExpressionOptions.NoCache);
        expression.Parameters["A"] = a;
        expression.Parameters["B"] = !a;
        expression.Parameters["C"] = a;

        return Convert.ToBoolean(expression.Evaluate());
    }
}

Ниже приведены результаты сравнения производительности:

Пример 4: Вычисление пользовательской логической функции.

Рассмотрим, как расширить программный контекст для поддержки дополнительных логических функций. Например, создадим функцию if, которая принимает три аргумента: первый — это условие, второй указывает, что возвращать, если условие истинно, и третий определяет, что возвращать, если оно ложно:

var context = new ProgrammingMathContext();
context.BindFunction((c, v1, v2) => c != 0.0 ? v1 : v2, "if");

var result = "if(3 % a = 1, true, false)"
    .EvaluateBoolean(new { a = 2.0 }, context);

Заключение

Библиотека MathEvaluator — это мощный и гибкий инструмент для вычиления логических математических выражений в различных контекстах. Независимо от того, нужно ли вам обрабатывать логические операции в программировании, научные вычисления или выражения на C#, MathEvaluator обеспечивает превосходную производительность и расширяемость по сравнению с альтернативными решениями.

Если вы считаете этот проект ценным, рассмотрите возможность спонсирования меня на GitHub.

Спасибо! Если у вас есть идеи или предложения, пожалуйста, оставьте их в комментариях.

P.S.: NCalc имеет встроенное кэширование, включенное по умолчанию в последних версиях. Хотя это может улучшить производительность в бенчмарках, в реальных сценариях кэширование может увеличить использование памяти и неэффективно, если результаты вычислений зависят от значений переменных. В таких случаях компиляция является лучшей альтернативой.

Возможность компиляции математических выражений из строк была добавлена в MathEvaluator 2.0

Tags:
Hubs:
Total votes 6: ↑5 and ↓1+7
Comments40

Articles