在AForge遗传.net框架中是否有一个解析器用于解决未知方程?



我必须找到解析器来解决未知方程。用户输入两个数字,解析器解析方程求解。例如:

方程:X ^3 + y^2 + sin60°

用户输入:x = 4, y = 2

必须使用AForge完成。净框架。控制台应用程序/WinForms

我一直在寻找它在AForge . net文档,但不能找到一些匹配我的问题。

我不相信在AForge中有一个正则数学表达式的求值器,但是你可以使用PolishExpression类来求用反向波兰符号写的表达式的值:

using AForge;
string expression = "$0 $0 $0 * * $1 $1 * + " + (Math.PI / 3) + " sin +";
// variables for the expression
double x = 4;
double y = 2;
double[] vars = new double[] { x, y };
// expression evaluation
double result = PolishExpression.Evaluate(expression, vars);
Console.WriteLine("Polish expression evaluation: " + result);

产生值68.86602540378443。也没有直接指数算子,所以我只是把x乘以它自己三次。但是你可以用对数来代替:

string expression = "$0 ln 3 * exp $1 ln 2 * exp + " + (Math.PI / 3) + " sin +";

你也可以确认这与你在c#中得到的相同(注意,为了使用Math.sin,你必须将60度转换为弧度):

double TestEvaluate(double x, double y)
{
// x^3 + y^2 + sin60°
// Note that Math.sin accepts radians, so we must convert 60°
return Math.Pow(x, 3) + Math.Pow(y, 2) + Math.Sin((Math.PI / 180) * 60);
}
Console.WriteLine("C# evalution: " + TestEvaluate(x, y));

但是,如果你不想在表达式中使用波兰符号,我建议你使用另一个库。

我也尝试了上面的代码示例"AForge. math . expression",但我不相信这个类存在于AForge。数学经过2.2.5。

解决方案

看来你需要一个表达式解析器。我决定写一个,它使用System.Linq.Expression来构建表达式树。我从https://github.com/toptensoftware/SimpleExpressionEngine的简单表达式解析器开始,并从那里构建。

示例代码

首先是有趣的东西。看看这个测试代码

public static void TestSOExpr()
{            
string expression = "x^3 + y^2 + sin(60°)";
Parser parser = new Parser(expression);
Console.WriteLine("Expression: ");
Console.WriteLine(parser.Expression);
Console.WriteLine("Arguments: ");
foreach (var item in parser.Arguments)
{
Console.WriteLine(item);
}
Func<double,double,double> f = parser.CompileBinary();
double x = 4, y = 2;
Console.WriteLine($"f({x},{y}) = {f(x, y)}");
}

与输出

Expression:
(((x ^ 3) + (y * y)) + sin((60 * 0.0174532925199433)))
Arguments:
x
y
f(4,2) = 68.8660254037844

结果是数值正确的。我们花了很多时间试图用隐含乘法将°作为一个常数值来处理。我对结果很满意。

<标题>

类结构要达到这个目标需要三个类

标记

这个信息类保存从字符串中提取的数据,如名称、值、操作符、函数和括号。

