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

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

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

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

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Q&A

解決済

5回答

2228閲覧

重複しない3桁の数字を生成するための最適案を知りたい。

kihochi

総合スコア2

C

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

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

0グッド

1クリップ

投稿2024/03/14 02:57

編集2024/03/14 04:05

実現したいこと

乱数で重複しない3桁の数字を生成する。

  • 自分で書いたコードで一応実現できましたが、テキストの模範解答とかなり異なっており、最適解なのか疑問です。模範解答は数当てゲームの一部のため、全文は以下に記載します。

著書名:スッキリわかるC言語入門第2版
著者名:中山清喬
出版社:インプレス
該当のページ:257

C

1#include <stdio.h> 2#include <stdbool.h> 3#include <stdlib.h> 4#include <time.h> 5 6typedef char String[1024]; 7 8int main(void) 9{ 10 srand((unsigned)time(NULL)); 11 12 printf("***数当てゲーム(レベル2)***¥n"); 13 printf("3桁の数を当ててください!¥n"); 14 printf("ただし各桁の数字は重複しません¥n"); 15 16 int answer[3]; 17 int input[3]; 18 bool check; 19 20 /* 答えを決める */ 21 for (int i = 0; i < 3; i++) { 22 do { 23 answer[i] = rand() % 10; // ランダムな0〜9を設定 24 25 // これまでの桁に同じ数字が使われているかをチェック 26 for (int j = 0; j < i; j++) { 27 check = false; 28 if (answer[i] == answer[j]) { // 同じ数字はNG 29 break; 30 } 31 check = true; // 重複なければOK 32 } 33 } while (i > 0 && check == false); // 1桁目はチェック不要 34 } 35 36 do { // ゲームが続く間はループする 37 /* 結果を初期化 */ 38 int hit = 0; 39 int blow = 0; 40 41 /* 入力された予想を変数に設定 */ 42 for (int i = 0; i < 3; i++) { 43 printf("%d桁目の予想を0〜9の数字で入力してください:", i + 1); 44 String inputStr; 45 scanf("%s", inputStr); 46 input[i] = atoi(inputStr); 47 } 48 49 /* 答えあわせ */ 50 for (int i = 0; i < 3; i++) { 51 if (input[i] == answer[i]) { 52 hit++; // 位置も数字も一致ならhit 53 } 54 for (int j = 0; j < 3; j++) { 55 if (input[i] == answer[j] && i != j) { 56 blow++; // 位置の異なる数字はblow 57 } 58 } 59 } 60 61 /* 結果発表 */ 62 printf("%dヒット! %dブロー!¥n", hit, blow); 63 64 if (hit == 3) { 65 // 正解 66 printf("正解です!¥n"); 67 break; 68 } else { 69 // 不正解 70 printf("続けますか?(0:終了 0以外の数字:続ける):"); 71 String retryStr; 72 scanf("%s", retryStr); 73 74 // 終了するなら正解を表示 75 if (atoi(retryStr) == 0) { 76 printf("正解は・・・"); 77 for (int i = 0; i < 3; i++) { 78 printf("%d", answer[i]); 79 } 80 printf("でした!¥n"); 81 break; // ループを抜けて終了 82 } 83 } 84 } while (true); 85 86 return 0; 87}

前提

私が書いたコードの評価、及び模範回答の修正と解説をお願いします。
特に、模範解答の27〜31行目のbool型については、ネットで調べてもこのような使い方が出てこないため全く理解できていません。

自作コード↓

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <time.h> 4 5int main (void){ 6 7 printf("重複しない3桁の数字を生成します。\n"); 8 srand((unsigned)time(NULL)); 9 int num [3] = {0}; 10 num [0] = rand() % 10; 11 while (1) { 12 num [1] = rand() % 10; 13 if (num [1] != num [0]) 14 break; 15 } 16 while (1) { 17 num [2] = rand() % 10; 18 if (num [2] != num [0] && num [2] != num [1]) 19 break; 20 } 21 for (int i = 0; i < 3; i++) { 22 printf("%d", num [i]); 23 } 24 printf("\n"); 25 return 0; 26}

