如何通过编程将字符串转换为数学表达式

7
我是一个C#的初学者。我在将字符串转换为数学表达式时遇到了问题。我有一个用户界面,用户可以使用随机公式字段创建公式。在另一个用户界面中,用户将提供这些公式字段的输入。
例如,第一次使用的公式可能是`(a+b)^n`,另一个公式可能是`((a+b+c)^n+b)`。
在我的计算用户界面中,对于第一个公式,用户将为a、b和n提供输入,对于第二个公式,用户将为a、b、c和n提供输入。请问如何通过程序获取两个公式的结果?谢谢。

什么是“随机公式字段”和“另一个UI”? - Tarec
将其转换为 ExpressionTree - hackp0int
我是说用户可以使用随机变量随意创建公式。@ Tarec - rafat
如何转换为表达式树? - rafat
请尝试访问https://github.com/mparlak/Flee/wiki/Getting-Started。 - Hussain
7个回答

5
以下是正确的操作方法:

应该这样做:

public class StringToFormula
{
    private string[] _operators = { "-", "+", "/", "*","^"};
    private  Func<double, double, double>[] _operations = {
        (a1, a2) => a1 - a2,
        (a1, a2) => a1 + a2,
        (a1, a2) => a1 / a2,
        (a1, a2) => a1 * a2,
        (a1, a2) => Math.Pow(a1, a2)
    };

    public double Eval(string expression)
    {
        List<string> tokens = getTokens(expression);
        Stack<double> operandStack = new Stack<double>();
        Stack<string> operatorStack = new Stack<string>();
        int tokenIndex = 0;

        while (tokenIndex < tokens.Count) {
            string token = tokens[tokenIndex];
            if (token == "(") {
                string subExpr = getSubExpression(tokens, ref tokenIndex);
                operandStack.Push(Eval(subExpr));
                continue;
            }
            if (token == ")") {
                throw new ArgumentException("Mis-matched parentheses in expression");
            }
            //If this is an operator  
            if (Array.IndexOf(_operators, token) >= 0) {
                while (operatorStack.Count > 0 && Array.IndexOf(_operators, token) < Array.IndexOf(_operators, operatorStack.Peek())) {
                    string op = operatorStack.Pop();
                    double arg2 = operandStack.Pop();
                    double arg1 = operandStack.Pop();
                    operandStack.Push(_operations[Array.IndexOf(_operators, op)](arg1, arg2));
                }
                operatorStack.Push(token);
            } else {
                operandStack.Push(double.Parse(token));
            }
            tokenIndex += 1;
        }

        while (operatorStack.Count > 0) {
            string op = operatorStack.Pop();
            double arg2 = operandStack.Pop();
            double arg1 = operandStack.Pop();
            operandStack.Push(_operations[Array.IndexOf(_operators, op)](arg1, arg2));
        }
        return operandStack.Pop();
    }

    private string getSubExpression(List<string> tokens, ref int index)
    {
        StringBuilder subExpr = new StringBuilder();
        int parenlevels = 1;
        index += 1;
        while (index < tokens.Count && parenlevels > 0) {
            string token = tokens[index];
            if (tokens[index] == "(") {
                parenlevels += 1;
            }

            if (tokens[index] == ")") {
                parenlevels -= 1;
            }

            if (parenlevels > 0) {
                subExpr.Append(token);
            }

            index += 1;
        }

        if ((parenlevels > 0)) {
            throw new ArgumentException("Mis-matched parentheses in expression");
        }
        return subExpr.ToString();
    }

    private List<string> getTokens(string expression)
    {
        string operators = "()^*/+-";
        List<string> tokens = new List<string>();
        StringBuilder sb = new StringBuilder();

        foreach (char c in expression.Replace(" ", string.Empty)) {
            if (operators.IndexOf(c) >= 0) {
                if ((sb.Length > 0)) {
                    tokens.Add(sb.ToString());
                    sb.Length = 0;
                }
                tokens.Add(c);
            } else {
                sb.Append(c);
            }
        }

        if ((sb.Length > 0)) {
            tokens.Add(sb.ToString());
        }
        return tokens;
    }
}

像这样调用类和方法:

string formula = "type your formula here"; //or get it from DB
StringToFormula stf = new StringToFormula();
double result = stf.Eval(formula);

很不错的努力!但是它并没有按照预期工作。例如,我尝试了(12/12*(10+10))/2,但它给出的结果是0.025,而不是10。 - Venugopal M
@VenugopalM 我认为错误出现在第31行,while循环正在寻找“token”的IndexOf小于IndexOf operatorStack.Peek()。这将始终将类似“1/2*3”这样的东西评估为2个操作。使用.Pop()(实际上是使用Stacks)意味着我们总是从右到左进行评估。如果您删除while循环的第二个条件,则表达式将被正确评估。另一个选择是将所有内容从Stack<T>更改为List<T>并使用.RemoveAt(0)以便从左到右进行评估。我们想要FIFO而不是LIFO。 - Michael
实际上,你只需要确保+-和*/的优先级相同,然后将<改为<=。这样它仍然会先执行*/,然后再执行+-(从右到左),但如果优先级值相同,它会从左到右进行评估。请参阅我下面发布的答案。 - undefined

