C# 宝くじプログラムの作り方|5桁入力と乱数当選番号の比較・一致数の数え方

C#で5桁の宝くじ課題を作ると、ユーザー入力と乱数で作った当選番号を比較しても一致数が期待通りにならないことがあります。原因は入力の取り方(Console.Read/ReadLine)や5桁の保存方法、Randomの使い方にあるケースが大半です。この記事では、桁一致・数字一致の両パターンで正しく数える実装を具体例で解説します。

目次

5桁の宝くじ比較がうまくいかない「よくある原因」

まず、つまずきポイントを整理します。ここを押さえるだけで、ほとんどの「一致しない問題」は解消します。

つまずきポイント起きること解決の方向性
Console.Read() を使っている1文字ずつしか取れない/戻り値が文字コードで扱いづらいConsole.ReadLine() で「5桁まとめて」文字列として受け取る
ループ内で num = … と上書きしている5桁を保存したつもりでも、最後の1桁しか残らない文字列のまま保持するか、配列(int[5])に入れて保持する
当選番号も「1つの数値」だけで持っている桁ごとの比較や、数字の比較が面倒になりやすい当選番号も5桁として保持(string / 配列)して比較する
Random をループ内で new している短時間に生成すると似た値が出る(同じような当選番号が出る)Random は外で1回だけ new し、使い回す
int に変換してしまい先頭0が消える「01234」が「1234」扱いになり、桁ズレや比較ミスが起きる5桁は文字列で扱う(先頭0も保持できる)

結論:5桁は「文字列」か「配列」で持つのが一番安全

宝くじのように「桁」や「数字」を扱う課題では、5桁を整数として持つより、文字列(string)として持つ方がトラブルが少ないです。

  • 先頭0をそのまま保持できる(例:00042)
  • 1桁ずつ取り出して比較しやすい(input[0]〜input[4])
  • 入力チェック(桁数・数字のみ)が簡単

Console.Read() と Console.ReadLine() の違い

「比較できない」系で最も多い原因がここです。

メソッド取得できるもの注意点この課題に向く?
Console.Read()1文字(戻り値は int:文字コード)数字として使うには変換が必要/ループと相性が悪い向かない
Console.ReadLine()1行(文字列)桁数・数字判定を入れると安全向く

ユーザー入力:5桁ちょうど&数字のみを安全に受け取る

課題で高評価になりやすいのは、「入力が不正なときに弾ける」実装です。たとえば、以下のようなチェックを入れます。

  • 長さが5である
  • 全てが数字(0〜9)である

入力チェック付きの関数(推奨)

static string ReadFiveDigits()
{
    while (true)
    {
        Console.Write("5桁の数字を入力してください: ");
        string? input = Console.ReadLine();


    if (string.IsNullOrEmpty(input))
    {
        Console.WriteLine("入力が空です。");
        continue;
    }

    if (input.Length != 5)
    {
        Console.WriteLine("5桁ちょうどで入力してください。");
        continue;
    }

    bool allDigits = true;
    for (int i = 0; i < input.Length; i++)
    {
        if (!char.IsDigit(input[i]))
        {
            allDigits = false;
            break;
        }
    }

    if (!allDigits)
    {
        Console.WriteLine("数字(0〜9)のみで入力してください。");
        continue;
    }

    return input; // ここで「5桁の文字列」として確定
}


}

この関数のポイントは、intに変換していないことです。これにより「01234」のような入力でも桁が崩れません。

配列(int[5])に変換したい場合

「配列で持ちなさい」という指定がある課題もあります。その場合は、文字列から1桁ずつ変換します。

static int[] ToDigitArray(string fiveDigits)
{
    int[] digits = new int[5];
    for (int i = 0; i < 5; i++)
    {
        digits[i] = fiveDigits[i] - '0'; // '0' を引くと 0〜9 の int になる
    }
    return digits;
}

当選番号:Randomで5桁を作って保持する

当選番号も同じく「5桁」として持つのがコツです。特にRandomの生成位置が重要です。

Random は「外で1回だけ」new して使い回す

短い時間に何度も new Random() すると、環境によっては似た値になりやすく、当選番号が偏ったように見えることがあります。基本は次の形にします。

Random rng = new Random(); // ここで1回だけ作る

当選番号を作る方法は2パターン

方法例メリット注意点
各桁を 0〜9 で生成Next(0,10) を5回桁が確実に5つ/比較が楽重複が普通に起こる(課題要件次第)
0〜99999 を生成して5桁に整形Next(0,100000) を D5コードが短い整形しないと先頭0が落ちる

おすすめ:各桁を生成して string にする

static string GenerateFiveDigits(Random rng)
{
    char[] buf = new char[5];
    for (int i = 0; i < 5; i++)
    {
        int d = rng.Next(0, 10);      // 0〜9
        buf[i] = (char)('0' + d);     // 文字にする
    }
    return new string(buf);
}

短く書く:0〜99999 を作って D5 で5桁固定

