7

I am a beginner at C#. I am facing a problem while converting a string to a mathematical expression. I have a UI where user can create formula using random formula field. And in another UI user will give input of those formula field.

like for example at first time the formula may be (a+b)^n and another the formula may be ((a+b+c)^n+b).

In my calculation UI for the first time user will give input for a,b,n and for 2nd formula user will give input for a,b,c,n. Can anyone please help me about how to get the result for both of the formula programmatic-ally? Thanks in advance

5
  • What is random formula field and another UI? Commented Feb 13, 2014 at 10:04
  • Convert it to ExpressionTree Commented Feb 13, 2014 at 10:05
  • i meant user can create a formula randomly using random variable. @ Tarec Commented Feb 13, 2014 at 10:06
  • how to convert to expression tree? Commented Feb 13, 2014 at 10:07
  • try github.com/mparlak/Flee/wiki/Getting-Started Commented Nov 19, 2018 at 14:30

7 Answers 7

5

This is how it should be done:

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;
    }
}

Call the class and method like this:

string formula = "type your formula here"; //or get it from DB
StringToFormula stf = new StringToFormula();
double result = stf.Eval(formula);
Sign up to request clarification or add additional context in comments.

3 Comments

Good effort! But it is not working as desired. For Eg. I tried (12 / 12 * (10 + 10)) / 2 and it is giving 0.025 instead of 10
@VenugopalM I think the error comes from Line 31 where the while loop is looking for IndexOf the 'token' to be less than the IndexOf operatorStack.Peek(). This will ALWAYS evaluate something like '1/2*3' as 2 operations. The use of .Pop() (actually the use of Stacks in general) means that we're always evaluating from right-to-left. If you remove this second condition of the while loop, then the expression is evaluated correctly. The other option would be to change everything from Stack<T> to List<T> and using .RemoveAt(0) so it's evaluated left-to-right. We want FIFO not LIFO.
Actually, all you have to do is make sure the precedence is the same for +- and */, then change the < to <=. That way it still does */ before doing +- (right to left), but if the precedence value is the same it evaluates left to right. See my answer posted below
1

Old post, but I liked the solution source posted and thought I'd post a fixed version. It uses the source posted above, but now correctly calculates the result with operator precedence and negative numbers. Eg. 10 + 12*10 - -47 % 30.0 = 147 or -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;
    }
}

22-10-2024 update:

This should probably be updated to use spans, so it doesn't make (string) copies of parts of the expression

Comments

0

There are plenty methods for formula evaluation, take a look. Just take your input, replace a, b, n chars in your string to values provided by user and resolve equation with one of methods mentioned.

1 Comment

Try flee for example, simple sample provided: flee.codeplex.com/…
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;

2 Comments

it will work for expression containing add, subtract, multiplication and division and for more you can enhance it...
0

The most structural approach which comes to my mind would be to define a grammar consisting out of operator symbols (which are apparently +,-,*,/ and ^ in your case) and operands; then, if a derivation of the input in the defined grammar exists, the derivation basically is the expression tree which can then be traversed recursively while the operators are directry translated to the actual operations. I admit that the description is a bit vague, but good parsing can be a bit difficult. Perhaps a look at LL parser can help a bit.

Comments

0

i think this is the solution

 Expression e = new Expression("((a+b+c)^n+b)");
 e.Evaluate();

1 Comment

Yes it works If ncalc Nuget package is installed
0

convert String To Mathematical Expression

var s3 = "3 - 4 + 5 * 9"

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

Answer : 44

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.