C# 初級講座
Visual Studio 2019 Visual Studio 2022

第5回 数学問題集

2022/8/7

この記事が対象とする製品・バージョン

Visual Studio 2022 Visual Studio 2022 対象です。
Visual Studio 2019 Visual Studio 2019 対象です。
Visual Studio 2017 Visual Studio 2017 対象外ですが参考にはなります。
Visual Studio 2015 Visual Studio 2015 対象外ですが参考にはなります。
Visual Studio 2013 Visual Studio 2013 対象外ですが参考にはなります。
Visual Studio 2012 Visual Studio 2012 対象外ですが参考にはなります。
Visual Studio 2010 Visual Studio 2010 対象外ですが参考にはなります。
Visual Studio 2008 Visual Studio 2008 対象外ですが参考にはなります。
Visual Studio 2005 Visual Studio 2005 対象外ですが参考にはなります。
Visual Studio.NET 2003 Visual Studio.NET 2003 対象外ですが参考にはなります。
Visual Studio.NET 2002 Visual Studio.NET (2002) 対象外ですが参考にはなります。
  Visual Studio Code 対象外ですが参考にはなります。

 

目次

 

1.ハードコード

算数・数学の問題を次々と出題する勉強用のプログラムを作ってみましょう。

今回はいきなり完成版をお見せするのではなく少しづつ作っていくことにします。

下記の設定で新規プロジェクトを作成してください。

プロジェクトテンプレート コンソール アプリ
プロジェクト名 CalcDrill
ソリューション名 CalcDrill
フレームワーク .NET 6.0

 

あらかじめプログラムの中に問題を仕込んでおいて、ユーザーに回答させるプログラムは簡単だと思います。

次のようになります。

 

まずは、次の通りプログラムをして実行してください。

Console.WriteLine("数学問題集 バージョン 1.0");

string answer;

Console.WriteLine("3 + 4 = ?");
answer = Console.ReadLine()!;

if (answer == "7")
{
    Console.WriteLine("正解!");
}
else
{
    Console.WriteLine("はずれ");
}

Console.WriteLine("3 の 2乗 は?");
answer = Console.ReadLine()!;

if (answer == "9")
{
    Console.WriteLine("正解!");
}
else
{
    Console.WriteLine("はずれ");
}

Console.WriteLine("終了するには Enter を押してください。");
Console.ReadLine();

このプログラムは前回の知識があれば作れますね。

最後の Console.ReadLine() はプログラムが自動的に終了しないように Enter の入力待ちをするだけなので、戻り値は受け取りません。

Visual Studioで実行していると、プログラムが終了してもコンソールが閉じないようになりますが、完成したプログラム(exe)をVisual Studioなしで単体で実行すると、プログラムが終了するとウィンドウが閉じてしまうので、常人の動体視力では最後に表示されるメッセージが確認できません。そこで、このプログラムのようにユーザーの操作をまって終了するようにすることがあります。

 

このプログラムでは 3 + 4 や 3 の 2乗 という具体的な問題がプログラム内に書き込まれており、回答の 7 や 9 も同様にプログラム内に埋め込まれています。このようにプログラム内に具体的な値を埋め込むことを「ハードコード」と呼びます。

ハードコードは初心者向けの簡易的なプログラムでは簡単で便利ですが、毎回同じ値が使用される点で、柔軟性がなく通常あまり使用されません。このプログラムも何度実行しても同じ問題が出題されるので面白味がありませんね。

ここから改造していきましょう。

 

2.インスタンス

2-1.インスタンスの作成と使用

ランダムな数を使って毎回違う問題が出題されるようにプログラムしてみましょう。

フレームワークのRandomクラス(読み方:Random=ランダム)を使うとランダムな数を簡単に生成することができます。ランダムな整数を生成するには Next メソッド(読み方:Next=ネクスト)を使用します。

ちょっと練習してみましょう。

次の小さなプログラムは1~100の間でランダムな数を表示します

Random randomizer = new Random();

while(true)
{
    decimal value = randomizer.Next(1, 101);
    Console.Write(value);
    Console.ReadLine(); //Enterの入力待ち
}

Enterをどんどん押していくとランダムな数字が表示されるのがわかります。

while(true) { } で囲んだので、無限に繰り返されます。

 

Nextメソッドは最初の引数以上で、2番目の引数より小さい整数をランダムで生成するので、Next(1, 101)という呼び出しは、1~100のランダムな数値を生成するという意味になります。

 

1行目はちょっと見慣れない記述かもしれません。1行目で使っている new (読み方:new=ニュー)は「コンストラクター」というものを呼び出す機能です。フレームワークの機能を呼び出すときによく登場します。この new は初心者が最初につまづくポイントのように思います。

