LoginSignup
8
13

More than 3 years have passed since last update.

C#で文字列の数式を計算するプログラム

Last updated at Posted at 2019-03-11

C#で文字列の数式を計算する方法を探していたんですが、
ちょっといいのがないので作りました。

以下の事項に対応しています
・四則演算
・括弧
・+/- の単項演算子
・変数、関数

よく見かける方法と比べて、変数や関数が使えるのが特徴です。

こちらのプログラムはGitHubの方に登録しました。
https://github.com/izmktr/SimpleCalc

using System;
using System.Collections.Generic;

namespace Calc
{
    class Program
    {
        static float mathfunc(string name, List<float> arg)
        {
            if (name == "pi") return (float)Math.PI;
            if (name == "sin") return (float)Math.Sin(arg[0]);
            if (name == "cos") return (float)Math.Cos(arg[0]);
            if (name == "max") return Math.Max(arg[0], arg[1]);

            return 0;
        }

        static void Main(string[] args)
        {
            var formura1 = Calc.Analyze("(-5 + 2 )* -4");

            float answer1 = formura1.Calc(null);
            Console.WriteLine($"calc :{answer1}");

            var formura2 = Calc.Analyze("max(sin(theta * pi / 180), cos(theta * pi / 180))");

            for (int i = 0; i <= 90; i += 5)
            {
                Func<string, List<float>, float> f = (name, arg) =>
                {
                    if (name == "theta") return (float)i;
                    return mathfunc(name, arg);
                };

                float answer2 = formura2.Calc(f);
                Console.WriteLine($"i = {i}: calc = {answer2}");
            }

        }
    }
}

変数や関数はCalc時にfloat f(string name, List < float > arg) の関数を引き渡します。
変数や関数が出てくるたびに渡した関数が呼び出されます。

pi と書いた場合は変数とみなされ、pi()と書いた場合は引数0の関数とみなされます。
変数の場合は、arg=null、関数の場合はarg=List となります。

Unityの座標計算に使うために作ったので、数値はすべてfloatですが、
Intなりdoubleなりに書き換えるといいと思います

関数本体のソース

using System;
using System.Collections.Generic;

namespace Calc
{
    static class Calc
    {
        enum Operator
        {
            Unknown,
            Number,
            Alphabet,
            Plus,
            Minus,
            Multi,
            Divide,
            Mod,
            LParen,
            RParen,
            Camma,
            Space,
        }

        class Lexical
        {
            public Operator op;
            public string str;

            public override string ToString() { return $"op={op}, str={str}"; }
        }

        public interface NodeBase
        {
            float Calc(Func<string, List<float>, float> func);
            bool IsCalcable { get; }
        }

        class NodeNumber: NodeBase
        {
            float value;

            public bool IsCalcable => true;

            public float Calc(Func<string, List<float>, float> func)
            {
                return value;
            }

            public NodeNumber(float value)
            {
                this.value = value;
            }
        }

        class NodeNegative: NodeBase
        {
            NodeBase node;
            public bool IsCalcable => node.IsCalcable;

            public float Calc(Func<string, List<float>, float> func)
            {
                return -node.Calc(func);
            }

            public NodeNegative(NodeBase node)
            {
                this.node = node;
            }
        }

        class NodeFunction : NodeBase
        {
            string funcname;
            List<NodeBase> nodearg = new List<NodeBase>();

            public NodeFunction(string func, List<NodeBase> nodearg)
            {
                this.funcname = func;
                this.nodearg = nodearg;
            }

            public float Calc(Func<string, List<float>, float> func)
            {
                List<float> numberarg = new List<float>();

                if (nodearg != null)
                {
                    foreach (var item in nodearg)
                    {
                        numberarg.Add(item.Calc(func));
                    }
                }

                return func(funcname, numberarg);
            }
            public bool IsCalcable => false;
        }

        class NodeTree : NodeBase
        {
            private Operator op;
            private NodeBase term1;
            private NodeBase term2;

            public NodeTree(Operator op, Calc.NodeBase term1, Calc.NodeBase term2)
            {
                this.op = op;
                this.term1 = term1;
                this.term2 = term2;
            }

            public bool IsCalcable => (term1 == null ? term1.IsCalcable : true) && (term2 == null ? term2.IsCalcable : true);

            public float Calc(Func<string, List<float>, float> func)
            {
                switch (op)
                {
                    case Operator.Plus:
                        return term1.Calc(func) + term2.Calc(func);
                    case Operator.Minus:
                        return term1.Calc(func) - term2.Calc(func);
                    case Operator.Multi:
                        return term1.Calc(func) * term2.Calc(func);
                    case Operator.Divide:
                        return term1.Calc(func) / term2.Calc(func);
                    case Operator.Mod:
                        return term1.Calc(func) % term2.Calc(func);
                }
                return 0;
            }
        }


        class Analyzer
        {
            List<Lexical> unitlist = new List<Lexical>();
            int current = 0;