public enum TokenType
{
EOF,
Operator,
StartBlock,
EndBlock,
Delimiter,
Identifier,
Number,
}
public readonly struct Token
{
public static readonly Token Empty = new Token(TokenType.EOF, string.Empty, 0, 0);
public Token(Token token, int level) : this()
{
this = token;
Level = level;
}
Token(TokenType type, string symbol, double number, int level) : this()
{
Type = type;
Symbol = symbol;
Number = number;
Level = level;
}
public static Token Operator(char symbol, int level = 0) => new Token(TokenType.Operator, symbol.ToString(), 0, level);
public static Token StartBlock(char symbol, int level = 0) => new Token(TokenType.StartBlock, symbol.ToString(), 0, level);
public static Token EndBlock(char symbol, int level = 0) => new Token(TokenType.EndBlock, symbol.ToString(), 0, level);
public static Token Delimiter(char symbol, int level = 0) => new Token(TokenType.Delimiter, symbol.ToString(), 0, level);
public static Token Identifier(string symbol, int level = 0) => new Token(TokenType.Identifier, symbol, 0, level);
public static Token Value(double number, int level = 0) => new Token(TokenType.Number, string.Empty, number, level);
public static Token operator +(Token token, int delta) => new Token(token, token.Level + delta);
public static Token operator -(Token token, int delta) => new Token(token, token.Level - delta);
public TokenType Type { get; }
public string Symbol { get; }
public double Number { get; }
public int Level { get; }
public bool IsEOF() => Type == TokenType.EOF;
public bool IsOperator(char @operator) => IsOperator(out char op) && op == @operator;
public bool IsOperator(out char @operator)
{
if (Type == TokenType.Operator)
{
@operator = Symbol[0];
return true;
}
@operator = '';
return false;
}
public bool IsIdentifier(string symbol) => IsIdentifier(out string x) && x.Equals(symbol);
public bool IsIdentifier(out string symbol)
{
if (Type == TokenType.Identifier)
{
symbol = Symbol;
return true;
}
symbol = string.Empty;
return false;
}
public bool IsNumber(double value) => IsNumber(out double x) && x.Equals(value);
public bool IsNumber(out double value)
{
if (Type == TokenType.Number)
{
value = Number;
return true;
}
value = 0;
return false;
}
public bool IsStartBlock(char delimiter) => IsStartBlock(out char sym) && sym == delimiter;
public bool IsStartBlock(out char delimiter)
{
if (Type == TokenType.StartBlock)
{
delimiter = Symbol[0];
return true;
}
delimiter = '';
return false;
}
public bool IsEndBlock(char delimiter) => IsEndBlock(out char sym) && sym == delimiter;
public bool IsEndBlock(out char delimiter)
{
if (Type == TokenType.EndBlock)
{
delimiter = Symbol[0];
return true;
}
delimiter = '';
return false;
}
public bool IsDelimiter(char delimiter) => IsDelimiter(out char sym) && sym == delimiter;
public bool IsDelimiter(out char delimiter)
{
if (Type == TokenType.Delimiter)
{
delimiter = Symbol[0];
return true;
}
delimiter = '';
return false;
}
public override string ToString()
{
var tab = new string('t', Level);
switch (Type)
{
case TokenType.EOF:
return $"{tab}{Type}";
case TokenType.Operator:
case TokenType.StartBlock:
case TokenType.EndBlock:
case TokenType.Delimiter:
case TokenType.Identifier:
return $"{tab}{Type}: {Symbol}";
case TokenType.Number:
return $"{tab}{Type}: {Number}";
default:
throw new NotSupportedException();
}
}
}

记号赋予器

标记器的工作是从字符串中提取Token。这是用.MoveNext().Reset()函数一步一步地完成的。在每一步中,.Current属性保存令牌信息。

/// <summary>
/// Parses strings into Tokens for further processing.
/// </summary>
/// <remarks>Code taken from https://github.com/toptensoftware/SimpleExpressionEngine </remarks>
public class Tokenizer : IEnumerator<Token>, IEnumerable<Token>
{
readonly string _expression;
TextReader _reader;
char _currentChar;
public Tokenizer(string expression)
{
_expression = expression;
Reset();
}
public Token Current { get; private set; }
object System.Collections.IEnumerator.Current { get => Current; }
public IEnumerator<Token> GetEnumerator() => this;
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();

public void Reset()
{
_reader = new StringReader(_expression);
NextChar();
//MoveNext();
}
// Read the next character from the input strem
// and store it in _currentChar, or load '' if EOF
void NextChar()
{
int ch = _reader.Read();
_currentChar = ch < 0 ? '' : (char)ch;
}
public bool MoveNext()
{
// Skip whitespace
while (char.IsWhiteSpace(_currentChar))
{
NextChar();
}
switch (_currentChar)
{
case '':
{
if (Current.Level > 0)
{
throw new InvalidOperationException();
}
Current = Token.Empty;
return false;
}
case '+':
case '-':
case '*':
case '/':
case '^':
case '=':
{
Current = Token.Operator(_currentChar, Current.Level);
NextChar();
return true;
}
case '(':
{
Current = Token.StartBlock(_currentChar, Current.Level + 1);
NextChar();
return true;
}
case ')':
{
Current = Token.EndBlock(_currentChar, Current.Level - 1);
NextChar();
return true;
}
case ';':
case ',':
{
Current = Token.Delimiter(_currentChar, Current.Level);
NextChar();
return true;
}
}
if (char.IsDigit(_currentChar) || _currentChar == '.')
{
// Capture digits/decimal point
StringBuilder sb = new StringBuilder();
bool haveDecimalPoint = false;
while (char.IsDigit(_currentChar) || (!haveDecimalPoint && _currentChar == '.'))
{
sb.Append(_currentChar);
haveDecimalPoint = _currentChar == '.';
NextChar();
}
// Parse it
if (double.TryParse(sb.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out double x))
{
Current = Token.Value(x, Current.Level);
//Token = Token.Number;
return true;
}
}
// Identifier - starts with letter or underscore
if (char.IsLetter(_currentChar) || _currentChar == '_' || _currentChar == '°')
{
var sb = new StringBuilder();
// Accept letter, digit or underscore
while (char.IsLetterOrDigit(_currentChar) || _currentChar == '_' || _currentChar == '°')
{
sb.Append(_currentChar);
NextChar();
}
// Setup token
Current = Token.Identifier(sb.ToString(), Current.Level);
return true;
}
Current = Token.Empty;
return false;
}
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposed)
{
if (disposed)
{
_reader.Dispose();
}
}
}
解析器

