В этой статье я продемонстрирую, как динамически вычислять логические математические выражения из строк в C#, с высокой производительностью. Решение, реализованное с использованием библиотеки .NET MathEvaluator, поддерживает логические операции в различных математических контекстах, включая программирование, научные вычисления и C#. Кроме того, библиотека позволяет расширять эти контексты, а также добавлять пользовательские переменные и функции.
![](https://habrastorage.org/getpro/habr/upload_files/d2e/ee1/0c5/d2eee10c5ad0d041c27e577f9a7ba9f5.jpg)
Библиотека 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.4037/23H2/2023Update/SunValley3)
11th Gen Intel Core i7-11800H 2.30GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 8.0.300
[Host] : .NET 6.0.30 (6.0.3024.21525), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.30 (6.0.3024.21525), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.5 (8.0.524.21615), 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_ProgrammingExpression()
{
_count++;
bool a = _count % 2 == 0; //randomizing values
return "A or not B and (C or B)"
.SetContext(_context)
.BindVariable(a, "A")
.BindVariable(!a, "B")
.BindVariable(a, "C")
.EvaluateBoolean();
}
[Benchmark(Description = "NCalc")]
public bool NCalc_Evaluate_ProgrammingExpression()
{
_count++;
bool a = _count % 2 == 0; //randomizing values
var expression = new Expression("A or not B and (C or B)");
expression.Parameters["A"] = a;
expression.Parameters["B"] = !a;
expression.Parameters["C"] = a;
return (bool)expression.Evaluate();
}
}
Ниже приведены результаты сравнения производительности:
![](https://habrastorage.org/getpro/habr/upload_files/40a/3dc/23c/40a3dc23c8b63fb13c0c6c96c6bdb21b.jpg)
Пример 2: Вычисление логического алгебраического выражения.
NCalc не поддерживает логические алгебраические выражения, такие как A∨¬B∧(C∨B)
. В отличие от неё, MathEvaluator может вычислить такие выражения и может быть расширен с помощью пользовательских математических контекстов, так как MathEvaluator следует простым математическим правилам основанным на приоритете операторов добавление других контекстов не вызывает сложности.
Рассмотрим пример создания контекста, поддерживающего логические алгебраические выражения:
using MathEvaluation.Context;
public class BooleanAlgebraMathContext : MathContext
{
public BooleanAlgebraMathContext()
{
static double andFn(double leftOperand, double rigntOperand)
=> leftOperand != default && rigntOperand != default ? 1.0 : default;
BindOperator(andFn, '∧', (int)EvalPrecedence.LogicalAnd);
static double orFn(double leftOperand, double rigntOperand)
=> leftOperand != default || rigntOperand != default ? 1.0 : default;
BindOperator(orFn, '∨', (int)EvalPrecedence.LogicalOr);
static double xorFn(double leftOperand, double rigntOperand)
=> leftOperand != default ^ rigntOperand != default ? 1.0 : default;
BindOperator(xorFn, '⊕', (int)EvalPrecedence.LogicalXor);
static double logicalNegationFn(double rigntOperand)
=> rigntOperand == default ? 1.0 : default;
BindConverter(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_ProgrammingExpression()
{
_count++;
bool a = _count % 2 == 0; //randomizing values
return "A∨¬B∧(C∨B)"
.SetContext(_context)
.BindVariable(a, "A")
.BindVariable(!a, "B")
.BindVariable(a, "C")
.EvaluateBoolean();
}
}
Ниже приведены результаты измерения производительности:
![](https://habrastorage.org/getpro/habr/upload_files/a9e/89d/e97/a9e89de973c99f04c3d693e6012bf218.jpg)
Пример 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_ProgrammingExpression()
{
_count++;
bool a = _count % 2 == 0; //randomizing values
return "A != !B || A == C && false ^ -2.9 >= -12.9 + 0.1 / 0.01"
.SetContext(_context)
.BindVariable(a, "A")
.BindVariable(!a, "B")
.BindVariable(a, "C")
.EvaluateBoolean();
}
[Benchmark(Description = "NCalc")]
public bool NCalc_Evaluate_ProgrammingExpression()
{
_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);
expression.Parameters["A"] = a;
expression.Parameters["B"] = !a;
expression.Parameters["C"] = a;
return Convert.ToBoolean(expression.Evaluate());
}
}
Ниже приведены результаты сравнения производительности:
![](https://habrastorage.org/getpro/habr/upload_files/a3d/11f/c49/a3d11fc493820166fa03ee3725c6057d.jpg)
Пример 4: Вычисление пользовательской логической функции.
Рассмотрим, как расширить программный контекст для поддержки дополнительных логических функций. Например, создадим функцию if, которая принимает три аргумента: первый — это условие, второй указывает, что возвращать, если условие истинно, и третий определяет, что возвращать, если оно ложно:
var context = new ProgrammingMathContext();
context.BindFunction((c, v1, v2) => c != 0.0 ? v1 : v2, "if");
var a = 2.0;
var result = "if(3 % a = 1, true, false)"
.SetContext(context)
.BindVariable(a)
.EvaluateBoolean();
Заключение
Библиотека MathEvaluator — это мощный и гибкий инструмент для вычиления логических математических выражений в различных контекстах. Независимо от того, нужно ли вам обрабатывать логические операции в программировании, научные вычисления или выражения на C#, MathEvaluator обеспечивает превосходную производительность и расширяемость по сравнению с альтернативными решениями.
Если вы считаете этот проект ценным, рассмотрите возможность спонсирования меня на GitHub.
Спасибо! Если у вас есть идеи или предложения, пожалуйста, оставьте их в комментариях.