質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

4回答

2884閲覧

数値に重複のないビンゴカードをつくる。

退会済みユーザー

退会済みユーザー

総合スコア0

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2018/07/02 05:14

前提・実現したいこと

乱数を使ってビンゴカードの数値を得ます。そのとき、すべての数値は互いに異なる必要があります。

発生している問題・エラーメッセージ

Get_number関数で、すべての数値が互いに異なるとき、Get_number関数を再帰的に呼び出さずに
Get_number関数を抜けるはずが、実際は抜けてくれないです。printf("********\n")によって
昇順ソート済みの重複のない配列を見やすくしています。
また今回の質問には関係ないことですが、Get_number関数ないで新たに配列subを作ったのは、
乱数なのに昇順ソートされて出てくるのが気持ち悪いからです。

###該当のソースコード

#include <stdio.h> #include <time.h> #include <stdlib.h> #define NUM 9 #define WIDTH 3 void Get_number(int p[]); void Make_card(int p1[], int p2[][WIDTH]); void Print_card(int p3[][WIDTH]); int main(void) { int num1[NUM] = {0}; int num2[WIDTH][WIDTH] = {}; //ビンゴカードの作成**********// srand((unsigned)time(NULL)); Get_number(num1); printf("PRINTDEBUG\n"); Make_card(num1, num2); Print_card(num2); return 0; } void Get_number(int p[]) { int i,j; int sub[NUM] = {0}; int buffer=0; for(i=0; i<NUM; i++) {p[i]=rand()%20+1;} for(i=0; i<NUM; i++) {sub[i]=p[i];} //昇順にソートして全て異なる数字かを判定する for(i=0; i<NUM-1; i++) { for(j=i+1; j<NUM; j++) { if(sub[i]>sub[j]) {buffer=sub[i]; sub[i]=sub[j]; sub[j]=buffer;} } } for(i=0; i<NUM; i++) {printf("%d ", sub[i]);} printf("\n"); for(i=0; i<NUM-1; i++) { if(sub[i]==sub[i+1]) {Get_number(p);} } //全ての整数が異なるときにGet_number関数を抜けて //くれないことをわかりやすくするために書いてます。 printf("******\n"); return; } void Make_card(int p1[], int p2[][WIDTH]) { int i,j; int k=0; for(i=0; i<WIDTH; i++) { for(j=0; j<WIDTH; j++) { p2[i][j]=p1[k]; k++; } } return; } void Print_card(int p3[][WIDTH]) { int i,j; for(i=0; i<WIDTH; i++) { for(j=0; j<WIDTH; j++) { printf("%d\t", p3[i][j]); } printf("\n"); } return; }

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

すべての数値が互いに異なるとき、Get_number関数を再帰的に呼び出さずにGet_number関数を抜けるはず

この部分に偽りはないのですが、

c

1for(i=0; i<NUM; i++) {p[i]=rand()%20+1;}

ランダムに作った場合、理論的には (20!/20^20) = 1.16e-9 くらいの果てしなく少ない確率でしか起こりません。
単純に、すでリストにある番号だったら除外して再度ランダムな値をとるようにしてリストを作ったほうが早いと思いますよ。

投稿2018/07/02 05:25

mather

総合スコア6753

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mather

2018/07/02 05:50

訂正。N=9なので、(20!/(20-9)!)/20^9 = 0.065 くらいですね。まぁ、何度も繰り返したら起こりうるくらいだとは思いますが、再帰呼び出しがStackOverflowしないか気になります。
退会済みユーザー

退会済みユーザー

2018/07/02 07:45

回答ありがとうございます。あまり考えてませんでしたが中々に低い確率だったので違う書き方を試してみます...ありがとうございました。
guest

0

通常はBINGOの各文字について5-5-4-5-5抽選すればいいだけです
(Nは最初から真ん中を抜くのが基本だから)
配列を用意してシャッフルして頭から上記数量抽出すればいいでしょう

投稿2018/07/02 05:18

yambejp

総合スコア114572

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

まず乱数を必要個数生成したから、重複の有無を調査して、重複があれば再生成というアルゴリズムですね。これだと平均5~10回(ちゃんと計算していません)は生成しなおさないと重複のない数字列が得られません。

