複数抽選アルゴリズムについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら

トピックに返信する


答えを正確にご入力ください。答えられるかどうかでスパムボットか否かを判定します。

BBCode: ON
[img]: ON
[flash]: OFF
[url]: ON
スマイリー: OFF

トピックのレビュー
   

展開ビュー トピックのレビュー: 複数抽選アルゴリズムについて

Re: 複数抽選アルゴリズムについて

#3

by にほ » 6年前

mannteraさん、ありがとうございます!!

ソースコードの76~92の部分をFisher Yates法に書き換えたら、今のところ当選者の被りはなく、無事期待通りの実行結果を得ることができました!

私ももっとアルゴリズムについて勉強しないといけなさそうですね…(汗
教えていただきありがとうございます!!

Re: 複数抽選アルゴリズムについて

#2

by manntera » 6年前

被らないランダム抽選であれば、「Fisher-Yates」でググると良い記事が出ると思いますよ。
ただ、ちょっと気になったのは作りたい物に対してかなり大規模な処理になっちゃった所ですかね。
だから、こういったバグにすぐ対応出来なくなっちゃった感じですかね。

もっと簡単なロジックで出来ちゃうので紹介しておきます。
以下のソースコードはアルゴリズムだけの紹介なので、
実装は自分でやってみてくださいね。

コード:

            候補者の名前を登録する配列を作成
            名前配列 = new string[人数分];
            名前配列に 候補者数分の名前を入力
            「Fisher-Yates」で名前配列をシャッフル

            //当選回数表示
            int 一人が最低限当選する回数 = 当選する全体の回数 / 候補者;
            int 一回多く当選する人の人数= 当選する全体の回数 % 候補者;

            foreach (int i=ほにゃららで候補者全員分回すで)
            {
                if(i< 一回多く当選する人の人数)
                {
                    Console.WriteLine(名前配列のi番目の人+ "は"+ (一人が最低限当選する回数+1)+"回当選しました。");
                }
                else
                {
                    Console.WriteLine(名前配列のi番目の人+ "は"+ (一人が最低限当選する回数)+"回当選しました。");
                }
            }


こんな感じに短いコードで書けちゃいます

複数抽選アルゴリズムについて

#1

by にほ » 6年前

はじめまして。今回プログラムに関してどうしてもわからないことがあったため、初めて質問させていただきます。

現在自分が制作しているのは、言語はC#で、一定数の人数の中から、当選回数が同じになるよう調整して任意の数抽選するプログラムです。登録した名前にはカウンタがつけられ、当選するごとにカウントを増やし、少ない人から優先して選ぶものとなっています。以下に自分が制作したコードを記載します。

コード:

using System;
using System.Drawing;
using System.Windows.Forms;

partial class MyButton : Button
{
	Chusen[] lot = new Chusen[maxPlayer];
	int countToSelect = 0;
	decimal nokori;
	int max;
	int[] winner = new int[maxPlayer];
	int lotMax = 0;
	Random r = new Random();
	
	void OnClicked(object sender, EventArgs e)
	{
		nokori = (int)dbtn.btn.spin.Value;
		max = (int)nokori;
		countToSelect = 0;
		
		// 抽選可能な対象を管理する一次抽選インスタンス初期化
		for(int i = 0; i < maxPlayer; i++)
		{
			lot[i] = new Chusen();
		}
		
		for(int i = 0; i < maxPlayer; i++)
		{
			winner[i] = -1;
		}
		
		// 抽選可能な人数残っていたら繰り返す
		while(nokori > 0)
		{
			for(int i = 0; i < maxPlayer; i++)
			{
				lot[i].reset();
			}
			
			lotMax = 0;
			// 現在抽選可能人数が0だったら
			while(lotMax == 0)
			{
				// すべてのプレイヤーに対して調査
				// フラグがオン、非参加モードでない、カウントが最小の物だったら
				for(int i = 0; i < maxPlayer; i++)
				{
					if(dbtn.btn.p[i].getFlag() && dbtn.btn.p[i].join && dbtn.btn.p[i].selected && (dbtn.btn.p[i].count == countToSelect))
					{
						int k;
						if((k = search_Lot()) != -1)
						{
							// 一次抽選インスタンスにID登録
							dbtn.btn.p[i].selected = false;
							lot[k].setId(i);
							lotMax++;
						}
					}
				}
				// ここまで登録されなかったら、カウントを増やしてもう一回
				countToSelect++;
			}
			
			// 残り人数よりも一次抽選人数が少なかったら全員強制当選
			if(lotMax <= nokori)
			{
				for(int i = 0; i < lotMax; i++)
				{
					int k = search_winner();
					winner[k] = lot[i].getId();
					nokori--;
					
				}
			}
			// そうでなければランダムに選んで当選させる
			else
			{
				for(int i = 0; i < nokori; i++)
				{
					// dbtn.btn.p[i].selected
					
					int k = search_winner();
					winner[k] = r.Next(lotMax - i);
					for(int j = winner[k]; j < lotMax - 1; j++)
					{
						lot[j].setId(lot[j + 1].getId());
					}
					lot[lotMax - 1].reset();
					lotMax--;
				}
				nokori = 0;
			}
			lotMax = 0;
		}
		
		// 当選者の表示など
		string str = "当選者は";
		for(int i = 0; i < maxPlayer; i++)
		{
			if(winner[i] != -1)
			{
				dbtn.btn.p[winner[i]].count++;
				str = str + dbtn.btn.p[winner[i]].getName() + ", ";
			}
		}
		str = str + "です。";
		DialogResult result = MessageBox.Show(str, "当選者確定", MessageBoxButtons.OKCancel);
		if (result == DialogResult.OK)
		{
			Clipboard.SetText(str);
		}
		else
		{
			for(int i = 0; i < maxPlayer; i++)
			{
				if(winner[i] != -1)
				{
					dbtn.btn.p[winner[i]].count--;
				}
			}
		}
		// すべて終わったら全員選ばれてないことにする
		for(int i = 0; i < maxPlayer; i++)
		{
			lot[i].reset();
			dbtn.btn.p[i].selected = true;
		}
	}
	
        public int search_winner()
	{
		for(int i = 0; i < maxPlayer; i++)
		{
			if(winner[i] == -1)
			{
				return i;
			}
		}
		return -1;
	}
	
	public int search_Lot()
	{
		for(int i = 0; i < maxPlayer; i++)
		{
			if(lot[i].getId() == -1)
			{
				return i;
			}
		}
		return -1;
	}
}

// 一次抽選に当選した人を管理するクラス
class Chusen
{
	int id;
	
	public Chusen()
	{
		id = -1;
	}
	
	public void reset()
	{
		id = -1;
	}
	
	public void setId(int a)
	{
		id = a;
	}
	
	public int getId()
	{
		return id;
	}
}
このコードを実行すると、配列の範囲外参照などのランタイムエラーは起こらないのですが、一度の抽選で同じ人が二度以上選ばれてしまうという論理エラーが発生します。例えば、参加者が{ a, b, c, d, e, f, g, h }、抽選人数が4人の時、当選者が{ c, d, d, a }のようになってしまいます。このプログラムは人に対して抽選を行うため、このような複数回当選してしまうことはあり得ないようにしなければいけません。このようなエラーが起こってしまうのはなぜでしょうか。また、このエラーを回避する方法があれば、教えていただけるとありがたいです。

現在の開発環境は、Windows10、Microsoft.NETのFramework64、v4.0.30319です。エディタはTeraPad、コマンドプロンプトからcsc.exeを実行してコンパイルしています。
現在C#は勉強したてで、コードの書き方など分からないことは多いですが、C言語でのプログラミング歴があるのである程度のソースコードなら理解することができます。

長くなってしまいましたが、ご教授いただけると幸いです。どうかよろしくお願いします。

ページトップ