static string GenerateFiveDigitsShort(Random rng)
{
    int n = rng.Next(0, 100000); // 0〜99999
    return n.ToString("D5");     // 00000〜99999 に整形
}

「一致」の定義を決める:桁一致か、数字一致か

比較が期待通りにならないもう一つの原因は、一致の定義が曖昧なことです。課題文で何を求められているかを確認し、次のどちらか(または両方)を実装しましょう。

一致の種類何を数える?例(ユーザー: 12345 / 当選: 10325)実装の難易度
桁位置まで一致(位置一致)同じ位置の数字が同じなら1カウント1桁目:1一致、2桁目:2≠0、3桁目:3一致、4桁目:4≠2、5桁目:5≠5? → この例なら 2(1と3)簡単
位置は無視して数字が含まれていれば一致(含有一致)数字が当選番号のどこかにあればカウント1,2,3,5 が含まれる → 4(ただし重複の扱いが必要)要件次第

「一致した桁数」と書かれていれば位置一致の可能性が高いです。「一致した数字の数」や「含まれている数字」なら含有一致の可能性があります。

比較方法A:桁位置まで一致(位置一致)を数える

位置一致はシンプルです。5桁なので、0〜4を回して比較します。

static int CountPositionalMatches(string user, string win)
{
    int match = 0;
    for (int i = 0; i < 5; i++)
    {
        if (user[i] == win[i])
        {
            match++;
        }
    }
    return match;
}

配列(int[5])で持っている場合も同じ考え方で、userDigits[i] == winDigits[i] を比較するだけです。

比較方法B:位置は無視して数字一致(含有一致)を数える

ここが少し注意ポイントです。ありがちな実装として、次のような書き方を見かけます。

// 注意:重複の扱いが要件とズレる可能性あり
static int CountDigitMatchesLoose(string user, string win)
{
    int match = 0;
    for (int i = 0; i < 5; i++)
    {
        if (win.Contains(user[i]))
        {
            match++;
        }
    }
    return match;
}

この方法は「含まれているか」の判定としては便利ですが、重複があると数え方がズレることがあります。

重複の例:ルールを決めないと不公平に見える

たとえば、ユーザーが「11111」、当選が「12345」なら、Contains を使うと 5 回とも true になり、5一致になってしまいます。しかし「当選側に1が1個しかないなら一致は1個」と数える課題も多いです。

そこで、重複も正しく扱える「頻度(カウント)方式」を紹介します。これは数字0〜9の出現回数を数えて、両方にある分だけ(最小値)を足す方法です。

重複を正しく扱う:頻度配列で一致数を数える(推奨)

static int CountDigitMatches(string user, string win)
{
    // 0〜9 の出現回数を数える
    int[] u = new int[10];
    int[] w = new int[10];


for (int i = 0; i < 5; i++)
{
    u[user[i] - '0']++;
    w[win[i] - '0']++;
}

// 両方に存在する分だけ足す
int match = 0;
for (int d = 0; d <= 9; d++)
{
    match += Math.Min(u[d], w[d]);
}

return match;


}

この方法なら、重複の扱いが明確で、結果に納得感が出ます。課題要件が「重複あり」でも「重複なし」でも、比較ロジックとしては安定します。

完成形のサンプル:入力→当選生成→比較→結果表示

ここまでの部品を組み合わせた、コンソールアプリの例です。課題提出用に、読みやすさを優先して関数に分けています。

using System;

class Program
{
    static void Main()
    {
        // 入力
        string user = ReadFiveDigits();

        // 当選番号生成
        Random rng = new Random();
        string win = GenerateFiveDigits(rng);

        Console.WriteLine();
        Console.WriteLine($"あなたの番号: {user}");
        Console.WriteLine($"当選番号    : {win}");

        // 比較(位置一致 / 数字一致)
        int positional = CountPositionalMatches(user, win);
        int digitMatch = CountDigitMatches(user, win);

        Console.WriteLine();
        Console.WriteLine($"位置一致(同じ桁が同じ): {positional}");
        Console.WriteLine($"数字一致(位置無視・重複考慮): {digitMatch}");

        // 例:位置一致で景品を決める(課題要件に合わせて変更)
        Console.WriteLine();
        Console.WriteLine(GetPrizeMessage(positional));
    }

    static string ReadFiveDigits()
    {
        while (true)
        {
            Console.Write("5桁の数字を入力してください: ");
            string? input = Console.ReadLine();

            if (string.IsNullOrEmpty(input))
            {
                Console.WriteLine("入力が空です。");
                continue;
            }

            if (input.Length != 5)
            {
                Console.WriteLine("5桁ちょうどで入力してください。");
                continue;
            }

            for (int i = 0; i < 5; i++)
            {
                if (!char.IsDigit(input[i]))
                {
                    Console.WriteLine("数字(0〜9)のみで入力してください。");
                    input = null;
                    break;
                }
            }

            if (input == null) continue;
            return input;
        }
    }