ここは重要なポイントなので次回少し視点を変えてあたらめて説明します。

今回はこれがどういう意味か、ちょっと Windowsの「メモ帳」を例に説明しましょう。

 

2-2.メモ帳の例

Windowsでは「メモ帳」の実体は既定では C:\WINDOWS\system32 フォルダーにある notepad.exe です。

メモ帳には文字を入力したり、ファイルを読み込んだりする機能がありますよね。

でも、この notepad.exe というファイルに文字を入力したり、ファイルを読み込ませたりということはできませんね。メモ帳の機能を使うには、notepad.exeをダブルクリックするなどして(通常はスタートメニューのショートカットを使いますが)メモ帳を起動する必要があります。

文字を入力したり、ファイルを読み込んだりできるのはこの起動したメモ帳であって、notepad.exeというファイル自体ではありません。

さらに面白いことに1つのnotepad.exeから複数のメモ帳を起動して、それぞれ別の文字を入力したり、別のファイルを開いたりすることができます。

 

フレームワークのクラスとインスタンスもこれと同じ考え方で、基本的にはクラス自体から機能を呼び出すのではなく、インスタンスから機能を呼び出します。

「メモ帳を起動する」のに相当するのが「クラスのコンストラクターを呼び出す」という操作です。

メモ帳を起動するにはダブルクリックなどしますが、クラスのコンストラクターを呼び出すには、new を使うのが一般的です。

new を複数回使用すると、インスタンスも複数作成されます。

今回はランダムな数を生成する機能は1つあれば十分なので1回だけnewして作成したインスタンスを使いまわします。

 

2-3.静的メンバー

とはいっても、Math.Pow や Convert.ToDecimal のようにクラスから直接メソッドやプロパティなどのメンバーを呼び出すことができるものも存在しています。

Math.Powは2の3乗や、5の2乗などの累乗(べき乗)を計算する機能です。これはどのような状況でも同じ引数で呼び出せば必ず同じ答えを返すので、インスタンスを作成してから呼び出す意味はなく、インスタンスなしでクラスからいきなり機能を呼び出せるようにデザインされています。このようなメソッドを静的メソッドと呼びます。プロパティの場合、静的プロパティです。まとめて表現する場合は静的メンバーと呼びます。

Convert.ToDecimalも値を decimal型に変換する機能であり、これもどのような状況でも同じ引数で呼び出せば同じ結果となるので静的メソッドとしてデザインされています。

ランダムな数の場合、ものがランダムなのでちょっと考えにくくいのですが、ランダムな数の生成は状況によって結果が変わります。「えー、ランダムななんだから状況とか関係ないんじゃないの?」と思うかもしれませんが実際はそうではないのです。毎回同じランダムな数を発生させることも可能なのです。

たとえば、さきほどのプログラム1行目のコンストラクター呼び出しのかっこの中に 1 を追加してください。

Random randomizer = new Random(1);

while(true)
{
    decimal value = randomizer.Next(1, 101);
    Console.WriteLine(value);
    Console.ReadLine(); //Enterの入力待ち
}

これで実行すると、何度実行しても必ず 25 → 12 → 47 → 78 …という順でランダムな数字が生成されます。

1を2にすると、78 → 41 → 17 → 99 …という順になります。

状況によって違うというのが理解していただけますでしょうか。そのため、クラスから直接呼び出すのではなく、インスタンス経由で機能を呼び出すようにデザインされています。

メモ帳の保存ボタンを押したときに、そのメモ帳の編集『状況』によって何が保存されるか変わるのと同じです。

 

 

3.たし算の自動生成

3-1.たし算をランダムの値で生成する

それではランダムな数の生成機能を使ってプログラムに改造してみます。

ポイントに集中するためにたし算だけにしてみました。

Console.WriteLine("数学問題集 バージョン 1.1");
Console.WriteLine("終了するには Ctrl + C を押してください。");

Random randomizer = new Random();

while (true)
{
    // 1 ~ 100 のランダムな数を value1, value2に代入
    decimal value1 = randomizer.Next(1, 101);
    decimal value2 = randomizer.Next(1, 101);

    decimal seikai = value1 + value2;
    Console.WriteLine($"{value1} + {value2} = ?");
    string answer = Console.ReadLine()!;

    if (answer != "" && Convert.ToDecimal(answer) == seikai)
    {
        Console.WriteLine("正解!");
    }
    else
    {
        Console.WriteLine("はずれ");
        Console.WriteLine($"正解は {seikai} でした。");
    }
}

小学生用のたし算練習プログラムとしてはなんとか使えるレベルですね。

 

3-2.&&

正解を判断する if文は、空の文字がエラーならないように条件を追加しています。

この部分です。

if (answer != "" && Convert.ToDecimal(answer) == seikai)
{
    Console.WriteLine("正解!");
}