パッと見て間違ってるのはget_numberを再帰呼び出ししたあとも重複チェックを続けてるところです。なので2か所で重複がある場合無意味にもう一度Get_numberを呼び出してしまいます。

C

1 if(sub[i]==sub[i+1]) {Get_number(p);} 2

ここはbreakを入れてループを抜けないといけません。

if(sub[i]==sub[i+1]) {Get_number(p);break;}

ただしこれは効率が悪いだけで番号が生成できないわけではないですね。私のところで上のソースをコンパイルしたら出力はちゃんと出ました。乱数の巡り合わせが悪いと再起呼び出しの深さが深くなりすぎてエラーで止まります(無限にスタックがあればいつかは止まるはず)。

生成した数値に重複が2組(あるいは同じ数字が3回以上)あれば、余分な再帰が起きます。そしてその再帰でまた生成した数値に重複が2組あるとさらに無駄な再帰が・・・ということ。

あるいは、再帰ではなくてやり直しループで書くのも手です。できるだけ今のコードを生かすならば、こんなかんじ。

for(;;){
for(i=0; i<NUM; i++) {p[i]=rand()%20+1;}
for(i=0; i<NUM; i++) {sub[i]=p[i];}
//昇順にソートして全て異なる数字かを判定する
for(i=0; i<NUM-1; i++) {
for(j=i+1; j<NUM; j++) {
if(sub[i]>sub[j]) {buffer=sub[i]; sub[i]=sub[j]; sub[j]=buffer;}
}
}
for(i=0; i<NUM; i++) {printf("%d ", sub[i]);}
printf("\n");
for(i=0; i<NUM-1; i++) {
if(sub[i]==sub[i+1])break;
}
//全ての整数が異なるとき
if(i==NUM-1) return;
}

あと、subだけ並び替えてpをそのまま返していますが、通常のビンゴカードは小さい順に数が並び替えられていませんか?

なお、
1~Nまでの数からランダムにM個(重複しないで)選ぶよくある手段は次のようなものです。

C

1int x[N]2int i,j; 3//1~Nの数を配列に入れる 4for(i=0;i<N;i++) 5  x[i]=i+1; 6 7for(i=0;i<M;i++) { 8 j= iからN-1までの整数が出てくる一様乱数(); 9 x[i]とx[j]を入れ替える; 10} 11x[0]~x[M-1]をソートする;

投稿2018/07/02 05:56

a_saitoh

総合スコア702

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/07/02 07:42

回答ありがとうございます。再起慣れしてないからというのもあるかもしれませんが、1度再起されたらそのfor文は抜けるものと勘違いしてたようです。(break書かないとですね)最後に紹介していただいたのがよくわからないのでもう少し考えてみます。ありがとうございました。
a_saitoh

2018/07/03 07:07

1~20のカードが1枚づつあるとき、カードを一つ選んで束から抜く、ということ9回繰り返せば、重複無く1~20の中から数を9個選べます。これを一つの配列上でやっているわけです。0~i-1が選択済みのカード、i~N-1がこれからランダムに選ぶ候補カード。
guest

0

ちゃんと抜けて、終了しますよ。

重複無くなるまでひたすら再帰呼び出しというのがいまいちなので、ループにしましょう。

投稿2018/07/02 05:24

otn

総合スコア84423

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

otn

2018/07/02 05:42 編集

ああ、何度か実行するといつまでも続くことが多いですね。 if(sub[i]==sub[i+1]) {Get_number(p);break;} でしょう。
otn

2018/07/02 05:33

他の回答にもありますが、そもそも効率の悪い方法です。 × 数を全部作ってから1組でも一致すれば全部やり直し ○ 数を作る度に一致を調べ、一致すればそれだけやり直し
otn

2018/07/02 05:40

最初、2回実行して、2回ともそう長くならずに終了したのは、確率的にレアだったのかな。
退会済みユーザー

退会済みユーザー

2018/07/02 07:31

回答ありがとうございます。break抜けは全く気づきませんでした; ; 。```for(i=0; i<NUM; i++) {p[i]=rand()%20+1;}```のところを重複がないかを確かめながら配列に入れていったほうが...ということですよね。それで作ってみます。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問