这是递归构建表达式树的主类。当调用构造函数时,结果表达式树存储在.Expression属性中,以及.Arguments中所需的任何参数。

/// <summary>
/// Parses a string into an <see cref="Expression"/> tree.
/// </summary>
public class Parser
{
readonly Tokenizer _tokenizer;
readonly Dictionary<string, ParameterExpression> _arguments;
public Parser(string expression) : this(new Tokenizer(expression)) { }
public Parser(Tokenizer tokenizer)
{
this._tokenizer = tokenizer;
this._arguments = new Dictionary<string, ParameterExpression>();
Expression = ParseTokens();
}
public Expression Expression { get; }
public IReadOnlyList<ParameterExpression> Arguments => _arguments.Values.ToList();
public Func<double> CompileNonary()
{
if (Arguments.Count == 0)
{
return Compile<Func<double>>();
}
throw new InvalidOperationException("Expression has too many arguments.");
}
public Func<double, double> CompileUnary()
{
if (Arguments.Count == 1)
{
return Compile<Func<double, double>>();
}
if (Arguments.Count > 1)
{
throw new InvalidOperationException("Expression has too many arguments.");
}
else
{
throw new InvalidOperationException("Expression has too few arguments.");
}
}
public Func<double, double, double> CompileBinary()
{
if (Arguments.Count == 2)
{
return Compile<Func<double, double, double>>();
}
if (Arguments.Count > 2)
{
throw new InvalidOperationException("Expression has too many arguments.");
}
else
{
throw new InvalidOperationException("Expression has too few arguments.");
}
}
TFunc Compile<TFunc>() where TFunc : Delegate
{
ParameterExpression[] arguments = _arguments.Values.ToArray();
return Expression.Lambda<TFunc>(Expression, arguments).Compile() as TFunc;
}
Expression ParseTokens()
{
_tokenizer.Reset();
if (_tokenizer.MoveNext())
{
Expression expr = ParseEquals();
// Check everything was consumed
if (_tokenizer.Current.Type != TokenType.EOF)
{
throw new InvalidOperationException("Unexpected characters at end of expression");
}
return expr;
}
throw new InvalidOperationException("Invalid Expression");
}
Expression ParseEquals()
{
Expression lhs = ParseAddSubtract();
while (true)
{
if (_tokenizer.Current.IsOperator('='))
{
_tokenizer.MoveNext();
Expression rhs = ParseAddSubtract();
lhs = Expression.Equal(lhs, rhs);
}
else
{
return lhs;
}
}
}
Expression ParseAddSubtract()
{
Expression lhs = ParseMulDivide();
while (true)
{
if (_tokenizer.Current.IsOperator('+'))
{
_tokenizer.MoveNext();
Expression rhs = ParseMulDivide();
lhs = Expression.Add(lhs, rhs);
}
else if (_tokenizer.Current.IsOperator('-'))
{
_tokenizer.MoveNext();
Expression rhs = ParseMulDivide();
lhs = Expression.Subtract(lhs, rhs);
}
else
{
return lhs;
}
}
}
Expression ParseMulDivide()
{
Expression lhs = ParsePower();
bool negate = false;
if (lhs is ConstantExpression lex)
{
double x = Convert.ToDouble(lex.Value);
if (x == 0)
{
return Expression.Constant(0.0);
}
else if (x == -1)
{
negate = true;
}
if (_tokenizer.Current.IsIdentifier(out string name))
{
// 60π - or example
Expression rhs = ParseLeaf();
return Expression.Multiply(lhs, rhs);
}
}

while (true)
{
if (_tokenizer.Current.IsOperator('*'))
{
_tokenizer.MoveNext();
Expression rhs = ParsePower();
if (rhs is ConstantExpression rex)
{
double y = Convert.ToDouble(rex.Value);
if (y == 0)
{
lhs = Expression.Constant(0.0);
}
else if (y == 1)
{
// do nothing
}
else if (y == -1)
{
negate = !negate;
}
else
{
lhs = Expression.Multiply(lhs, rhs);
}
}
else
{
lhs = Expression.Multiply(lhs, rhs);
}
}
else if (_tokenizer.Current.IsOperator('/'))
{
_tokenizer.MoveNext();
Expression rhs = ParsePower();
if (rhs is ConstantExpression rex)
{
double y = Convert.ToDouble(rex.Value);
if (y == 0)
{
lhs = Expression.Constant(double.PositiveInfinity);
}
else if (y == 1)
{
// do nothing
}
else if (y == -1)
{
negate = !negate;
}
else
{
lhs = Expression.Divide(lhs, rhs);
}
}
else
{
lhs = Expression.Divide(lhs, rhs);
}
}
else
{
return negate ? Expression.Negate(lhs) : lhs;
}
}
}
Expression ParsePower()
{
Expression lhs = ParseUnary();
while (true)
{
if (_tokenizer.Current.IsOperator('^'))
{
_tokenizer.MoveNext();
Expression rhs = ParseUnary();
if (rhs is ConstantExpression cex)
{
double x = Convert.ToDouble(cex.Value);
if (x == 0)
{
return Expression.Constant(1.0);
}
if (x == 1)
{
return lhs;
}
if (x == -1)
{
return Expression.Divide(Expression.Constant(1.0), lhs);
}
if (x == 2)
{
return Expression.Multiply(lhs, lhs);
}
}
lhs = Expression.Power(lhs, rhs);
}
else
{
return lhs;
}
}
}
Expression ParseUnary()
{
while (true)
{
if (_tokenizer.Current.IsOperator('+'))
{
// ignore unary +
_tokenizer.MoveNext();
continue;
}
if (_tokenizer.Current.IsOperator('-'))
{
_tokenizer.MoveNext();
Expression rhs = ParseUnary();
if (rhs is UnaryExpression uex)
{
if (uex.NodeType == ExpressionType.Negate)
{
return uex.Operand;
}
}
return Expression.Negate(rhs);
}
return ParseLeaf();
}
}
private Expression ParseLeaf()
{
if (_tokenizer.Current.IsNumber(out double x))
{
_tokenizer.MoveNext();
return Expression.Constant(x);
}
if (_tokenizer.Current.IsStartBlock('('))
{
_tokenizer.MoveNext();
Expression node = ParseAddSubtract();
if (!_tokenizer.Current.IsEndBlock(')'))
{
throw new InvalidOperationException("Mismatched Parenthesis.");
}
_tokenizer.MoveNext();
return node;
}
if (_tokenizer.Current.IsIdentifier(out string name))
{
_tokenizer.MoveNext();
if (_tokenizer.Current.IsStartBlock('('))
{
// function call
_tokenizer.MoveNext();
// Parse arguments
List<Expression> arguments = new List<Expression>();
while (true)
{
Expression node = ParseAddSubtract();
arguments.Add(node);
if (_tokenizer.Current.IsDelimiter(','))
{
_tokenizer.MoveNext();
continue;
}
// end of arguments
break;
}
if (!_tokenizer.Current.IsEndBlock(')'))
{
throw new InvalidOperationException("Mismatched Parenthesis.");
}
_tokenizer.MoveNext();

MethodInfo f = typeof(MathFunctions).GetMethod(name, BindingFlags.Static | BindingFlags.Public);
if (f != null)
{
switch (arguments.Count)
{
case 0:
return Expression.Call(f);
case 1:
return Expression.Call(f, arguments[0]);
case 2:
return Expression.Call(f, arguments[0], arguments[1]);
default:
throw new InvalidOperationException($"Too many arguments for function {name}.");
}
}
else
{
throw new InvalidOperationException($"Unknown function {name}.");
}
}
else
{
if (MathFunctions.knownConstants.ContainsKey(name))
{
// named constant
return Expression.Constant(MathFunctions.knownConstants[name]);
}
// variable
if (!_arguments.ContainsKey(name))
{
// add to list of arguments
_arguments.Add(name, Expression.Parameter(typeof(double), name));
}
return _arguments[name];
}
}
if (!_tokenizer.Current.IsEOF())
{
throw new InvalidOperationException($"Unexpected token {_tokenizer.Current}");
}
throw new NotImplementedException();
}
}