            public void LexicalAnalysis(string formula)
            {
                unitlist.Clear();

                for (int idx = 0; idx < formula.Length; idx++)
                {
                    char c = formula[idx];
                    Operator op = GetOperator(c);

                    if (op == Operator.Space) continue;
                    if (op == Operator.Unknown) throw new Exception($"unknown letter: {c}");

                    if (op == Operator.Number)
                    {
                        var str = GetContinuity(formula, n => n == Operator.Number, ref idx);
                        unitlist.Add(new Lexical() { op = op, str = str });
                    }
                    else if (op == Operator.Alphabet)
                    {
                        var str = GetContinuity(formula, n => n == Operator.Number || n == Operator.Alphabet, ref idx);
                        unitlist.Add(new Lexical() { op = op, str = str });

                    }
                    else
                    {
                        unitlist.Add(new Lexical() { op = op });
                    }

                }
            }

            private string GetContinuity(string formula, Func<Operator, bool> compare, ref int idx)
            {
                int start = idx;
                for (int i = idx + 1; i < formula.Length; i++)
                {
                    if (!compare(GetOperator(formula[i])))
                    {
                        idx = i - 1;
                        return formula.Substring(start, i - start);
                    }
                }
                idx = formula.Length - 1;
                return formula.Substring(start);
            }

            private Operator GetOperator(char c)
            {
                switch (c)
                {
                    case '+': return Operator.Plus;
                    case '-': return Operator.Minus;
                    case '*': return Operator.Multi;
                    case '/': return Operator.Divide;
                    case '%': return Operator.Mod;
                    case '(': return Operator.LParen;
                    case ')': return Operator.RParen;
                    case ',': return Operator.Camma;
                    case ' ': return Operator.Space;
                    case '\t': return Operator.Space;
                    case '.': return Operator.Number;
                }

                if (Char.IsNumber(c)) return Operator.Number;
                if (Char.IsLetter(c)) return Operator.Alphabet;

                return Operator.Unknown;
            }

            public NodeBase GetExpr()
            {
                var term1 = GetTerm();

                for(;;)
                {
                var next = GetNext();
                if (next == null) return term1;
                if (next.op == Operator.Plus || next.op == Operator.Minus)
                {
                    var term2 = GetTerm();
                    term1 = new NodeTree(next.op, term1, term2);
                }
                    else
                        break;
                }
                Unget();
                return term1;
            }

            NodeBase GetTerm()
            {
                var term1 = GetFactor();

                for(;;)
                {
                    var next = GetNext();
                    if (next == null) return term1;
                    if (next.op == Operator.Multi || next.op == Operator.Divide || next.op == Operator.Mod)
                    {
                        var term2 = GetFactor();
                        term1 = new NodeTree(next.op, term1, term2);
                    }
                    else
                        break;
                }
                Unget();
                return term1;
            }

            NodeBase GetFactor()
            {
                var lparen = GetNext();
                if (lparen == null)
                {
                    throw new Exception("式の終端に達しました");
                }

                switch (lparen.op)
                {
                    case Operator.LParen:
                        {
                            var expr = GetExpr();
                            var rparen = GetNext();
                            if (rparen == null || rparen.op == Operator.RParen) return expr;
                            Unget();
                            break;
                        }
                    default:
                        Unget();
                        return GetNumber();
                }
                {
                    throw new Exception("式の終端に達しました");
                }
            }

            NodeBase GetNumber()
            {
                var unit = GetNext();

                switch (unit.op)
                {
                    case Operator.Number:
                        return new NodeNumber(float.Parse(unit.str));
                    case Operator.Alphabet:
                        Unget();
                        return GetFunction();
                    case Operator.Plus:
                        return GetNumber();
                    case Operator.Minus:
                        {
                            var minus = GetNumber();
                            return minus != null ? new NodeNegative(minus) : null;
                        }
                }
                return null;
            }

            private NodeBase GetFunction()
            {
                var funcname = GetNext();
                if (funcname == null || funcname.op != Operator.Alphabet)
                {
                    throw new Exception("関数名がありません");
                }

                var lparen = GetNext();
                if (lparen == null || lparen.op != Operator.LParen)
                {
                    if (lparen != null) Unget();
                    return new NodeFunction(funcname.str, null);
                }

                var arg = new List<NodeBase>();
                for (; ; )
                {
                    var node = GetExpr();
                    if (node == null) return new NodeFunction(funcname.str, arg);
                    arg.Add(node);

                    var end = GetNext();

                    if (end == null || end.op == Operator.RParen)
                    {
                        return new NodeFunction(funcname.str, arg);
                    }
                    if (end.op != Operator.Camma)
                    {
                        throw new Exception("関数内の区切りがカンマではありません");
                    }
                }

            }

            Lexical GetNext()
            {
                if (current < unitlist.Count)
                {
                    return unitlist[current++];
                }
                return null;
            }

            void Unget()
            {
                current--;
            }

        }

        static public NodeBase Analyze(string formula)
        {
            Analyzer analyzer = new Analyzer();

            analyzer.LexicalAnalysis(formula);

            return analyzer.GetExpr() ;
        }

    }

}

8
13
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
13