总结
缺点
这段代码要求表达式非常符合规范,例如10*(····) 不能写为10(····)。
注意到这个表达式能通过勾选,但是10并没有参与计算,因为10与(之间并没有数学符号,在构建表达式的那段代码里自然不能让它参与计算。加上乘法符号'*'后能得出正确结果。这其实是一个bug,可以在字符串哪里让它不能通过或者加个*之后在让它通过。
我认为这段代码移植是能移植的,就是比较麻烦,并且能用到的地方并不多,感觉非常鸡肋。
总的来说是能用的,虽然鸡肋,也许能启发一些你自己的思路从而写出更简洁精炼的代码。
例子
我是在WPF的Textblock控件中输入。要用到System.Linq.Expressions
命名空间。没有第三方库。我的方法只能处理(可带嵌套的圆括号)加减乘除。
下面是运行后的例子。当输入框里的例子输入完之后,要点击“检查公式并勾选”按钮,如果检查通过会勾选(用户只能取消勾选),否则无法勾选并不能参与后续处理。
字符串处理
下面的代码处理字符串并判断它是否能构建完整的计算式,Check方法的返回值为上述流程中的勾选的属性赋值。
public bool Check(string expr)
{
// 示例计算式字符串
string expression = expr;
try
{
// 解析并验证计算式
string[] parsedExpression = ParseAndValidateExpression(expression);
BuildExp(parsedExpression.ToList());
// 输出结果
MessageBox.Show(string.Join(" ", parsedExpression));
return true;
}
catch (Exception ex)
{
MessageBox.Show($"错误: {ex.Message}\n公式无法参与计算");
return false;
}
}
private string[] ParseAndValidateExpression(string expression)
{
List<string> matches = new();
string str = "";
int i = 0;
double num = 0;
foreach (char c in expression)
{
if (FormulaParamterModel.MathSymbol.Contains(c.ToString()))
{
if (str != "")
{
matches.Add(str);
str = "";
}
matches.Add((c.ToString()));
}
else if (FormulaParamterModel.KuoHao.Contains(c.ToString()))
{
if (str != "")
{
matches.Add(str);
str = "";
}
matches.Add((c.ToString()));
}
else
{
str += c;
}
if (i == expression.Length - 1)
{
if (str != "")
{
matches.Add(str);
str = "";
}
}
i++;
}
// 提取匹配的字符串
List<string> parsedTokens = new List<string>();
foreach (var match in matches)
{
parsedTokens.Add(match);
}
// 验证提取的字符串
foreach (string token in parsedTokens)
{
if (!SqliteModel.ColumnName.Contains(token) && !FormulaParamterModel.MathSymbol.Contains(token) && !FormulaParamterModel.KuoHao.Contains(token))
{
if (token[0] != '(')
{
if (double.TryParse(token, out num))
{
}
else
{
throw new ArgumentException($"无效的字符或变量名: {token}");
}
}
else
{
throw new ArgumentException($"包含中文括号: {token[0]}");
}
}
}
// 验证计算式的完整性
if (!IsValidExpression(parsedTokens))
{
throw new ArgumentException("计算式不完整或无效\n例如,连续的操作符或变量\n括号不匹配");
}
return parsedTokens.ToArray();
}
/// <summary>
/// 验证计算式有无问题
/// </summary>
/// <param name="tokens"></param>
/// <returns></returns>
private bool IsValidExpression(List<string> tokens)
{
// 简单的计算式完整性验证
int openParentheses = 0;
for (int i = 0; i < tokens.Count; i++)
{
string token = tokens[i];
if (token == "(")
{
openParentheses++;
}
else if (token == ")")
{
openParentheses--;
if (openParentheses < 0)
{
return false; // 多余的右括号
}
}
if (i > 0)
{
string prevToken = tokens[i - 1];
if (!IsKuoHao(prevToken) && !IsKuoHao(token))
{
if ((IsOperator(token) && IsOperator(prevToken)) || (IsVariable(token) && IsVariable(prevToken)))
{
return false; // 连续的操作符或变量
}
}
else
{
if ((IsOperator(token) && IsOperator(prevToken)) || (IsVariable(token) && IsVariable(prevToken)) || ((token==")") && (prevToken=="(")))
{
return false;
}
}
}
}
if (openParentheses != 0)
{
return false; // 括号不匹配
}
// 确保计算式以变量或右括号结束
if (IsOperator(tokens.Last()))
{
return false; // 结束于操作符
}
return true;
}
private bool IsOperator(string token)
{
return FormulaParamterModel.MathSymbol.Contains(token);
}
private bool IsVariable(string token)
{
return SqliteModel.ColumnName.Contains(token);
}
private bool IsKuoHao(string token)
{
return FormulaParamterModel.KuoHao.Contains(token);
}
这是上面判断是否包含的泛型集合的实例。
public static List<string> MathSymbol { get; }=new() { "+","-","*","/"};
public static List<string> KuoHao { get; } = new() {"(",")" };
public static readonly List<string> ColumnName = new() { "原料编号","原料类别", "原料名称" , "干基单价", "TFe", ·······"烧结成本","铁水成本","吨度价"};
构建表达式
下面的思路有点乱。大致思路是先处理嵌套在最里面的括号里的表达式,处理了这个括号后,前面存储表达式的数学符号,变量及括号的顺序都变了,所以用while循环来判断处理是否有括号;当括号处理完了,继续用while循环处理乘除法,最后只剩一次就处理完成的加减法。
private void BuildExp(List<string> tokens)
{
double num = 0;
Exp.ParameterExpression[] paraMidExp = new Exp.ParameterExpression[60];
Exp.ParameterExpression[] paraExp = new Exp.ParameterExpression[60];
Exp.BinaryExpression[] bindExp = new Exp.BinaryExpression[40];
Exp.ConstantExpression[] constExp = new Exp.ConstantExpression[60];
List<Exp.ParameterExpression> ParaExpList = new();
values.Clear();
for (int i = 0; i < tokens.Count; i++)
{
if (IsVariable(tokens[i]))
{
paraExp[i] = Exp.Expression.Parameter(typeof(double?), $"x{i + 1}");
values.Add(tokens[i]);
}
else if (double.TryParse(tokens[i], out num))
{
constExp[i] = Exp.Expression.Constant(num, typeof(double?));
}
}
M = 0;
paraMidExp = paraExp.ToArray();
while (IsLeft(tokens))
{
tokens = DealKuoHao(tokens, paraExp, bindExp, constExp);
}
while (tokens.Contains("*") || tokens.Contains("/"))
{
tokens = DealNoKuoHao(tokens, paraExp, bindExp, constExp);
}
DealNoKuoHao(tokens, paraExp, bindExp, constExp);
Exp.BinaryExpression Finally = bindExp[M - 1];
foreach (var paraexp in paraMidExp)
{
if (paraexp != null)
{
ParaExpList.Add(paraexp);
}
}
Type funcType = CreateFuncType(ParaExpList.Count, typeof(double?));
function = Exp.Expression.Lambda(funcType, Finally, ParaExpList);
}
private List<string> DealKuoHao(List<string> tokens, Exp.ParameterExpression[] paraExp, Exp.BinaryExpression[] bindExp, Exp.ConstantExpression[] constExp)
{
List<int> left = new();
List<int> right = new();
List<string> keyList = tokens.ToList();
for (int i = 0; i < tokens.Count; i++)
{
if (tokens[i] == "(")
{
left.Add(i);
}
else if (tokens[i] == ")")
{
right.Add(i);
}
}
left.Add(right[right.Count - 1] + 1);
for (int i = 0; i < left.Count - 1; i++)
{
for (int j = 0; j < right.Count; j++)
{
if (right[j] > left[i] && right[j] < left[i + 1])//一对匹配的括号,构建表达式并将其移除出list
{
for (int k = left[i]; k < right[j]; k++)//先将乘除法处理
{
if (keyList[k] == "*" || keyList[k] == "/")
{
MathMethod(k, keyList, paraExp, bindExp, constExp);
}
}
for (int k = left[i]; k < right[j]; k++)//再将加减法处理
{
if (keyList[k] == "+" || keyList[k] == "-")
{
MathMethod(k, keyList, paraExp, bindExp, constExp);
}
}
for (int k = right[j]; k > left[i]; k--)
{
keyList.RemoveAt(k);
}
keyList[left[i]] = $"b{M - 1}";
for (int s = right[j]; s < paraExp.Length; s++)
{
if (s + 1 < paraExp.Length)
{
paraExp[(s + 1 - (right[j] - left[i]))] = paraExp[s + 1];
constExp[(s + 1 - (right[j] - left[i]))] = constExp[s + 1];
}
else
{
paraExp[(s + 1 - (right[j] - left[i]))] = null;
constExp[(s + 1 - (right[j] - left[i]))] = null;
}
}
return keyList;//一次消掉一对括号
}
}
}
return tokens;
}
private List<string> DealNoKuoHao(List<string> tokens, Exp.ParameterExpression[] paraExp, Exp.BinaryExpression[] bindExp, Exp.ConstantExpression[] constExp)
{
List<string> keyList = tokens.ToList();
int k = 0;
for (int i = 0; i < keyList.Count; i++)
{
if (keyList[i] == "*" || keyList[i] == "/")
{
k = i;
break;
}
}
if (keyList.Contains("*") || keyList.Contains("/"))
{
for (int i = k; i < keyList.Count; i++)
{
if (keyList[i] == "*" || keyList[i] == "/")
{
MathMethod(i, keyList, paraExp, bindExp, constExp);
}
}
keyList.RemoveAt(k + 1);
keyList.RemoveAt(k);
keyList[k - 1] = $"b{M - 1}";
for (int s = k; s < paraExp.Length; s++)
{
if (s + 2 < paraExp.Length)
{
paraExp[s] = paraExp[s + 2];
constExp[s] = constExp[s + 2];
}
else
{
paraExp[s] = null;
constExp[s] = null;
}
}
return keyList;//一次消掉乘除符号及周围的两个数
}
else if (keyList.Contains("+") || keyList.Contains("-"))
{
for (int i = 0; i < keyList.Count; i++)
{
if (keyList[i] == "+" || keyList[i] == "-")
{
MathMethod(i, keyList, paraExp, bindExp, constExp);
}
}
}
return tokens;
}
private void MathMethod(int k, List<string> keyList, Exp.ParameterExpression[] paraExp, Exp.BinaryExpression[] bindExp, Exp.ConstantExpression[] constExp)
{
if (keyList[k] == "*")
{
if (IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
bindExp[M] = Exp.Expression.Multiply(paraExp[k - 1], paraExp[k + 1]);
}
else if (!IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1))
{
bindExp[M] = Exp.Expression.Multiply(bindExp[IsBind(keyList[k - 1])], paraExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Multiply(constExp[k - 1], paraExp[k + 1]);
}
}
else if (IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Multiply(paraExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Multiply(paraExp[k - 1], constExp[k + 1]);
}
}
else if (!IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Multiply(bindExp[IsBind(keyList[k - 1])], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Multiply(bindExp[IsBind(keyList[k - 1])], constExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Multiply(constExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Multiply(constExp[k - 1], constExp[k + 1]);
}
}
keyList[k + 1] = $"b{M}";
keyList[k - 1] = $"b{M}";
M++;
}
else if (keyList[k] == "/")
{
if (IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
bindExp[M] = Exp.Expression.Divide(paraExp[k - 1], paraExp[k + 1]);
}
else if (!IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1))
{
bindExp[M] = Exp.Expression.Divide(bindExp[IsBind(keyList[k - 1])], paraExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Divide(constExp[k - 1], paraExp[k + 1]);
}
}
else if (IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Divide(paraExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Divide(paraExp[k - 1], constExp[k + 1]);
}
}
else if (!IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Divide(bindExp[IsBind(keyList[k - 1])], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Divide(bindExp[IsBind(keyList[k - 1])], constExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Divide(constExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Divide(constExp[k - 1], constExp[k + 1]);
}
}
keyList[k + 1] = $"b{M}";
keyList[k - 1] = $"b{M}";
M++;
}
else if (keyList[k] == "+")
{
if (IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
bindExp[M] = Exp.Expression.Add(paraExp[k - 1], paraExp[k + 1]);
}
else if (!IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1))
{
bindExp[M] = Exp.Expression.Add(bindExp[IsBind(keyList[k - 1])], paraExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Add(constExp[k - 1], paraExp[k + 1]);
}
}
else if (IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Add(paraExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Add(paraExp[k - 1], constExp[k + 1]);
}
}
else if (!IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Add(bindExp[IsBind(keyList[k - 1])], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Add(bindExp[IsBind(keyList[k - 1])], constExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Add(constExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Add(constExp[k - 1], constExp[k + 1]);
}
}
keyList[k + 1] = $"b{M}";
keyList[k - 1] = $"b{M}";
M++;
}
else if (keyList[k] == "-")
{
if (IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
bindExp[M] = Exp.Expression.Subtract(paraExp[k - 1], paraExp[k + 1]);
}
else if (!IsVariable(keyList[k - 1]) && IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1))
{
bindExp[M] = Exp.Expression.Subtract(bindExp[IsBind(keyList[k - 1])], paraExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Subtract(constExp[k - 1], paraExp[k + 1]);
}
}
else if (IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Subtract(paraExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else
{
bindExp[M] = Exp.Expression.Subtract(paraExp[k - 1], constExp[k + 1]);
}
}
else if (!IsVariable(keyList[k - 1]) && !IsVariable(keyList[k + 1]))
{
if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Subtract(bindExp[IsBind(keyList[k - 1])], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) != (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Subtract(bindExp[IsBind(keyList[k - 1])], constExp[k + 1]);
DealChange(keyList, IsBind(keyList[k - 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) != (-1))
{
bindExp[M] = Exp.Expression.Subtract(constExp[k - 1], bindExp[IsBind(keyList[k + 1])]);
DealChange(keyList, IsBind(keyList[k + 1]), M);
}
else if (IsBind(keyList[k - 1]) == (-1) && IsBind(keyList[k + 1]) == (-1))
{
bindExp[M] = Exp.Expression.Subtract(constExp[k - 1], constExp[k + 1]);
}
}
keyList[k + 1] = $"b{M}";
keyList[k - 1] = $"b{M}";
M++;
}
}
private int IsBind(string key)
{
char[] c = key.ToArray();
string num = "";
int a;
if (c[0] != 'b')
{
return -1;
}
for (int i = 1; i < key.Length; i++)
{
num += c[i];
}
if (int.TryParse(num, out a))
{
return a;
}
else
{
return -1;
}
}
private void DealChange(List<string> str, int old, int New)
{
for (int i = 0; i < str.Count; i++)
{
if (IsBind(str[i]) == old)
{
str[i] = $"b{New}";
}
}
}
private bool IsLeft(List<string> tokens)
{
for (int i = 0; i < tokens.Count; i++)
{
if (tokens[i] == "(")
{
return true;
}
}
return false;
}
表达式运算
在这里常量无限制,变量最多16个是方法重载里最大是16,如果自己写方法重载应该能超过16个。
Type CreateFuncType(int parameterCount, Type returnType)
{
// 获取 Func 泛型类型的定义
string funcTypeName = $"System.Func`{parameterCount + 1}";
Type funcType = Type.GetType(funcTypeName);
if (funcType == null)
{
throw new NotSupportedException("Func with more than 16 parameters is not supported.");
}
// 创建包含多个参数的 Func 泛型类型
Type[] genericArguments = new Type[parameterCount + 1];
for (int i = 0; i < parameterCount; i++)
{
genericArguments[i] = typeof(double?);
}
genericArguments[parameterCount] = returnType;
return funcType.MakeGenericType(genericArguments);
}
private object GetPropertyValueByQueryKey(object obj, string queryKey)
{
var type = obj.GetType();
var property = type.GetProperties()
.FirstOrDefault(p => p.GetCustomAttribute<QueryByAttribute>()?.QueryKey == queryKey);
if (property != null && property.CanRead)
{
return property.GetValue(obj);
}
return null;
}
private void KeyStringTranObject(Iron i, object?[] args)
{
for (int j = 0; j < values.Count; j++)
{
string queryKey = values[j];
var value = GetPropertyValueByQueryKey(i, queryKey);
args[j] = (double?)value;
}
}
特性
这是关于特性的一些代码,它与GetPropertyValueByQueryKey方法有关。
输出结果
这里的i.UserSet1就是前面例子中表格的“自定义1”列。