MathFunctions

解析器依赖于一个实用函数来为数学函数提供方法体。大多数是围绕Math类的包装,但有些是新的。名称是小写的,以便与解析字符串中调用的任何函数匹配。

public static class MathFunctions
{
internal static readonly Random rng = new Random();
internal static readonly Dictionary<string, double> knownConstants =
new Dictionary<string, double>()
{
["pi"] = Math.PI,
["π"] = Math.PI,
["e"] = Math.E,
["Φ"] = (1 + Math.Sqrt(5)) / 2,
["°"] = Math.PI / 180,
["deg"] = Math.PI / 180,
["rad"] = 180 / Math.PI,
["rpm"] = 2 * Math.PI / 60,
};

public static double rand() => rng.NextDouble();
public static double abs(double x) => Math.Abs(x);
public static double sqr(double x) => x * x;
public static double sqrt(double x) => Math.Sqrt(x);
public static double sign(double x) => (double)Math.Sign(x);
public static double floor(double x) => Math.Floor(x);
public static double round(double x) => Math.Round(x);
public static double exp(double x) => Math.Exp(x);
public static double log(double x) => Math.Log(x);
public static double sin(double x) => Math.Sin(x);
public static double cos(double x) => Math.Cos(x);
public static double tan(double x) => Math.Tan(x);
public static double asin(double x) => Math.Asin(x);
public static double acos(double x) => Math.Acos(x);
public static double atan(double x) => Math.Atan(x);
public static double sinh(double x) => Math.Sinh(x);
public static double cosh(double x) => Math.Cosh(x);
public static double tanh(double x) => Math.Tanh(x);
public static double asinh(double x) => Math.Log(Math.Sqrt(x * x + 1) + x);
public static double acosh(double x) => Math.Log(Math.Sqrt(x * x - 1) + x);
public static double atanh(double x) => -Math.Log((1 - x) / (1 + x)) / 2;

public static double pow(double x, double y) => Math.Pow(x, y);
public static double atan2(double dy, double dx) => Math.Atan2(dy, dx);
public static double min(double x, double y) => Math.Min(x, y);
public static double max(double x, double y) => Math.Max(x, y);
}
最后需要说明的是,解析器还支持一些命名常量
"pi" = Math.PI,
"π" = Math.PI,
"e" = Math.E,
"Φ" = (1 + Math.Sqrt(5)) / 2,
"°" = Math.PI / 180,
"deg" = Math.PI / 180,
"rad" = 180 / Math.PI,
"rpm" = 2 * Math.PI / 60,

支持60°1000rpm等输入。

编译. net表达式类有一个.Compile()方法,可用于将表达式转换为函数。解析器中存在围绕该方法的包装器,使得CompileUnary()CompileBinary()分别产生形式为f(x)f(x,y)的函数,其参数和返回类型为双精度数。 在上面的示例代码中,使用 来演示这一点
var f = parser.CompileBinary();
double x = ...
double y = ...
double z = f(x, y);

最新更新