0
我认为这是解决方案。
 Expression e = new Expression("((a+b+c)^n+b)");
 e.Evaluate();

1
如果安装了 ncalc Nuget 包,它就能正常工作。 - the smart life

0
将字符串转换为数学表达式
var s3 = "3 - 4 + 5 * 9"

var s4 = NSExpression(format: s3).expressionValue(with: nil, context: nil) as! Double // 44.0

答案:44


0
string input= "(12 + 4 * 6) * ((2 + 3 * ( 4 + 2 ) ) ( 5 + 12 ))";       
    string str4 = "(" + input`enter code here`.Replace(" ", "") + ")";
            str4 = str4.Replace(")(", ")*(");
            while (str4.Contains('('))
            {
                string sub1 = str4.Substring(str4.LastIndexOf("(") + 1);
                string sub = sub1.Substring(0, sub1.IndexOf(")"));
                string sub2 = sub;
                string str21 = sub2.Replace("^", "~^~").Replace("/", "~/~").Replace("*", "~*~").Replace("+", "~+~").Replace("-", "~-~");
                List<string> str31 = str21.Split('~').ToList();
                while (str31.Count > 1)
                {
                    while (str31.Contains("*"))
                    {
                        for (int i = 0; i < str31.Count; i++)
                        {
                            if (str31[i] == "*")
                            {
                                val = Convert.ToDouble(str31[i - 1]) * Convert.ToDouble(str31[i + 1]);
                                str31.RemoveRange(i - 1, 3);
                                str31.Insert(i - 1, val.ToString());
                            }
                        }
                    }
                    while (str31.Contains("/"))
                    {
                        for (int i = 0; i < str31.Count; i++)
                        {
                            if (str31[i] == "/")
                            {
                                val = Convert.ToDouble(str31[i - 1]) / Convert.ToDouble(str31[i + 1]);
                                str31.RemoveRange(i - 1, 3);
                                str31.Insert(i - 1, val.ToString());
                            }
                        }
                    }
                    while (str31.Contains("+"))
                    {
                        for (int i = 0; i < str31.Count; i++)
                        {
                            if (str31[i] == "+")
                            {
                                val = Convert.ToDouble(str31[i - 1]) + Convert.ToDouble(str31[i + 1]);
                                str31.RemoveRange(i - 1, 3);
                                str31.Insert(i - 1, val.ToString());
                            }
                        }
                    }
                    while (str31.Contains("-"))
                    {
                        for (int i = 0; i < str31.Count; i++)
                        {
                            if (str31[i] == "-")
                            {
                                val = Convert.ToDouble(str31[i - 1]) - Convert.ToDouble(str31[i + 1]);
                                str31.RemoveRange(i - 1, 3);
                                str31.Insert(i - 1, val.ToString());
                            }
                        }
                    }
                }
                str4 = str4.Replace("(" + sub + ")", str31[0].ToString());
            }

            string sum = str4;

这是一个简单的解析器。你可以进一步增强它。 以下是一些相关链接... http://www.dreamincode.net/forums/topic/35320-reverse-polish-notation-in-c%23/ http://mathosparser.codeplex.com/ http://www.codeproject.com/Articles/274093/Math-Parser-NET http://www.codeproject.com/Articles/88435/Simple-Guide-to-Mathematical-Expression-Parsing http://www.mathparsers.com/math-parser-for-csharp-dotnet/ - Amit Tiwari
它可以处理包含加、减、乘、除的表达式,如果需要,您可以进行增强... - Amit Tiwari

0
我能想到最结构化的方法是定义一个由操作符(在你的情况下显然是+,-,*,/和^)和运算元组成的语法; 如果输入在定义的语法中存在推导,那么推导基本上就是表达式树,然后可以在递归遍历表达式树时将操作符直接转换为实际操作。 我承认这个描述有点模糊,但好的解析可能有点困难。也许看一下LL解析器可以有所帮助。

0

有很多公式求解方法,请看一下。只需将用户提供的值替换字符串中的abn字符,然后使用提到的方法之一解析方程。


尝试使用Flee,例如,提供了一个简单的示例:http://flee.codeplex.com/wikipage?title=GettingStarted&referringTitle=Examples - Tarec