模範解答↓
※抜粋したためか、以下のコードでは数字の生成がされないまま終了してしまします。

C

1#include <stdio.h> 2#include <stdbool.h> 3#include <stdlib.h> 4#include <time.h> 5 6typedef char String[1024]; 7 8int main(void) 9{ 10 srand((unsigned)time(NULL)); 11 12 printf("***数当てゲーム(レベル2)***¥n"); 13 printf("3桁の数を当ててください!¥n"); 14 printf("ただし各桁の数字は重複しません¥n"); 15 16 int answer[3]; 17 int input[3]; 18 bool check; 19 20 /* 答えを決める */ 21 for (int i = 0; i < 3; i++) { 22 do { 23 answer[i] = rand() % 10; // ランダムな0〜9を設定 24 25 // これまでの桁に同じ数字が使われているかをチェック 26 for (int j = 0; j < i; j++) { 27 check = false; 28 if (answer[i] == answer[j]) { // 同じ数字はNG 29 break; 30 } 31 check = true; // 重複なければOK 32 } 33 } while (i > 0 && check == false); // 1桁目はチェック不要 34 } 35 return 0; 36}

発生している問題

模範解答の抜粋では実行をかけてもprintf文が実行されるだけで、表示されるだけで数字は生成されません。 抜粋したために発生した問題かと思いますが、問題の部分も分からない状態です。

試したこと

ネットでの検索。

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

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

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

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

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

ikedas

2024/03/14 03:03

使っているテキストを明記してください (書籍なら著者名、標題、出版社、該当のページ番号。ウェブサイトならURLを記してください)。 ちなみに、このコメント欄に書くのではありません。質問文を編集して書き加えてください。
kihochi

2024/03/14 04:06

コメントありがとうございます。質問を修正したのでご確認よろしくお願いいたします。
guest

回答5

0

コロンブスの卵的なことで、一度分かれば何度も悩まなくて済みます。
比較し易いように、書籍の模範解答・kihochiさん版・そしてシャッフル版・ランダム取り出し版・一発版を並べました。一発版はデータを用意するのが面倒(& teratail に書ききれないかも?)なのでコメントですが、他はどれも正常に動作しています(と思われます^^;;;)
シャッフル版というのは、トランプ等カードゲームで "山" をシャッフルして上から必要枚数引くようなものです。
模範解答・kihochi さん版に比べ、シャッフル版は固定回数のループだけなので必ず一定時間で結果が出ますが、最速となる(乱数の数値が重ならなかった)場合は模範解答・kihochi さん版のほうが早いです。

(paizaIO で bool がエラーになったので定義してます。)

