【C言語】乱数の出現確率を操作するためには”分岐”を使おう

C言語

ようこそ、KiRiOのブログへ!

本記事では「新・明解C言語中級編」の自由課題について解説します。


自由課題のとおり、この参考書には解答が載っておりません。
そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にしてみてください。

今回解説する課題は「演習1-2」で、課題内容は以下のとおりです。

演習1-2前問の「演習1-1」で作成したプログラムを、出る運勢が均等とならないように改良したプログラムを作成せよ(たとえば〔末吉〕〔凶〕〔大凶〕を出にくくするとよい。

「演習1-1」は「生成した乱数をもとに《おみくじ》プログラムを作成する」というものです。簡単にまとめますと、

  1. 0〜6の乱数を生成
  2. 生成した乱数の値に応じて占い結果を表示

というものです。詳細につきましては、下記ブログを参照してください。

【C言語】乱数を生成するためには”種”が必要
ようこそ、KiRiOのブログへ! 本記事では、「新・明解C言語中級編」の自由課題について解説します。 自由課題のとおり、この参考書には解答が載っておりません。 そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にして...

今回の課題内容を整理しますと、以下のようになります。

  1. 乱数を非一様に生成する

それでは、上記の処理を実行するために、まずは設計から行っていきます。

設計

乱数を非一様に生成する

前問で行った乱数の生成は、生成範囲を指定していただけで、その出現確率までは指定していませんでした。
しかし今回は、〔末吉〕〔凶〕〔大凶〕が出にくくなるように乱数を生成しなければなりません。

前問では、0〜6の乱数を生成して、以下のように占い結果を割り当てていました。

乱数 0 1 2 3 4 5 6
占い結果 大吉 中吉 小吉 末吉 大凶

すなわち、4〜6の乱数を生成されにくくする必要があります。

乱数の出現確率を操作するためには、分岐を使うというのが方法の一つです。
前問のプログラムを改良しますので、rand()にて0〜6の乱数を生成した後で、生成した乱数の値により分岐を行い、2つの処理を実行するようにします。
そして、それぞれの分岐先で「〔大吉〕〔中吉〕〔小吉〕〔吉〕のいずれかを表示する」「〔末吉〕〔凶〕〔大凶〕のいずれかを表示する」処理を行います。

ここでポイントとなるのが、分岐点となる生成乱数の閾値を「乱数範囲の中央値から遠ざける」という点です。

今回は、生成乱数の範囲である0〜6の中で閾値を指定して分岐させ、なおかつ〔末吉〕〔凶〕〔大凶〕が出にくくなるように設計します。
すなわち、閾値を「3」のように範囲の中央値(近辺)に設定してしまうと、分岐後の処理が実行される確率に偏りが生じにくくなってしまいます。

そのため、例えば「”0″と”1〜6″」や「”0〜5″と”6″」のように、分岐点を範囲の中央値から遠ざけたほうが、分岐後の処理実行確率に偏りが生じやすくなります(分岐点の設定や生成する乱数の範囲は自由に設定して構いません。乱数範囲を広げて分岐点を最小値・最大値に近づけるほど、確率の偏りが大きくなります。本問では、前問の改良ということで、簡単のため0〜6の中で「”0″と”1〜6″」または「”0〜5″と”6″」というふうに設計しています)。

今回は、以下のように設計することにします。

  • 0〜5が生成されたら〔大吉〕〔中吉〕〔小吉〕〔吉〕のいずれかを表示する
  • 6が生成されたら〔末吉〕〔凶〕〔大凶〕のいずれかを表示する

分岐後のそれぞれの処理については、容易に想像がつくと思いますので、以下に簡潔にまとめます。

  • 0〜5が生成されたら0〜3を生成して対応する占い結果を表示
  • 6が生成されたら4〜6を生成して対応する占い結果を表示

以上で設計は完了となります。

実装

それでは設計内容をもとに、以下の順番に沿って実装していきます。

  1. 0〜5が生成されたら0〜3の乱数を生成し、対応する占い結果を表示
  2. 6が生成されたら4〜6の乱数を生成し、対応する占い結果を表示

0〜5が生成されたら0〜3の乱数を生成し、対応する占い結果を表示

設計の部分で述べたように、分岐は乱数生成後で行い、if文を使います。
本問では、「0〜6の範囲で0〜5のいずれかが生成された場合」ですので、「6以外が生成された場合」と同意となります。

また、範囲を指定して乱数を生成する場合はrand()を用いて以下のように記述します。

a 以上 (a + b) 以下の乱数を生成a + rand() % (b + 1);

今回の場合では0〜3の乱数を生成しますので「rand() % 4;」となります。

占い結果の表示は前問のswitch文を流用しますので、ここでの説明は省略します。
これらを実装したものが下記プログラムです(関連部分のみ記述)。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	omikuji = rand() % 7;

	if (omikuji != 6)
        {
        	omikuji = rand() % 4;

        	switch (omikuji)
        	{
        	case 0: puts("\n【大吉】でした。"); break;
        	case 1: puts("\n【中吉】でした。"); break;
        	case 2: puts("\n【小吉】でした。"); break;
        	case 3: puts("\n【吉】でした。"); break;
        	default: break;
        	}
        }	

	return 0;
}

