How to read string, as math expression and solve in unity?

Hi,
All i need is for my script to be able to read a string as a mathematical expression, and solve it. It doesn’t need to understand any complicated math (variables or formulas). Just pure basic algebra. Unity’s version only works in the editor but doesn’t work when i build. I’ve tried importing NCalc and Expressive-master, but i can’t get them to work (never tried to import custom C#, .NET libraries with unity before). Any help/tips would be appreciated.

It rather depends how complex your expressions are that Need to be parsed (if you are using parantheses, the Parser must be recursive). This may get you started.

 void Calculate()
    {
        foreach (char item in exp)
        {
            if (item == '+' || item == '-' || item == '/' || item == '*')
            {
                Oprator += 1;
                Opratorarray.Add(item.ToString());
            }
            else
            {
                if (Oprand == Oprator)
                {
                    Oprand += 1;
                    Oprandarray.Add(item.ToString());
                }
                else
                {
                    Oprandarray[Oprator] = Oprandarray[Oprator] + item;
                }
            }
        }

        if (Oprand <= Oprator)
        {
            Debug.LogError("Expression is not valid");
            return;
        }
        ans = float.Parse(Oprandarray[0]);

        foreach (string item in Opratorarray)
        {
            switch (item)
            {
                case "+":
                    ans += float.Parse(Oprandarray[count]);
                    break;
                case "-":
                    ans += float.Parse(Oprandarray[count]);
                    break;
                case "/":
                    ans /= float.Parse(Oprandarray[count]);
                    break;
                case "*":
                    ans *= float.Parse(Oprandarray[count]);
                    break;
                default:
                    Debug.LogError("Expression is not valid");
                    break;
            }
            count += 1;
        }

        Debug.Log("Answer is : "+ans);
    }

Hey, that’s a nice quick and dirty hack (love it), but I don’t think it’ll correctly evaluate

3 + 4 * 5

and it’ll barf on

3 + 4 * -5

Yes But He said “Just pure basic algebra” ;):wink:

1 Like

Indeed :slight_smile:

… but according to Ms Wormwood, a third grade teacher, 3 + 4 * 5 is basic algebra.

3+45 will work but 3+4-5 won’t

need to do some more work for that

There is one more problem that

5 + 3 * 2 = 11
but this logic will return 16
need to work on that as well

Hmm. Looking at your code (which - again - I believe is really clever) I think it will evaluate

3+4*5

to 35 which is wrong (it should be 23)

The problem is that your code currently does not regard precedence of Operation, and unfortunately I see no simple fix for that. The code also won’t correctly handle

-3+4

but I beleive you can fix that by always treating - as unary operator, and insert a ‘+’ if no other operator follows

I used this in the past and it worked great

https://archive.codeplex.com/?p=ncalc

I believe the OP mentioned they did not have much success with NCalc

This is the evaluator I wrote for use in our code base:

I attempt to keep the gc to a minimum when evaluating the strings by keeping generation of substrings to a minimum. And I wanted abilities for some built in functions as well as calling functions/properties on an object passed in… this accessing an object dynamically has a dependency on DynamicUtil which you would likely want to strip out.

Basically I’m saying this is not plug and play… but could give you an idea of how to pull it off by ripping out the bits you want.

Sorry I just woke up when I wrote that. Makes me curious why it didn’t work for him, I managed to import it into Unity just fine, you just need to target the assembly as .NET 3.5.

Look into Abstract Syntax Trees. I think there’s a bunch of guides online for how to build one from parsing algebra in strings.

There are libraries by people which are presumably more robust than something you code yourself.

Here an example I just made using NCalc. Download the zip and put the .dll in a folder called Plugins. The code looks something like this

using UnityEngine;
using NCalc;

public class Calculator : MonoBehaviour
{
    public void Start()
    {
        object answer = GetAnswer("10*20");
        Debug.Log(answer.ToString());
    }

    public object GetAnswer(string expression)
    {
        Expression ex = new Expression(expression);
        return ex.Evaluate();
    }
}


4881788–471263–NCalc.zip (61.4 KB)

I will drop it here, for anyone one interested, digging in a bit more into topic.
__ Ordering math equation from pseudo code. __

1 Like

If you want to build your own code based on a well-known and simple algorithm, here is a start: Shunting yard algorithm - Wikipedia

Also this thread may be useful for you: Ordering math equation from pseudo code.

1 Like

N.B. “Algebra” means it does involve variables or formulas. I think you meant basic arithmetic.

Wikipedia: “Elementary algebra differs from arithmetic in the use of abstractions, such as using letters to stand for numbers that are either unknown or allowed to take on many values.”