c

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4#include <time.h> 5 6#ifndef bool 7#define bool int 8#define true (1) 9#define false (0) 10#endif 11 12int *sukkiri(int answer[]) { 13 bool check; 14 15 /* 答えを決める */ 16 for (int i = 0; i < 3; i++) { 17 do { 18 answer[i] = rand() % 10; // ランダムな0〜9を設定 19 20 // これまでの桁に同じ数字が使われているかをチェック 21 for (int j = 0; j < i; j++) { 22 check = false; 23 if (answer[i] == answer[j]) { // 同じ数字はNG 24 break; 25 } 26 check = true; // 重複なければOK 27 } 28 } while (i > 0 && check == false); // 1桁目はチェック不要 29 } 30 31 return answer; 32} 33 34int *shitsumon(int num[]) { 35 num [0] = rand() % 10; 36 while (1) { 37 num [1] = rand() % 10; 38 if (num [1] != num [0]) 39 break; 40 } 41 while (1) { 42 num [2] = rand() % 10; 43 if (num [2] != num [0] && num [2] != num [1]) 44 break; 45 } 46 return num; 47} 48 49int *narabikae(int answer[]) { 50 int v[] = {0,1,2,3,4,5,6,7,8,9}; 51 //shuffle 52 for(int i=0; i<10; i++) { 53 int j = rand() % 10; 54 int t = v[i]; v[i] = v[j]; v[j] = t; //i番目とj番目を交換 55 } 56 for(int i=0; i<3; i++) answer[i] = v[i]; 57 return answer; 58} 59 60int *toridashi(int answer[]) { 61 int v[] = {0,1,2,3,4,5,6,7,8,9}; 62 for(int i=0, c=10; i<3; i++) { 63 int j = rand() % c; 64 answer[i] = v[j]; 65 for(c--; j<c; j++) v[j] = v[j+1]; //j番目を消すように前詰め 66 } 67 return answer; 68} 69 70/* 71static int IPPATSU_ANSWERS[10*9*8][3] = { 72 {0,1,2},{0,1,3},{0,1,4},{0,1,5},{0,1,6},{0,1,7},{0,1,8},{0,1,9}, 73 {0,2,1}, {0,2,3},{0,2,4},{0,2,5},{0,2,6},{0,2,7},{0,2,8},{0,2,9}, 74 {0,3,1},{0,3,2}, {0,3,4},{0,3,5},{0,3,6},{0,3,7},{0,3,8},{0,3,9}, 75(中略) 76 {5,4,0},{5,4,1},{5,4,2},{5,4,3}, {5,4,6},{5,4,7},{5,4,8},{5,4,9}, 77 {5,6,0},{5,6,1},{5,6,2},{5,6,3},{5,6,4}, {5,6,7},{5,6,8},{5,6,9}, 78(中略) 79 {9,6,0},{9,6,1},{9,6,2},{9,6,3},{9,6,4},{9,6,5}, {9,6,7},{9,6,8}, 80 {9,7,0},{9,7,1},{9,7,2},{9,7,3},{9,7,4},{9,7,5},{9,7,6}, {9,7,8}, 81 {9,8,0},{9,8,1},{9,8,2},{9,8,3},{9,8,4},{9,8,5},{9,8,6},{9,8,7} 82}; 83 84int *ippatsu(int answer[]) { 85 return memcpy(answer, IPPATSU_ANSWERS[rand()%(10*9*8)], sizeof(int)*3); 86} 87*/ 88 89void print(char *prompt, int a[]) { 90 printf("%s : %d %d %d\n", prompt, a[0], a[1], a[2]); 91} 92 93int main(void){ 94 int a[3]; 95 srand((unsigned)time(NULL)); 96 print("模範", sukkiri(a)); 97 print("質問", shitsumon(a)); 98 print("並替", narabikae(a)); 99 print("取出", toridashi(a)); 100 //print("一発", ippatsu(a)); 101}

投稿2024/03/14 11:05

編集2024/03/19 08:28
jimbe

総合スコア12648

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

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

kihochi

2024/03/15 23:33

ご回答ありがとうございます!”最適解”は何を優先するかで変わりますよね。どのコードがダメ、ということではなく、それぞれの違いが分かるようご教示いただいたおかげで理解が深まりました。また、シャッフル版は別の回答者の方にヒントをいただき自身で作成してみましたが、jimbeさんのコードがより簡潔でとても勉強になりました!!
jimbe

2024/03/16 04:25 編集

fana さんが回答で提案された >集合から(中略)取り出す(「取り出す」なので,集合の要素は減っていく) を自分で書かないといけないのでシャッフルより少し考える部分が増えますね。最近の言語なら標準でこの機能(やシャッフルする機能も)あったりしますので、コード量はかなり減ります。 例えば java では List を使って、シャッフルは List<Integer> v = new ArrayList<>(Arrays.asList(0,1,2,3,4,5,6,7,8,9)); Collections.shuffle(v); for(int i=0; i<3; i++) answer[i] = v.get(i); ランダム取り出しは List<Integer> v = new ArrayList<>(Arrays.asList(0,1,2,3,4,5,6,7,8,9)); Random r = new Random(); for(int i=0; i<3; i++) answer[i] = v.remove(r.nextInt(v.size())); となります。 まぁ、行数は書き方次第ですので参考程度でしかありませんが^^;
guest

0

ベストアンサー