    static string GenerateFiveDigits(Random rng)
    {
        char[] buf = new char[5];
        for (int i = 0; i < 5; i++)
        {
            int d = rng.Next(0, 10);
            buf[i] = (char)('0' + d);
        }
        return new string(buf);
    }

    static int CountPositionalMatches(string user, string win)
    {
        int match = 0;
        for (int i = 0; i < 5; i++)
        {
            if (user[i] == win[i]) match++;
        }
        return match;
    }

    static int CountDigitMatches(string user, string win)
    {
        int[] u = new int[10];
        int[] w = new int[10];

        for (int i = 0; i < 5; i++)
        {
            u[user[i] - '0']++;
            w[win[i] - '0']++;
        }

        int match = 0;
        for (int d = 0; d <= 9; d++)
        {
            match += Math.Min(u[d], w[d]);
        }
        return match;
    }

    static string GetPrizeMessage(int positionalMatch)
    {
        // if/else を並べるより switch の方が条件ミスが減ります
        switch (positionalMatch)
        {
            case 5: return "おめでとう!1等(5桁完全一致)";
            case 4: return "2等(4桁一致)";
            case 3: return "3等(3桁一致)";
            case 2: return "4等(2桁一致)";
            case 1: return "参加賞(1桁一致)";
            default: return "残念!一致なし";
        }
    }
}

課題要件でよくある追加ルール:重複禁止にする場合

学校課題では「同じ数字を複数回使わない(重複禁止)」が条件に入ることがあります。ユーザー入力と当選番号の両方で重複を禁止したいなら、HashSet を使うと分かりやすいです。

入力側:重複禁止チェックを追加

static bool HasDuplicateDigits(string fiveDigits)
{
    var set = new System.Collections.Generic.HashSet<char>();
    for (int i = 0; i < 5; i++)
    {
        if (!set.Add(fiveDigits[i]))
        {
            return true; // 追加できない=重複あり
        }
    }
    return false;
}

ReadFiveDigits() の最後に次のチェックを足せます。

if (HasDuplicateDigits(input))
{
    Console.WriteLine("同じ数字の重複は不可です。別の5桁を入力してください。");
    continue;
}

当選側:重複なしの5桁を生成する

static string GenerateFiveUniqueDigits(Random rng)
{
    var set = new System.Collections.Generic.HashSet<char>();
    char[] buf = new char[5];
    int idx = 0;


while (idx < 5)
{
    char c = (char)('0' + rng.Next(0, 10));
    if (set.Add(c))
    {
        buf[idx] = c;
        idx++;
    }
}

return new string(buf);


}

重複禁止のときは、数字一致(位置無視)を数えるロジックがさらにシンプルに感じられますが、課題の採点では「要件を守れているか」が最重要なので、まずは要件に合わせるのが正解です。

一致数が合わないときのデバッグのコツ

「理屈は分かったのに、なぜか結果が違う」というときは、次の順番で確認すると原因が見つかりやすいです。

  • 入力と当選番号を必ず表示する(比較対象が見えないと判断できない)
  • 比較している型を確認する(string同士か、int同士か、char同士か)
  • 5桁が本当に保持されているか(最後の1桁だけになっていないか)
  • 先頭0が消えていないか(int化していないか、ToString(“D5”)しているか)

特に「Console.Read() の戻り値をそのまま int に入れている」場合、入力が「5」でも内部では 53(’5’の文字コード)になり、比較が成立しません。数字として扱いたいなら、文字から数値への変換が必要です。

条件分岐の書き方ミスを避ける:else if と || の基本

一致数に応じたメッセージを出す部分で、条件式のミスが起きがちです。ありがちなミスと安全な書き方をまとめます。

よくあるミスなぜダメ?おすすめの書き方
if を連続で書いてしまう複数の条件に同時に入って、想定外の表示になるelse if / switch を使う
Match == 2; Match == 3 のように区切る条件式になっていない(文が分かれてしまう)Match == 2 || Match == 3
Match < 0 など不自然な範囲チェックを多用本来起きない状況を疑う前に、入力と比較ロジックを見直すべき入力検証を強くして、比較はシンプルに

提出前チェックリスト:この5つができていれば強い

  • ユーザー入力を ReadLine() で受け取り、長さ5・数字のみを検証している
  • 当選番号は Randomを1回だけ生成して、5桁を保持している
  • 比較は「位置一致」か「数字一致」か、要件に合わせて定義している
  • 数字一致は、必要なら重複を考慮して数える(頻度配列が安全)
  • 結果の判定は switch などで整理し、条件式ミスを減らしている

宝くじ課題は、見た目は簡単でも「入力」「保存」「比較」「要件(重複や一致の定義)」が絡むため、つまずくポイントが多い題材です。逆に言えば、ここで文字列/配列で桁を扱う感覚を身につけると、暗号、パスコード、ナンバープレート、ゲームの当たり判定など、幅広いプログラムで応用できます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次