1 Like

retrived from: https://wiki.unity3d.com/index.php/ExpressionParser

/* * * * * * * * * * * * * *
* A simple expression parser
* --------------------------
*
* The parser can parse a mathematical expression into a simple custom
* expression tree. It can recognise methods and fields/contants which
* are user extensible. It can also contain expression parameters which
* are registrated automatically. An expression tree can be "converted"
* into a delegate.
*
* Written by Bunny83
* 2014-11-02
*
* Features:
* - Elementary arithmetic [ + - * / ]
* - Power [ ^ ]
* - Brackets ( )
* - Most function from System.Math (abs, sin, round, floor, min, ...)
* - Constants ( e, PI )
* - MultiValue return (quite slow, produce extra garbage each call)
*
* * * * * * * * * * * * * */
using System.Linq;
using System.Collections.Generic;
namespace B83.ExpressionParser
{
    public interface IValue
    {
        double Value { get; }
    }
    public class Number : IValue
    {
        private double m_Value;
        public double Value
        {
            get { return m_Value; }
            set { m_Value = value; }
        }
        public Number(double aValue)
        {
            m_Value = aValue;
        }
        public override string ToString()
        {
            return "" + m_Value + "";
        }
    }
    public class OperationSum : IValue
    {
        private IValue[] m_Values;
        public double Value
        {
            get { return m_Values.Select(v => v.Value).Sum(); }
        }
        public OperationSum(params IValue[] aValues)
        {
            // collapse unnecessary nested sum operations.
            List<IValue> v = new List<IValue>(aValues.Length);
            foreach (var I in aValues)
            {
                var sum = I as OperationSum;
                if (sum == null)
                    v.Add(I);
                else
                    v.AddRange(sum.m_Values);
            }
            m_Values = v.ToArray();
        }
        public override string ToString()
        {
            return "( " + string.Join(" + ", m_Values.Select(v => v.ToString()).ToArray()) + " )";
        }
    }
    public class OperationProduct : IValue
    {
        private IValue[] m_Values;
        public double Value
        {
            get { return m_Values.Select(v => v.Value).Aggregate((v1, v2) => v1 * v2); }
        }
        public OperationProduct(params IValue[] aValues)
        {
            m_Values = aValues;
        }
        public override string ToString()
        {
            return "( " + string.Join(" * ", m_Values.Select(v => v.ToString()).ToArray()) + " )";
        }
    }
    public class OperationPower : IValue
    {
        private IValue m_Value;
        private IValue m_Power;
        public double Value
        {
            get { return System.Math.Pow(m_Value.Value, m_Power.Value); }
        }
        public OperationPower(IValue aValue, IValue aPower)
        {
            m_Value = aValue;
            m_Power = aPower;
        }
        public override string ToString()
        {
            return "( " + m_Value + "^" + m_Power + " )";
        }
    }
    public class OperationNegate : IValue
    {
        private IValue m_Value;
        public double Value
        {
            get { return -m_Value.Value; }
        }
        public OperationNegate(IValue aValue)
        {
            m_Value = aValue;
        }
        public override string ToString()
        {
            return "( -" + m_Value + " )";
        }
    }
    public class OperationReciprocal : IValue
    {
        private IValue m_Value;
        public double Value
        {
            get { return 1.0 / m_Value.Value; }
        }
        public OperationReciprocal(IValue aValue)
        {
            m_Value = aValue;
        }
        public override string ToString()
        {
            return "( 1/" + m_Value + " )";
        }
    }
    public class MultiParameterList : IValue
    {
        private IValue[] m_Values;
        public IValue[] Parameters { get { return m_Values; } }
        public double Value
        {
            get { return m_Values.Select(v => v.Value).FirstOrDefault(); }
        }
        public MultiParameterList(params IValue[] aValues)
        {
            m_Values = aValues;
        }
        public override string ToString()
        {
            return string.Join(", ", m_Values.Select(v => v.ToString()).ToArray());
        }
    }
    public class CustomFunction : IValue
    {
        private IValue[] m_Params;
        private System.Func<double[], double> m_Delegate;
        private string m_Name;
        public double Value
        {
            get
            {
                if (m_Params == null)
                    return m_Delegate(null);
                return m_Delegate(m_Params.Select(p => p.Value).ToArray());
            }
        }
        public CustomFunction(string aName, System.Func<double[], double> aDelegate, params IValue[] aValues)
        {
            m_Delegate = aDelegate;
            m_Params = aValues;
            m_Name = aName;
        }
        public override string ToString()
        {
            if (m_Params == null)
                return m_Name;
            return m_Name + "( " + string.Join(", ", m_Params.Select(v => v.ToString()).ToArray()) + " )";
        }
    }
    public class Parameter : Number
    {
        public string Name { get; private set; }
        public override string ToString()
        {
            return Name+"["+base.ToString()+"]";
        }
        public Parameter(string aName) : base(0)
        {
            Name = aName;
        }
    }
    public class Expression : IValue
    {
        public Dictionary<string, Parameter> Parameters = new Dictionary<string, Parameter>();
        public IValue ExpressionTree { get; set; }
        public double Value
        {
            get { return ExpressionTree.Value; }
        }
        public double[] MultiValue
        {
            get {
                var t = ExpressionTree as MultiParameterList;
                if (t != null)
                {
                    double[] res = new double[t.Parameters.Length];
                    for (int i = 0; i < res.Length; i++)
                        res[i] = t.Parameters[i].Value;
                    return res;
                }
                return null;
            }
        }
        public override string ToString()
        {
            return ExpressionTree.ToString();
        }
        public ExpressionDelegate ToDelegate(params string[] aParamOrder)
        {
            var parameters = new List<Parameter>(aParamOrder.Length);
            for(int i = 0; i < aParamOrder.Length; i++)
            {
                if (Parameters.ContainsKey(aParamOrder[i]))
                    parameters.Add(Parameters[aParamOrder[i]]);
                else
                    parameters.Add(null);
            }
            var parameters2 = parameters.ToArray();
            return (p) => Invoke(p, parameters2);
        }
        public MultiResultDelegate ToMultiResultDelegate(params string[] aParamOrder)
        {
            var parameters = new List<Parameter>(aParamOrder.Length);
            for (int i = 0; i < aParamOrder.Length; i++)
            {
                if (Parameters.ContainsKey(aParamOrder[i]))
                    parameters.Add(Parameters[aParamOrder[i]]);
                else
                    parameters.Add(null);
            }
            var parameters2 = parameters.ToArray();
            return (p) => InvokeMultiResult(p, parameters2);
        }
        double Invoke(double[] aParams, Parameter[] aParamList)
        {
            int count = System.Math.Min(aParamList.Length, aParams.Length);
            for (int i = 0; i < count; i++ )
            {
                if (aParamList[i] != null)
                    aParamList[i].Value = aParams[i];
            }
            return Value;
        }
        double[] InvokeMultiResult(double[] aParams, Parameter[] aParamList)
        {
            int count = System.Math.Min(aParamList.Length, aParams.Length);
            for (int i = 0; i < count; i++)
            {
                if (aParamList[i] != null)
                    aParamList[i].Value = aParams[i];
            }
            return MultiValue;
        }
        public static Expression Parse(string aExpression)
        {
            return new ExpressionParser().EvaluateExpression(aExpression);
        }
        public class ParameterException : System.Exception { public ParameterException(string aMessage) : base(aMessage) { } }
    }
    public delegate double ExpressionDelegate(params double[] aParams);
    public delegate double[] MultiResultDelegate(params double[] aParams);
    public class ExpressionParser
    {
        private List<string> m_BracketHeap = new List<string>();
        private Dictionary<string, System.Func<double>> m_Consts = new Dictionary<string, System.Func<double>>();
        private Dictionary<string, System.Func<double[], double>> m_Funcs = new Dictionary<string, System.Func<double[], double>>();
        private Expression m_Context;
        public ExpressionParser()
        {
            var rnd = new System.Random();
            m_Consts.Add("PI", () => System.Math.PI);
            m_Consts.Add("e", () => System.Math.E);
            m_Funcs.Add("sqrt", (p) => System.Math.Sqrt(p.FirstOrDefault()));
            m_Funcs.Add("abs", (p) => System.Math.Abs(p.FirstOrDefault()));
            m_Funcs.Add("ln", (p) => System.Math.Log(p.FirstOrDefault()));
            m_Funcs.Add("floor", (p) => System.Math.Floor(p.FirstOrDefault()));
            m_Funcs.Add("ceiling", (p) => System.Math.Ceiling(p.FirstOrDefault()));
            m_Funcs.Add("round", (p) => System.Math.Round(p.FirstOrDefault()));
            m_Funcs.Add("sin", (p) => System.Math.Sin(p.FirstOrDefault()));
            m_Funcs.Add("cos", (p) => System.Math.Cos(p.FirstOrDefault()));
            m_Funcs.Add("tan", (p) => System.Math.Tan(p.FirstOrDefault()));
            m_Funcs.Add("asin", (p) => System.Math.Asin(p.FirstOrDefault()));
            m_Funcs.Add("acos", (p) => System.Math.Acos(p.FirstOrDefault()));
            m_Funcs.Add("atan", (p) => System.Math.Atan(p.FirstOrDefault()));
            m_Funcs.Add("atan2", (p) => System.Math.Atan2(p.FirstOrDefault(),p.ElementAtOrDefault(1)));
            //System.Math.Floor
            m_Funcs.Add("min", (p) => System.Math.Min(p.FirstOrDefault(), p.ElementAtOrDefault(1)));
            m_Funcs.Add("max", (p) => System.Math.Max(p.FirstOrDefault(), p.ElementAtOrDefault(1)));
            m_Funcs.Add("rnd", (p) =>
            {
                if (p.Length == 2)
                    return p[0] + rnd.NextDouble() * (p[1] - p[0]);
                if (p.Length == 1)
                    return rnd.NextDouble() * p[0];
                return rnd.NextDouble();
            });
        }
        public void AddFunc(string aName, System.Func<double[],double> aMethod)
        {
            if (m_Funcs.ContainsKey(aName))
                m_Funcs[aName] = aMethod;
            else
                m_Funcs.Add(aName, aMethod);
        }
        public void AddConst(string aName, System.Func<double> aMethod)
        {
            if (m_Consts.ContainsKey(aName))
                m_Consts[aName] = aMethod;
            else
                m_Consts.Add(aName, aMethod);
        }
        public void RemoveFunc(string aName)
        {
            if (m_Funcs.ContainsKey(aName))
                m_Funcs.Remove(aName);
        }
        public void RemoveConst(string aName)
        {
            if (m_Consts.ContainsKey(aName))
                m_Consts.Remove(aName);
        }
        int FindClosingBracket(ref string aText, int aStart, char aOpen, char aClose)
        {
            int counter = 0;
            for (int i = aStart; i < aText.Length; i++)
            {
                if (aText[i] == aOpen)
                    counter++;
                if (aText[i] == aClose)
                    counter--;
                if (counter == 0)
                    return i;
            }
            return -1;
        }
        void SubstitudeBracket(ref string aExpression, int aIndex)
        {
            int closing = FindClosingBracket(ref aExpression, aIndex, '(', ')');
            if (closing > aIndex + 1)
            {
                string inner = aExpression.Substring(aIndex + 1, closing - aIndex - 1);
                m_BracketHeap.Add(inner);
                string sub = "&" + (m_BracketHeap.Count - 1) + ";";
                aExpression = aExpression.Substring(0, aIndex) + sub + aExpression.Substring(closing + 1);
            }
            else throw new ParseException("Bracket not closed!");
        }
        IValue Parse(string aExpression)
        {
            aExpression = aExpression.Trim();
            int index = aExpression.IndexOf('(');
            while (index >= 0)
            {
                SubstitudeBracket(ref aExpression, index);
                index = aExpression.IndexOf('(');
            }
            if (aExpression.Contains(','))
            {
                string[] parts = aExpression.Split(',');
                List<IValue> exp = new List<IValue>(parts.Length);
                for (int i = 0; i < parts.Length; i++)
                {
                    string s = parts[i].Trim();
                    if (!string.IsNullOrEmpty(s))
                        exp.Add(Parse(s));
                }
                return new MultiParameterList(exp.ToArray());
            }
            else if (aExpression.Contains('+'))
            {
                string[] parts = aExpression.Split('+');
                List<IValue> exp = new List<IValue>(parts.Length);
                for (int i = 0; i < parts.Length; i++)
                {
                    string s = parts[i].Trim();
                    if (!string.IsNullOrEmpty(s))
                        exp.Add(Parse(s));
                }
                if (exp.Count == 1)
                    return exp[0];
                return new OperationSum(exp.ToArray());
            }
            else if (aExpression.Contains('-'))
            {
                string[] parts = aExpression.Split('-');
                List<IValue> exp = new List<IValue>(parts.Length);
                if (!string.IsNullOrEmpty(parts[0].Trim()))
                    exp.Add(Parse(parts[0]));
                for (int i = 1; i < parts.Length; i++)
                {
                    string s = parts[i].Trim();
                    if (!string.IsNullOrEmpty(s))
                        exp.Add(new OperationNegate(Parse(s)));
                }
                if (exp.Count == 1)
                    return exp[0];
                return new OperationSum(exp.ToArray());
            }
            else if (aExpression.Contains('*'))
            {
                string[] parts = aExpression.Split('*');
                List<IValue> exp = new List<IValue>(parts.Length);
                for (int i = 0; i < parts.Length; i++)
                {
                    exp.Add(Parse(parts[i]));
                }
                if (exp.Count == 1)
                    return exp[0];
                return new OperationProduct(exp.ToArray());
            }
            else if (aExpression.Contains('/'))
            {
                string[] parts = aExpression.Split('/');
                List<IValue> exp = new List<IValue>(parts.Length);
                if (!string.IsNullOrEmpty(parts[0].Trim()))
                    exp.Add(Parse(parts[0]));
                for (int i = 1; i < parts.Length; i++)
                {
                    string s = parts[i].Trim();
                    if (!string.IsNullOrEmpty(s))
                        exp.Add(new OperationReciprocal(Parse(s)));
                }
                return new OperationProduct(exp.ToArray());
            }
            else if (aExpression.Contains('^'))
            {
                int pos = aExpression.IndexOf('^');
                var val = Parse(aExpression.Substring(0, pos));
                var pow = Parse(aExpression.Substring(pos + 1));
                return new OperationPower(val, pow);
            }
            int pPos = aExpression.IndexOf("&");
            if (pPos > 0)
            {
                string fName = aExpression.Substring(0, pPos);
                foreach (var M in m_Funcs)
                {
                    if (fName == M.Key)
                    {
                        var inner = aExpression.Substring(M.Key.Length);
                        var param = Parse(inner);
                        var multiParams = param as MultiParameterList;
                        IValue[] parameters;
                        if (multiParams != null)
                            parameters = multiParams.Parameters;
                        else
                            parameters = new IValue[] { param };
                        return new CustomFunction(M.Key, M.Value, parameters);
                    }
                }
            }
            foreach (var C in m_Consts)
            {
                if (aExpression == C.Key)
                {
                    return new CustomFunction(C.Key,(p)=>C.Value(),null);
                }
            }
            int index2a = aExpression.IndexOf('&');
            int index2b = aExpression.IndexOf(';');
            if (index2a >= 0 && index2b >= 2)
            {
                var inner = aExpression.Substring(index2a + 1, index2b - index2a - 1);
                int bracketIndex;
                if (int.TryParse(inner, out bracketIndex) && bracketIndex >= 0 && bracketIndex < m_BracketHeap.Count)
                {
                    return Parse(m_BracketHeap[bracketIndex]);
                }
                else
                    throw new ParseException("Can't parse substitude token");
            }
            double doubleValue;
            if (double.TryParse(aExpression, out doubleValue))
            {
                return new Number(doubleValue);
            }
            if (ValidIdentifier(aExpression))
            {
                if (m_Context.Parameters.ContainsKey(aExpression))
                    return m_Context.Parameters[aExpression];
                var val = new Parameter(aExpression);
                m_Context.Parameters.Add(aExpression, val);
                return val;
            }
            throw new ParseException("Reached unexpected end within the parsing tree");
        }
        private bool ValidIdentifier(string aExpression)
        {
            aExpression = aExpression.Trim();
            if (string.IsNullOrEmpty(aExpression))
                return false;
            if (aExpression.Length < 1)
                return false;
            if (aExpression.Contains(" "))
                return false;
            if (!"abcdefghijklmnopqrstuvwxyz§$".Contains(char.ToLower(aExpression[0])))
                return false;
            if (m_Consts.ContainsKey(aExpression))
                return false;
            if (m_Funcs.ContainsKey(aExpression))
                return false;
            return true;
        }
        public Expression EvaluateExpression(string aExpression)
        {
            var val = new Expression();
            m_Context = val;
            val.ExpressionTree = Parse(aExpression);
            m_Context = null;
            m_BracketHeap.Clear();
            return val;
        }
        public double Evaluate(string aExpression)
        {
            return EvaluateExpression(aExpression).Value;
        }
        public static double Eval(string aExpression)
        {
            return new ExpressionParser().Evaluate(aExpression);
        }
        public class ParseException : System.Exception { public ParseException(string aMessage) : base(aMessage) { } }
    }
}

how to use:

using B83.ExpressionParser;

void Start()
{
    ExpressionParser parser = new ExpressionParser();
    Expression exp = parser.EvaluateExpression("(5+3)*8^2-5*(-2)");
    Debug.Log("Result: " + exp.Value);  // prints: "Result: 522"
}
1 Like

.Net has a built in function in the DataTable class:

System.Data.DataTable table = new System.Data.DataTable();

string expression = "(123 + 456) * 789";

try
{
   print(table.Compute(expression, ""));
}

catch
{
   // handle input error
}
3 Likes