自分で書いたコードで一応実現できましたが、テキストの模範解答とかなり異なっており、最適解なのか疑問です。

模範解答と異なっている点について

あなたのコードも模範解答も やってることは本質的に同じと見えるので,実装のちょっとした差は正直どうでもいい ように思います.
(個人的には模範解答なるコードも何だかごちゃごちゃしていて微妙な感じかなぁ,とか.)

あなた自身が「やっていることが何なのかを分かってコードを書いていて」その結果として「適切に動作するコードができた」のであれば自信を持てばよいのでは.

「最適解」について

「被ったらもう一回サイコロを振り直せばいいよね」という方法では,「本当に運が悪ければずっと同じ目が出続けてなかなか終わらない(何なら永久に終わらない)こともあり得る」という意味では,効率面では最適ではないだろうと思います.

例えば,
{ 0,1,2,3,4,5,6,7,8,9 } という数値の集合を用意し,乱数を用いてこの集合から要素を1つ選択し,それを取り出す(「取り出す」なので,集合の要素は減っていく)ということを考えれば,「同じ目が出る」こと自体が無くなるので,効率は良いかもしれませんよね.

抜粋したら動作が変だとかいう話について

模範解答の抜粋では実行をかけてもprintf文が実行されるだけで、表示されるだけで数字は生成されません。

一部を抜粋したならば,その抜粋された部分しか動作しないのは当たり前ではないでしょうか.
「生成されません」という文言が正しいかどうかは別として,生成した結果を表示するようなコードが含まれない「抜粋」の仕方をしたのであれば,動作結果が見えないということになるでしょう.それだけの話です.

模範解答の解説

模範回答の修正と解説

模範解答を修正しろ,とは如何なる要求なのか…? が分からないので以下は部分的な解説のみです.

動作については順を追って見ていけばよいだけかと思います.(コード内にコメントもたくさん書かれていますし)

27〜31行目のbool型

について言えば,
27行目で check=false; としているから,その直後の ifbreak; となったならば,j に関する for ループを抜けた時点での check の値は false であり,
そうでないなら 31行目で check=true; とされるから、 break; でループを抜けなかった場合には check の値が true な状態となる.
…というだけです.

最初の数値を決めるとき( i==0 のとき )にはこの j に関する for ループの内側は実施されないので
その場合には未初期化な check の値は不定なままとなりますが,
33行目のループ条件 while (i > 0 && check == false); のところでは,まず i>0 の部分で偽になるのでショートサーキットによって check==false の評価はなされないから大丈夫,ということになっています.

投稿2024/03/14 04:27

編集2024/03/14 05:01
fana

総合スコア11658

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

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

kihochi

2024/03/14 05:29

fanaさん、ご回答ありがとうございます! 【模範解答と異なっている点について】 →そのようにおっしゃっていただけて救われます。引き続きコードをたくさん書いていって自信に繋げていきたいと思います! 【「最適解」について 】 →非常に分かりやすいです、いただいたヒントを基に書き直してみます! 【抜粋したら動作が変だとかいう話について】 →初歩的なミスですね…表示するコードを追加して解決しました。 【模範解答の解説】 →非常によく理解できました。ここが理解できず行き詰まっていたので本当に助かりました。
fana

2024/03/14 06:00

> 模範解答と異なっている点 について言えば,今回はたった「3桁」の話なので,あなたのコードのように愚直に3回分の処理を書き並べても良いだろうし,何ならかえって分かり易い可能性もあるかもしれません. が,この桁数がもっと大きい場合にはさすがに辛くなるので,模範解答のようにループを使って書くことになるでしょう. あるいは,桁数が実行時にしか定まらない場合とかもそうなるでしょうし.
fana

2024/03/14 06:12

> 最適解 も,「どのような観点での」最適という話なのか次第だと思うので,いろいろと考えてみてはどうでしょうか. 例えば処理速度だけに着目するならば 「あり得る3桁のパターン全てをあらかじめ全部網羅したもの(配列とか?)を用意しておき,単にそこから乱数で1つ選ぶだけ」 みたいなのが優秀ということになるのかもしれませんよね.
kihochi

