LoginSignup
3
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-05-24

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

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

最初の一歩として関数電卓(のようなもの)をつくってみよう!

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

ソースコードに関しては、Calculatorクラス, Mainメソッドまで飛ばしますようお願いします

内容

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

起動時の画面はこんな感じ
(演算子,命令の説明は今後変更予定)
C__windows_system32_cmd.exe 2019_05_30 13_16_18.png

計算はこのようになる
C__windows_system32_cmd.exe 2019_05_30 13_16_28.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

処理

Dictionary<string, 演算>に、演算子をkey、演算をvalueに登録する
(以下、演算Dictionary)
またStack<double>に、数値を登録する
(以下、数値Stack)

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

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

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

それを最後まで繰り返す

今後

  • 中置記法でも書けるようにする

Calculatorクラス

以下が「計算機」のソースコード

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

Mainメソッド

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();
            }
        }
    }
}
3
3
0

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
3
3