この if 文では2つの条件を判断しています。 && は2つの条件を結合し、両方とも成立する場合にif文の内容を実行します。どちらかが成立しないか、両方が成立しない場合は、else if や else など後続の文が実行されます。

このプログラムでは1つ目の条件は次の通りです。


answer != ""

!= は == の逆で、等しくないかを調べます。"" はゼロ文字の文字列です。

ユーザーが何も入力せずEnterを押した場合、answer は ゼロ文字の文字列(つまり、"")になるので、この条件では != を使ってそうではないことを確認しています。

もう1つの条件は次の通りです。


Convert.ToDecimal(answer) == seikai)

これは前にもでてきたようにユーザーの入力を decimal に変換して seikai を一致するかを調べるものです。

この2つの条件が && で結びつけられているので、この if 文は全体として、ユーザーが1文字以上入力しており、その値を decimal に変換すると seikai に一致するかを調べるという意味になります。

 

なお、ここでは空文字ではないチェックしか追加していないので、ユーザーがdecimal変換できない文字を入力した場合は、ToDecimalがエラーになってプログラムは終了してしまいます。

 

4.ひき算の自動生成

4-1.ひき算が出題されることもあるようにする

ランダムにひき算も出題されるようにしてみましょう。

たし算になるかひき算になるかもランダムに決定するようにしてみます。

Console.WriteLine("数学問題集 バージョン 1.2");
Console.WriteLine("終了するには Ctrl + C を押してください。");

Random randomizer = new Random();

while (true)
{
    // 1 ~ 100 のランダムな数を value1, value2に代入
    decimal value1 = randomizer.Next(1, 101);
    decimal value2 = randomizer.Next(1, 101);

    // 1 ~ 2 のランダムな数を生成する。1ならたし算、2ならひき算にする。
    int mondaiType = randomizer.Next(1, 3);
    decimal seikai;

    if (mondaiType == 1)
    {
        //たし算
        seikai = value1 + value2;
        Console.WriteLine($"{value1} + {value2} = ?");
    }
    else
    {
        //ひき算
        if (value1 < value2)
        {
            // value1 - value2 がマイナスにならないように
            // value の方が小さい場合は入れ替える。
            (value1, value2) = (value2, value1);
        }

        seikai = value1 - value2;
        Console.WriteLine($"{value1} - {value2} = ?");
    }
    
    string answer = Console.ReadLine()!;

    if (answer != "" && Convert.ToDecimal(answer) == seikai)
    {
        Console.WriteLine("正解!");
    }
    else
    {
        Console.WriteLine("はずれ");
        Console.WriteLine($"正解は {seikai} でした。");
    }
}

たし算になるかひき算になるかは mondaiType という変数で決定するようにしました。

mondaiTypeが1のときはたし算、2のときはひき算です。そして、mondaiTypeが1になるか2になるかををRandom.Nextメソッドを使ってランダムで決定するようにしています。

 

4-2.タプル

ひき算の答えはマイナスでも構わないのですが、今回は必ず答えがプラスになるようにプログラムしてみました。

value1 - value2 という問題なので value1 の方が小さいと答えがマイナスになってしまいます。

そこで、最初に value1 が value2 より小さいか確認する if 文を用意して、小さい場合 value1 と value2 を入れ替えるようにしました。

この部分です。

if (value1 < value2)
{
    // value1 - value2 がマイナスにならないように
    // value の方が小さい場合は入れ替える。
    (value1, value2) = (value2, value1);
}

2つ以上の変数や値をまとめて扱うには、このプログラムのように ( ) で囲んでカンマで区切ります。これをタプルと呼びます。なかなか便利です。

タプルを使うと複数の変数に異なる値を1行で代入することもできます。

int x, y;
string s;
(x, y, s) = (2, 5, "hello!");

Console.WriteLine(x); // 2
Console.WriteLine(y); // 5
Console.WriteLine(s); // hello!

次のようにタプルと変数の宣言を組み合わせることもできます。

(int x, int y, string s) = (2, 5, "hello!");

Console.WriteLine(x); // 2
Console.WriteLine(y); // 5
Console.WriteLine(s); // hello!

 

5.小数のかけ算の自動生成

5-1.小数のかけ算も出題するようにしてみる

RandomeクラスのNextDoubleメソッド(読み方:NextDouble=ネクストダブル)を使用するとランダムな小数を生成できます。

通常は小数が必要な場合これを使うと良いのですが、私たちのプログラムでは既に整数のたし算・ひき算がありますから、NextメソッドをNextDoubleメソッドに書き換えると、これらが小数のたし算・ひき算になってしまいます。

それにNextDoubleが生成するランダムな小数はやたらと桁数が多いです。