2024/03/15 23:42

ご返信ありがとうございます!”最適解”は目的や状況により異なる、これは今後念頭においてコードを考えるようにします。様々な方法を学ぶことで初めて、自分のコードの愚直さを認識しました。とても勉強になりました。
kihochi

2024/03/16 00:05 編集

「あり得る3桁のパターン全てをあらかじめ全部網羅したもの(配列とか?)を用意しておき,単にそこから乱数で1つ選ぶだけ」 →なるほど!そのような方法もありますね。早速、自分なりに書いてみましたが、重複しないものを避けたり、他のコードでは”012”等、0から始まるものが含まれるのにこちらは含まれなかったり(寧ろ3桁の整数と言えないからいいのかも…?)課題が残りますが、勉強していく中で改善できるよう努めます! #include <stdio.h> #include <stdlib.h> #include <time.h> int main (void){ int array [899]; for (int i = 0; i < 899; i++) { array [i] = i + 100; } srand((unsigned)time(NULL)); int j = rand() % 900; printf("%d\n", array[j]); return 0; }
guest

0

テキストの模範解答とかなり異なっており、最適解なのか疑問です。
私が書いたコードの評価 ... をお願いします。

コードの最適化の評価尺度として主に以下のような観点が考えられます。ただし,実行する環境等により重要性は変化します。

  • 実行時間の短さ
  • コードの読み易さ
  • メンテナンス(バグ修正,仕様変更等)のし易さ
  • (実行環境によっては)メモリの使用量の少なさ

これらの観点で質問者様のコードを拝見させていただくと,特に大きな問題はないと思います。強いて言えば,仕様変更で「3桁」を「4桁」に変更する場合には模範回答と言われるコードの方が変更が少なくて済むので有利とは言えます。

なお,(特に桁数を増やした場合の)実行時間の短さ(と安定)を重視するなら集合から必要桁数を抽出する方法が良さそうです。記述例を下記に示しますが,桁数(N)は 10 まで設定可能です。

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <time.h> 4 5#define N 3 6 7int main(void) 8{ 9 int n[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 10 int r; // number of unused elements in the n-array 11 int a[N], i, j; 12 13 srand((unsigned int)time(NULL)); 14 15 for (i = 0, r = 10; i < N; i++, r--) { 16 a[i] = n[j = rand() % r]; 17 n[j] = n[r - 1]; // fill the used element with the last element 18 } // of the current 19 20 for (i = 0; i < N; i++) 21 printf("%d", a[i]); 22 printf("\n"); 23 24 return 0; 25}

投稿2024/03/19 11:12

編集2024/03/20 06:49
little_street

総合スコア319

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

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

0

模範解答が全然模範的じゃないですね。3桁だからどうでもいい話ですが桁数が増えてくると「とりあえず生成してみて重複があったらやり直し」は非効率的になります。
rand()を3回しか呼ばない定番のやり方を擬似コードで示します。こんな感じでいいはず。

int work[10]=[0,1,2,3,4,5,6,7,8,9];
int answer[3];
for(i=0;i<3;i++){
j=rand()%(10-i)+i; // i~9の乱数を生成
work[i]と work[j]を入れ替える;
answer[i]=work[i];
}

投稿2024/03/19 09:12

編集2024/03/21 07:51
a_saitoh

総合スコア702

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

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

0

ざっくり書きます。
1.10個の配列に0~9を入れます。この配列をAとします。
2.0~9の数を乱数で選びAから出力します。Aのとった数と10番目の数を交換します。
3.0~8の数を乱数で選びAから出力します。Aのとった数と9番目を交換します。
4.0~7の数を乱数で選びAから出力します。
これでいいですよね。

投稿2024/03/21 07:17

pochi0701

総合スコア210

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

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

fana

2024/03/21 07:44

これは何か既存回答とは異なる新しい事柄を言ってるんでしょうか?
pochi0701

2024/03/21 10:26

ごめんなさい。二つ上の回答とかぶってました。 ちゃんと見ないとだめですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問