6が生成されたら4〜6の乱数を生成し、対応する占い結果を表示

実装内容としましては、上記とほとんど同じとなります。

分岐の条件は「0〜6の範囲で6が生成された場合」ですので、条件式は必要なくelse文で十分です。

また、分岐後に生成する乱数の範囲は4〜6ですので「4 + rand() % 3;」となります。

これらを実装したものが下記プログラムです(関連部分のみ記述)。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	omikuji = rand() % 7;

	else
        {
        	omikuji = 4 + rand() % 3;

        	switch (omikuji)
        	{
        	case 4: puts("\n【末吉】でした。"); break;
        	case 5: puts("\n【凶】でした。"); break;
        	case 6: puts("\n【大凶】でした。"); break;
        	default: break;
        	}
        }	

	return 0;
}

最後に、前問のプログラムと本問の改良部分を合併したものが下記プログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	int omikuji;
	int try, retry;

	srand(time(NULL));

	do
	{
		printf("おみくじを引きますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

		if (try == 0)
		{
			return 0;
		}
	} while (try != 0 && try != 1);

	do
	{
		omikuji = rand() % 7;

		if (omikuji != 6)
		{
			omikuji = rand() % 4;

			switch (omikuji)
			{
				case 0: puts("\n【大吉】でした。"); break;
				case 1: puts("\n【中吉】でした。"); break;
				case 2: puts("\n【小吉】でした。"); break;
				case 3: puts("\n【吉】でした。"); break;
				default: break;
			}
		}
		else
		{
			omikuji = 4 + rand() % 3;

			switch (omikuji)
			{
				case 4: puts("\n【末吉】でした。"); break;
				case 5: puts("\n【凶】でした。"); break;
				case 6: puts("\n【大凶】でした。"); break;
				default: break;
			}
		}

		printf("もう一度引きますか?【はい…1/いいえ…0】:");
		scanf("%d", &retry);
		} while (retry == 1);

		puts("\nおみくじを終了します。");
		return 0;
	}

以上で完了となります。
プログラムを実行して、出る運勢が不均等となっているか確認してみてください。

まとめ

それでは、本記事で説明した内容を以下にまとめます。

  1. 乱数の出現確率を操作するためには分岐を使う
  2. 生成乱数を不均等にするためには、分岐点を生成範囲の中央値から遠ざける

ここまで、記事を読んでいただき、ありがとうございました。

最後に、今回取り上げた課題が記載されている本を紹介します。

今回扱った課題以外にも、様々なプログラム開発について記載されているため、この記事とあわせて読んでいただくと、より理解が深まると思います。


また、参考として以下の本もご紹介しておきます。

こちらもオススメです。2021年12月14日に最新版が発売されました!

それでは、また別の記事でお会いしましょう!

コメント

タイトルとURLをコピーしました