そこで、今回のプログラムではかけ算を出題するときはvalue1 または value2 または その両方を 10 で割ることで小数を作り出すことにします。

Console.WriteLine("数学問題集 バージョン 1.3");
Console.WriteLine("終了するには Ctrl + C を押してください。");

Random randomizer = new Random();

while (true)
{
    // 1 ~ 100 のランダムな数を value1, value2に代入
    decimal value1 = randomizer.Next(1, 101);
    decimal value2 = randomizer.Next(1, 101);

    // 1 ~ 3 のランダムな数を生成する。1ならたし算、2ならひき算、3ならかけ算にする。
    int mondaiType = randomizer.Next(1, 4); //←ここの変更を忘れずに
    decimal seikai;

    if (mondaiType == 1)
    {
        //たし算
        seikai = value1 + value2;
        Console.WriteLine($"{value1} + {value2} = ?");
    }
    else if (mondeiType == 2)
    {
        //ひき算
        if (value1 < value2)
        {
            // value1 - value2 がマイナスにならないように
            // value の方が小さい場合は入れ替える。
            (value1, value2) = (value2, value1);
        }

        seikai = value1 - value2;
        Console.WriteLine($"{value1} - {value2} = ?");
    }
    else
    {
        //小数のかけ算
        //1のとき、value1を小数にする。2のときvalue2を、3の時両方を小数にする。
        int saikoro = randomizer.Next(1, 4);

        if (saikoro == 1 || saikoro == 3)
        {
            value1 = value1 / 10;
        }

        if (saikoro == 2 || saikoro == 3)
        {
            value2 = value2 / 10;
        }

        seikai = value1 * value2;
        Console.WriteLine($"{value1} × {value2} = ?");
    }
    
    string answer = Console.ReadLine()!;

    if (answer != "" && Convert.ToDecimal(answer) == seikai)
    {
        Console.WriteLine("正解!");
    }
    else
    {
        Console.WriteLine("はずれ");
        Console.WriteLine($"正解は {seikai} でした。");
    }
}

mondaiTypeが3のとき小数のかけ算を出題するので、mondaiTypeを生成しているNextメソッドの引数を変更するのを忘れないように気を付けてください。

 

5-2.||

途中出てくる if 文は2つの条件を || 結合しています。

|| は、日本語で言うと「または」というような意味でどちらかの条件が成立していればOKという意味になります。

//小数のかけ算
//1のとき、value1を小数にする。2のときvalue2を、3の時両方を小数にする。
int saikoro = randomizer.Next(1, 4);

if (saikoro == 1 || saikoro == 3)
{
    value1 = value1 / 10;
}

if (saikoro == 2 || saikoro == 3)
{
    value2 = value2 / 10;
}

前述したように、今回のプログラムではかけ算を出題するときはvalue1 または value2 または その両方を 10 で割ることで小数を作り出すことにしたので、ランダムに value1 を 10で割ったり、value2 を 10で割ったりする必要があります。今回作りたい問題は小数のかけ算なのでvalue1とvalue2の片方が整数になるのは構いませんが、両方が整数にならないようにします。

そこで、ランダムに 1, 2, 3の整数を生成し、変数 saikoro に格納します。この値が 1 または 3 のときは value1 を 10 で割り、2 または 3 のときは value2 を 10 で割るということでこの仕様を実現しています。

条件を && で結合した時は両方が成立する場合のみ OK になるのに対し、 || で結合する場合は 片方が成立すればOKです。

 

今回、お題が「数学問題集」なのに作ったプログラムは算数の問題集になってしまいました。冒頭で紹介した累乗はぎりぎり中学校の範囲なので数学と言えるかもしれません。

いろいろな問題を出題するアプリの作り方はわかったと思うので、お好みなら数学の問題を出題するようにプログラムの追加にチャレンジしてみてください。

 

 

6.練習問題

問1.ランダムな数を生成する機能があるフレームワークのクラスはどれでしょうか?
Mathクラス
Randomクラス
Convertクラス
問2.Randomクラスの機能はインスタンスを作成してから呼び出す必要があります。インスタンスを作成するときに使うキーワードはどれでしょうか?
while
else
new
問3.if 文などで、2つの条件の両方が成立している場合にOKとするには、どれを使って条件を結合すればよいでしょうか?
&&
||
!=
問4.次のプログラムの結果 x と y の値は何になりますか?
			
(int x, int y) = (2, 5)

x = 2 で y = 5
x = 5 で y = 2
x = 0 で y = 0
問5.if 文などで、2つの条件の少なくとも片方が成立している場合にOKとするには、どれを使って条件を結合すればよいでしょうか?
&&
||
!=

 

 

次の回では、メソッドやプロパティの呼び出し方を掘り下げます。

C#初級講座講座 次の回へ