C#で関数電卓を作ってみた

最終更新：2019/5/30
現在ver.1.2.2

プログラム言語をつくってみたいが、なかなかハードルが高い・・・

最初の一歩として**関数電卓**（のようなもの）をつくってみよう！

とういわけで作ってみました

ソースコードに関しては、[Calculatorクラス](https://qiita.com/HitsujiRere/private/a5114dcbee028e1178e6#calculator%E3%82%AF%E3%83%A9%E3%82%B9), [Mainメソッド](https://qiita.com/HitsujiRere/private/a5114dcbee028e1178e6#main%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)まで飛ばしますようお願いします

# 内容
今回の電卓は、 **逆ポーランド記法**で書く
スペースで区切って入力する

起動時の画面はこんな感じ
（演算子,命令の説明は今後変更予定）
![C__windows_system32_cmd.exe 2019_05_30 13_16_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422071/a787a09d-aca0-fe74-e67d-b867fb35f568.png)

計算はこのようになる
![C__windows_system32_cmd.exe 2019_05_30 13_16_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422071/328c212f-6266-ace4-de11-e52c3adef072.png)

`5 2 + 3 4 * 5 - +`
は
`(5 + 2) + ((3 * 4) - 5)`
の意味である

また、変数機能が使用でき、
`xvar:変数名`
で変数を定義する
（初期値は0.0）

そして、
`xin:変数名`
で一番後ろの数値を変数に入れる
（変数が変数が定義されていない場合は、自動的に定義される）

`xout:変数名`
もしくは
`変数名`
で値を数値Stackに入れる

なお、
`xcout`
で現在登録されている変数の一覧を確認することができる

他の演算子,命令の機能は
`help`
で確認することができる
![C__windows_system32_cmd.exe 2019_05_30 13_20_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422071/18aa51e1-df26-3906-b97e-5d27d5add071.png)


# 処理
Dictionary\<string, 演算>に、演算子をkey、演算をvalueに登録する
（以下、演算Dictionary）
またStack\<double>に、数値を登録する
（以下、数値Stack）

まず、入力されたstringが演算Dictionaryに登録しているかどうか調べる

登録してあるなら、演算Dictionaryの演算が求めている数の数字を数値Stackから演算に与える
そして、その返り値を数値Stackに追加する

登録してない場合、doubleに変換できるなら数値Stackに追加する

それを最後まで繰り返す

# 今後
- 中置記法でも書けるようにする

# Calculatorクラス
以下が「計算機」のソースコード

```c#:Calculator.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace Calculator
{
    // 計算機
    class Calculator
    {
        // バージョン情報
        static public readonly Version version = new Version(1, 2, 2);
        // 製作者名
        static public readonly string maker = "HitsujiRere";

        // 接頭辞と初期値のディクショナリ
        static public Dictionary<char, bool> prefixs = new Dictionary<char, bool>()
        {
            ['&'] = true,
            ['@'] = false,
        };

        // 演算子と計算のディクショナリ
        static public Dictionary<string, Func<double, string[], double>> operatorsOf1 =
            new Dictionary<string, Func<double, string[], double>>()
            {
                ["=%"] = (a, _) => a * 0.01,
                ["-%"] = (a, _) => a * 100.0,
                ["++"] = (a, _) => a + 1.0,
                ["--"] = (a, _) => a - 1.0,
                ["*-"] = (a, _) => a * -1.0,
                ["sq"] = (a, _) => a * a,
                ["sqrt"] = (a, _) => Math.Sqrt(a),
                ["bx"] = (a, _) => a * a * a,
                ["bxrt"] = (a, _) => Math.Pow(a, 1.0 / 3.0),
            };
        static public Dictionary<string, Func<double, double, string[], double>> operatorsOf2 =
            new Dictionary<string, Func<double, double, string[], double>>()
            {
                ["+"] = (a, b, _) => a + b,
                ["-"] = (a, b, _) => a - b,
                ["*"] = (a, b, _) => a * b,
                ["/"] = (a, b, _) => a / b,
                ["%"] = (a, b, _) => a % b,
                ["mod"] = (a, b, _) => a % b,
                ["div"] = (a, b, _) => (a - (a % b)) / b,
                ["^"] = (a, b, _) => Math.Pow(a, b),
            };
        static public Dictionary<string, Func<double[], string[], double>> operatorsAll =
            new Dictionary<string, Func<double[], string[], double>>()
            {
                ["cnt"] = (a, _) => a.Count(),
                ["sum"] = (a, _) => a.Sum(),
                ["ave"] = (a, _) => a.Average(),
            };

        // 命令と計算のディクショナリ
        static public Dictionary<string, Action<Calculator, string[]>> instructions =
            new Dictionary<string, Action<Calculator, string[]>>()
            {
                ["sort"] = (calc, _) =>
                {
                    calc.numStack = new Stack<double>(calc.numStack.OrderBy(x => x));
                },
                ["rev"] = (calc, _) =>
                {
                    calc.numStack = new Stack<double>(calc.numStack);
                },
                ["clr"] = (calc, _) => { calc.numStack.Clear(); },
                ["help"] = (calc, _) =>
                {
                    Console.WriteLine("特殊な接頭辞");
                    Prints(prefixs.Select(x => x.Key.ToString()));

                    Console.WriteLine("1項演算子");
                    Prints(operatorsOf1.Select(x => x.Key));

                    Console.WriteLine("2項演算子");
                    Prints(operatorsOf2.Select(x => x.Key));

                    Console.WriteLine("全てへの演算子");
                    Prints(operatorsAll.Select(x => x.Key));

                    Console.WriteLine("命令");
                    Prints(instructions.Select(x => x.Key));

                    void Prints(IEnumerable<string> a)
                    {
                        foreach (var item in a)
                        {
                            Console.WriteLine("{0,6} = {1}", item,
                                comments.ContainsKey(item) ? comments[item] : "not found");
                        }
                        Console.WriteLine();
                    }
                },
                ["xvar"] = (calc, names) =>
                {
                    foreach (var name in names)
                    {
                        if (!calc.variables.ContainsKey(name))
                        {
                            calc.variables[name] = 0.0;
                        }
                    }
                },
                ["xin"] = (calc, names) =>
                {
                    foreach (var name in names)
                    {
                        if (calc.numStack.Count >= 1)
                        {
                            calc.variables[name] = calc.numStack.Pop();
                        }
                    }
                },
                ["xout"] = (calc, names) =>
                {
                    foreach (var name in names)
                    {
                        if (calc.variables.ContainsKey(name))
                        {
                            calc.numStack.Push(calc.variables[name]);
                        }
                    }
                },
                ["xcout"] = (calc, _) =>
                {
                    foreach (var item in calc.variables)
                    {
                        Console.WriteLine("{0,6} = {1}", item.Key, item.Value);
                    }
                    Console.WriteLine();
                },
            };

        // 接頭辞,演算子,命令についての説明
        static public Dictionary<string, string> comments =
            new Dictionary<string, string>()
            {
                ["&"] = "計算のために取得した数字を削除しない",
                ["@"] = "全ての数に対して演算を行う（実装中）",
                ["=%"] = "前の数値を、パーセント数値にする（*100する）",
                ["-%"] = "前のパーセント数値を、数値にする（*0.01する）",
                ["++"] = "前の数字を、+1する",
                ["--"] = "前の数字を、-1する",
                ["*-"] = "前の数字を、*(-1)する",
                ["sq"] = "前の数字を、平方する",
                ["sqrt"] = "前の数字を、平方根にする",
                ["bx"] = "前の数字を、立方する",
                ["bxrt"] = "前の数字を、立方根にする",
                ["+"] = "前の数字2つを、足し算する",
                ["-"] = "前の数字2つを、引き算する",
                ["*"] = "前の数字2つを、掛け算する",
                ["/"] = "前の数字2つを、割り算する",
                ["%"] = "前の数字2つを、割り算し、その余りをとる",
                ["mod"] = "前の数字2つを、割り算し、その余りをとる",
                ["div"] = "前の数字2つを、割り算し、その商をとる",
                ["^"] = "前の数字2つを、累乗する",
                ["cnt"] = "前の数字全てを、計数する",
                ["sum"] = "前の数字全てを、合計する",
                ["ave"] = "前の数字全てを、平均する",
                ["sort"] = "数値スタックを、昇順にソートする",
                ["rev"] = "数値スタックを、順序逆転する",
                ["clr"] = "数値スタックを、全て消去する",
                ["help"] = "演算子,命令の一覧,説明を、出力する",
                ["xvar"] = "変数を登録する",
                ["xin"] = "前の数字を、変数に入れる",
                ["xout"] = "変数を数値Stackに入れる",
                ["xcout"] = "全ての変数とその値を出力する",
            };

        // 数値を入れておくスタック
        public Stack<double> numStack = new Stack<double>();

        // 変数のディクショナリ
        public Dictionary<string, double> variables =
            new Dictionary<string, double>();


        // 計算もしくは数字を追加する
        public bool PassString(string[] txts)
        {
            var main = txts[0];
            var options = txts.Skip(1).ToArray();

            // 演算子が有効かどうか
            var havePrefixs = new Dictionary<char, bool>(prefixs);

            // 先頭に接頭辞が見つからなくなるまで繰り返す
            var isContain = true;
            while (isContain)
            {
                if (havePrefixs.ContainsKey(main[0]))
                {
                    havePrefixs[main[0]] ^= true;
                    main = main.Substring(1);
                    isContain = true;
                }
                else
                {
                    isContain = false;
                }
            }

            if (numStack.Count >= 1 && operatorsOf1.ContainsKey(main))
            {
                var num = havePrefixs['&'] ? numStack.Pop() : numStack.Peek();
                numStack.Push(operatorsOf1[main](num, options));

                return true;
            }
            else if (numStack.Count >= 2 && operatorsOf2.ContainsKey(main))
            {
                var num1 = havePrefixs['&'] ? numStack.Pop() : numStack.Peek();
                var num2 = havePrefixs['&'] ? numStack.Pop() : numStack.Peek();
                numStack.Push(operatorsOf2[main](num2, num1, options));

                return true;
            }
            else if (operatorsAll.ContainsKey(main))
            {
                var nums = numStack.ToArray();
                if (havePrefixs['&'])
                    numStack.Clear();
                numStack.Push(operatorsAll[main](nums, options));

                return true;
            }
            else if (instructions.ContainsKey(txts[0]))
            {
                instructions[main](this, options);

                return true;
            }
            else if (double.TryParse(main, out var num))
            {
                numStack.Push(num);

                return true;
            }
            else if (variables.ContainsKey(main))
            {
                numStack.Push(variables[main]);

                return true;
            }

            return false;
        }
        public bool PassString(string word)
        {
            var elements = word.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);

            return PassString(elements);
        }

        // txtsを順に計算もしくは数字を追加する
        public double[] PassStrings(string[] words, bool debug = false)
        {
            bool existNotFound = false;

            foreach (var word in words)
            {
                if (PassString(word.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)))
                {
                    if (debug)
                    {
                        Console.Write("= ");
                        if (numStack.Count > 0)
                        {
                            foreach (var num in numStack.Reverse())
                            {
                                Console.Write("{0}, ", num);
                            }
                            Console.Write('\n');
                        }
                        else
                        {
                            Console.WriteLine("nothing");
                        }
                    }
                }
                else
                {
                    Console.WriteLine("{0} is not found", word);
                    existNotFound = true;
                }
            }
            if (debug || existNotFound)
            {
                Console.WriteLine();
            }

            return numStack.Reverse()
                           .ToArray();
        }

        // txtの命令を実行する
        public bool Instruction(string words)
        {
            var word = words.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);

            if (instructions.ContainsKey(word[0]))
            {
                instructions[word[0]](this, word.Skip(1).ToArray());

                return true;
            }

            return false;
        }
    }
}
```

</details>

# Mainメソッド
```C#:Program.cs
using System;

namespace Calculator
{
    class Program
    {
        static void Main(string[] args)
        {
            // 計算機
            var calc = new Calculator();

            Console.WriteLine("Scientific Calculator");
            Console.WriteLine("ver.{0}", Calculator.version);
            Console.WriteLine("maked by {0}", Calculator.maker);
            Console.WriteLine();

            Console.WriteLine("演算子,命令の一覧,説明は、'help'で確認できます");
            Console.WriteLine();

            //下のwhileを繰り返すかどうか
            var isRepeat = true;
            while (isRepeat)
            {
                // コマンドから入力したものを' '（空白）もしくは','で分割する
                Console.Write("> ");
                var inputs = Console.ReadLine().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
                Console.WriteLine();

                // 分割した入力配列を渡す
                var nums = calc.PassStrings(inputs, debug: false);
                Console.Write("= ");
                if (nums.Length > 0)
                {
                    foreach (var num in nums)
                    {
                        Console.Write("{0}, ", num);
                    }
                    Console.Write('\n');
                }
                else
                {
                    Console.WriteLine("nothing");
                }
                Console.WriteLine();
            }
        }
    }
}
```