0
老帖子了,但我喜欢原帖中的解决方案,并且我想发表一个修正版本。它使用了上面发布的源代码,但现在正确地计算了运算符优先级和负数。例如:10 + 12*10 - -47 % 30.0 = 147 或者 -2 + -4 * (-2 - -2*10) = -74
public static class StringToFormula
{
    private static readonly string[] operators = { "+", "-", "/", "%", "*", "^" };
    private static readonly Func<double, double, double>[] operations = {
        (a1, a2) => a1 + a2,
        (a1, a2) => a1 - a2,
        (a1, a2) => a1 / a2,
        (a1, a2) => a1 % a2,
        (a1, a2) => a1 * a2,
        (a1, a2) => Math.Pow(a1, a2)
    };

    public static bool TryEval(string expression, out double value)
    {
        try
        {
            value = Eval(expression);
            return true;
        }
        catch
        {
            value = 0.0;
            return false;
        }
    }

    public static double Eval(string expression)
    {
        if (string.IsNullOrEmpty(expression))
            return 0.0;

        if (double.TryParse(expression, NumberStyles.Any, CultureInfo.InvariantCulture, out double value))
            return value;

        List<string> tokens = GetTokens(expression);
       tokens.Add("$"); // Append end of expression token
        Stack<double> operandStack = new Stack<double>();
        Stack<string> operatorStack = new Stack<string>();
        int tokenIndex = 0;

        while (tokenIndex < tokens.Count - 1)
        {
            string token = tokens[tokenIndex];
            string nextToken = tokens[tokenIndex + 1];
            
            switch (token)
            {
                case "(":
                {
                    string subExpr = GetSubExpression(tokens, ref tokenIndex);
                    operandStack.Push(Eval(subExpr));
                    continue;
                }
                case ")":
                    throw new ArgumentException("Mis-matched parentheses in expression");
                
                // Handle unary ops
                case "-":
                case "+":
                {
                    if (!IsOperator(nextToken) && operatorStack.Count == operandStack.Count)
                    {
                        operandStack.Push(double.Parse($"{token}{nextToken}", CultureInfo.InvariantCulture));
                        tokenIndex += 2;
                        continue;
                    }
                }
                break;
            }
            
            if (IsOperator(token))
            {
                while (operatorStack.Count > 0 && OperatorPrecedence(token) <= OperatorPrecedence(operatorStack.Peek()))
                {
                    if (!ResolveOperation()) {
                        throw new ArgumentException(BuildOpError());
                    }
                }
                operatorStack.Push(token);
            }
            else
            {
                operandStack.Push(double.Parse(token, CultureInfo.InvariantCulture));
            }
            tokenIndex += 1;
        }

        while (operatorStack.Count > 0)
        {
            if (!ResolveOperation())
                throw new ArgumentException(BuildOpError());
        }

        return operandStack.Pop();

        bool IsOperator(string token)
        {
            return Array.IndexOf(operators, token) >= 0;
        }
        int OperatorPrecedence(string op)
        {
            switch (op)
            {
            case "^":
                return 3;
            case "*":
            case "/":
            case "%":
                return 2;
                
            case "+":
            case "-":
                return 1;
            default:
                return 0;
            }
        }
        
        string BuildOpError() {
            string op = operatorStack.Pop();
            string rhs = operandStack.Any() ? operandStack.Pop().ToString() : "null";
            string lhs = operandStack.Any() ? operandStack.Pop().ToString() : "null";
            return $"Operation not supported: {lhs} {op} {rhs}";
        }
       
        bool ResolveOperation()
        {
            if (operandStack.Count < 2)
            {
                return false;
            }
            
            string op = operatorStack.Pop();
            double rhs = operandStack.Pop();
            double lhs = operandStack.Pop();
            operandStack.Push(operations[Array.IndexOf(operators, op)](lhs, rhs));
            Console.WriteLine($"Resolve {lhs} {op} {rhs} = {operandStack.Peek()}");
            return true;
        }
    }

    private static string GetSubExpression(List<string> tokens, ref int index)
    {
        StringBuilder subExpr = new StringBuilder();
        int parenlevels = 1;
        index += 1;
        while (index < tokens.Count && parenlevels > 0)
        {
            string token = tokens[index];
            switch (token) {
                case "(": parenlevels += 1; break;
                case ")": parenlevels -= 1; break;
            }
            
            if (parenlevels > 0)
                subExpr.Append(token);
            
            index += 1;
        }

        if (parenlevels > 0)
            throw new ArgumentException("Mis-matched parentheses in expression");
        
        return subExpr.ToString();
    }

    private static List<string> GetTokens(string expression)
    {
        string operators = "()^*/%+-";
        List<string> tokens = new List<string>();
        StringBuilder sb = new StringBuilder();

        foreach (char c in expression.Replace(" ", string.Empty))
        {
            if (operators.IndexOf(c) >= 0)
            {
                if ((sb.Length > 0))
                {
                    tokens.Add(sb.ToString());
                    sb.Length = 0;
                }
                tokens.Add(c.ToString());
            }
            else
            {
                sb.Append(c);
            }
        }

        if ((sb.Length > 0))
        {
            tokens.Add(sb.ToString());
        }
        return tokens;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接