- ベストアンサー
C言語での質問
プログラミングの課題をやっているのですが、一部どうしてもわからないことがあるので質問させて下さい。 課題では100個の点のX座標、Y座標を0から5の間でランダムに設定し、ファイルに書き出すといった内容なのですが、この点を設定した時、任意の2点のX座標、Y座標の差が0.01以下になる場合は設定し直さないといけません。 これは後々の課題に関係してくるから、みたいなのですが。 そこで点の設定の関数として以下のようなプログラムを考えてみました。(ちなみに構造体pointを前に宣言してあり、p[].xは点のX座標、p[].yは点のY座標です。また、NUM=100と最初に宣言してあります。) void tensettei(point p[], int num){ int i, j, k; double sa; for(i=0; i < num; i++){ p[i].x = ((double) rand()) / ((double) RAND_MAX) * 5; p[i].y = ((double) rand()) / ((double) RAND_MAX) * 5; } for(j=0; j < (num - 1); j++){ for(k=j+1; k < num; k++){ if(p[j].x > p[k].x) sa = p[j].x - p[k].x; else sa = p[k].x - p[j].x; if(sa < 0.01) tensettei(p, NUM) } } for(j=0; j < (num - 1); j++){ for(k=j+1; k < num; k++){ if(mp[j].y > mp[k].y) sa = mp[j].y - mp[k].y; else sa = mp[k].y - mp[j].y; if(sa < 0.01) tensettei(p, NUM) } } } しかし、実行してもエラーが起こったのかすぐに終了してしまい、この後に点データを出力するプログラムを書いているのですが、ちゃんと出力されません。 いろいろプログラムを変えて試した結果、原因は「tensettei(p, NUM)」にあることはわかりました。 それさえ変えればすればうまく動作しましたので。 再帰と同じ感じでいけるかと思ったのですがどうやら駄目のようです。 そこで質問なのですが、ループの中である条件が起こったら最初からやり直し、てなプログラムはどんな風にすればいいのでしょうか?
- みんなの回答 (10)
- 専門家の回答
質問者が選んだベストアンサー
>任意の2点のX座標、Y座標の差が0.01以下になる場合 という定義が少しあいまいなので、認識が間違っているかも しれません。2点というのは、今まで設定された座標と 今設定しようとしている座標の事だとして、 今までに登録されたX,Yとこれから登録するX,Yの誤差が 0.01未満ならば再設定不要であると言うことでよいでしょうか? つまり、登録されるX,Yの差異が全て0.01以上になれば良いと 判断します。 以下サンプル /* * WinXP Pro SP2 VC++6.0 */ #include <math.h> #include <time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXARRAYCOUNT (100) #define OUTPERMISSION (0.01) //許容外数値設定 typedef struct{ double x; double y; }point; void tensettei( point *p, int num ) { int i,j; double diff = 0.0; printf( "Out Permission Setting = %f \n", OUTPERMISSION); for( i=0; i<num; i++ ){ // X軸,Y軸を配列にセット p[i].x = ( 5.0 * rand()/RAND_MAX ); p[i].y = ( 5.0 * rand()/RAND_MAX ); //自身の座標X/Yが0.01以下 diff = fabs( p[i].x - p[i].y ); if( diff< OUTPERMISSION ){ i--; continue; } //既存設定要素内と照合 for( j=0; j<i; j++ ){ //任意2点のXが0.01以下 diff = fabs( p[j].x - p[i].x ); if( diff< OUTPERMISSION ){ break; } //任意2点のX/Yが0.01以下 diff = fabs( p[j].x - p[i].y ); if( diff< OUTPERMISSION ){ break; } //任意2点のY/Xが0.01以下 diff = fabs( p[j].y - p[i].x ); if( diff< OUTPERMISSION ){ break; } //任意2点のYが0.01以下 diff = fabs( p[j].y - p[i].y ); if( diff< OUTPERMISSION ){ break; } } //任意要素1つのXまたはYの誤差が0.01以下なので再設定 if( j != i ){ i--; continue; } }//for } int main( int argc, char* argv[] ) { int i, j; double diff = 0.0; point p[MAXARRAYCOUNT]; //乱数生成 srand(time(NULL)); srand(rand()/RAND_MAX); //初期化 memset( p, 0, sizeof(p)); //問題の箇所 tensettei( p, MAXARRAYCOUNT ); printf( "----- Result Check -----\n" ); //Debug 既存設定要素内と照合確認し全ての要素の座標において重複が無いか確認 //もし配列内に1つでも差異0.01未満があれば何番目と何番目の差異がどれだけあるかを表示する for( i=0; i<MAXARRAYCOUNT; i++ ){ for( j=0; j<MAXARRAYCOUNT; j++ ){ if( i != j ){ diff = fabs( p[j].x - p[i].x ); if( diff< OUTPERMISSION ){ printf( "[OUTPERMISSION] => X[%03d]:%f X[%03d]:%f Diff:%f\n", j, p[j].x, i, p[i].x, diff ); } diff = fabs( p[j].x - p[i].y ); if( diff< OUTPERMISSION ){ printf( "[OUTPERMISSION] => X[%03d]:%f Y[%03d]:%f Diff:%f\n", j, p[j].x, i, p[i].y, diff ); } diff = fabs( p[j].y - p[i].x ); if( diff< OUTPERMISSION ){ printf( "[OUTPERMISSION] => Y[%03d]:%f X[%03d]:%f Diff:%f\n", j, p[j].y, i, p[i].x, diff ); } diff = fabs( p[j].y - p[i].y ); if( diff< OUTPERMISSION ){ printf( "[OUTPERMISSION] => Y[%03d]:%f Y[%03d]:%f Diff:%f\n", j, p[j].y, i, p[i].y, diff ); } } else { diff = fabs( p[i].x - p[i].y ); if( diff< OUTPERMISSION ){ printf( "[OUTPERMISSION] => X[%03d]:%f Y[%03d]:%f Diff:%f\n", j, p[i].x, i, p[i].y, diff ); }else{ printf( "[%03d]X:%f Y:%f \n", i, p[i].x, p[i].y ); } } } } return 0; }
その他の回答 (9)
- aris-wiz
- ベストアンサー率38% (96/252)
#9です。 コメントがうそがいっぱいありますね。。。 以下のように読み替えてください。 座標X/Yが0.01以下 >> 座標X/Yの『差異』が0.01『未満』 また、OUTPERMISSIONの値を大きくすると、 乱数の発生する数値の総数が100個以下になり、 無限ループに陥ります。
- redfox63
- ベストアンサー率71% (1325/1856)
再帰は必要ないように思います 0から5までで 任意の2要素の差が 0.01より大きくしたいなら 500個のフラグを用意して これを参照しながら各要素を決定してみてはいかがでしょう char flagX[500]; int n = 0, i = 0; for( n=0; n<500; n++ ) { flagX[n] = 1; } n = 0; while ( n < NUM ) { double rndX = 5.0 * rand() / RAND_MAX; i = (int)(rndX * 100); // 要素rndXが使用済みか if ( flagX[i] ) { // 要素rndXが 1以上か if ( i>0 ) { // 要素rndX-0.01は使用済みか if (flagX[i-1]) { // 要素rndXが 4.99未満か if (i<499) { // 要素rndX+0.01は使用済みか if ( flagX[n+1] ) { p[n].x = rndX; n++; flagX[i-1] = flagX[i] = flagX[i+1] = 0; } } else { // 要素rndXが4.99の場合 p[n].x = rndX; n++; } } } else { // 要素rndXが0の場合 if ( flagX[i+1] ) { p[n] = rndX; n++; flagX[i] = flagX[n+1] = 0; } } } } Y側も同様の処理で可能だと思います
お礼
こういう考え方もアリですね。 ただ、やっぱり1個作るたびに判定して・・・のほうがプログラムとしてはすっきりするので。。。 ありがとうございました。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★修正。 ・問題文を間違って理解してしまったようです。 0.01 以下で再設定でしたね。 下に修正版を載せます。 修正版: void tensettei( point p[], int num ) { double diff, rndX, rndY; int i; // 最初だけ 0~5 の乱数 p[ 0 ].x = (5.0 * rand() / RAND_MAX); p[ 0 ].y = (5.0 * rand() / RAND_MAX); for ( i = 1 ; i < num ; i++ ){ // X軸の乱数 do { rndX = (5.0 * rand() / RAND_MAX); // 0~5の乱数 diff = fabs( p[i - 1].x - rndX ); // 差を計算 } while ( diff < 0.01 ); // Y軸の乱数 do { rndY = (5.0 * rand() / RAND_MAX); // 0~5の乱数 diff = fabs( p[i - 1].y - rndY ); // 差を計算 } while ( diff < 0.01 ); // X軸,Y軸を配列にセット p[ i ].x = rndX; p[ i ].y = rndY; } }
お礼
下のお礼に挙げた理由で採用は出来ませんが、とりあえずこのプログラムでも動作はすると思います。 ありがとうございました。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★アドバイス ・再帰処理と書かれていますが、普通にループでも出来ますが…。 あと最初に 100 個の点を乱数で発生させるよりも1つ1つ乱数で発生させるときに 0.01 を越えないように乱数の段階で制御して 100 個の点を配列にセットすれば楽だと思います。 ・つまり、最初の1つ目だけ 0~5 の乱数でそれ以降の 99 個は 0 ~ 0.02 の範囲に設定します。 そして、前の点に加算して 0~5 の範囲に制御すれば良いと思います。 ・下にそのサンプルを載せておきます。 サンプル: void tensettei( point p[], int num ) { double px, rndX; double py, rndY; int i; // 最初だけ 0~5 の乱数 p[ 0 ].x = (5.0 * rand() / RAND_MAX); p[ 0 ].y = (5.0 * rand() / RAND_MAX); for ( i = 1 ; i < num ; i++ ){ // -0.01~+0.01 までの乱数 rndX = (0.02 * rand() / RAND_MAX) - 0.01; rndY = (0.02 * rand() / RAND_MAX) - 0.01; // 新しいX軸,Y軸 px = p[i - 1].x + rndX; py = p[i - 1].y + rndY; // 新しいX軸,Y軸の 0~5 補正 px = (px >= 5.0) ? 5.0 : (px < 0.0) ? 0.0 : px; py = (py >= 5.0) ? 5.0 : (py < 0.0) ? 0.0 : py; // 補正したX軸,Y軸を配列にセット p[ i ].x = px; p[ i ].y = py; } }
お礼
下のお礼にも書いたとおり、最初に100個の点を作ったのが問題だったのですが、Oh-Orangeさんの考え方だと次の課題に影響が出てくるので・・・ 結局1個新しい点を作るたびにそれまで作った点と比較して、差が0.01以下のものがあれば--iしてやってやり直し、というプログラムにしました。
- fatbowler
- ベストアンサー率48% (26/54)
まず1つ、再帰呼出しから戻ってきた後、諦めたはずなのにさっきのforループを 続けてしまう、という問題があります。 それを解消するには色々策がありますが、質問者のやりたいことに近いだろうと 思われるコードを書いてみました。 ポイントは、差を評価する部分を関数化して「forループ2つを一気に終了させる」 ことを可能にした点です。 (ちなみに、C言語ではgoto文は蛇蝎のごとく嫌われるので、極力使わない方がいいです。) 一応これで動いているのですが、残念ながら100個の点全てのx座標、y座標が うまく散らばることは非常に難しいらしく、何度も再帰呼出しを繰り返した末、 スタックが不足してエラーが起こっているようです。 ダメだったら最初からやり直し、というやり方に無理があるように思います。 #include <stdio.h> #include <stdlib.h> #define NUM 100 typedef struct _point { double x; double y; } point; int check_points(point p[], int num) { int j,k; double sa; for(j=0; j < (num - 1); j++){ for(k=j+1; k < num; k++){ if(p[j].x > p[k].x) sa = p[j].x - p[k].x; else sa = p[k].x - p[j].x; if(sa < 0.01) return(0); } } for(j=0; j < (num - 1); j++){ for(k=j+1; k < num; k++){ if(p[j].y > p[k].y) sa = p[j].y - p[k].y; else sa = p[k].y - p[j].y; if(sa < 0.01) return(0); } } return(1); } void tensettei(point p[], int num) { int i; for(i=0; i < num; i++){ p[i].x = ((double) rand()) / ((double) RAND_MAX) * 5; p[i].y = ((double) rand()) / ((double) RAND_MAX) * 5; } if(check_points(p, num) == 0) tensettei(p, num); } void main(void) { point points[100]; int i; tensettei(points, NUM); for(i=0 ; i<NUM ; i++) printf("points[%3d} = (%2.3lf, %2.3lf)\n", i, points[i].x, points[i].y); }
お礼
はい、結局「残念ながら100個の点全てのx座標、y座標がうまく散らばることは非常に難しいらしく、何度も再帰呼出しを繰り返した末、スタックが不足してエラーが起こっている」ということが問題でした。 根本的な間違いだったわけです。 ありがとうございました。
- koko_u_
- ベストアンサー率18% (459/2509)
再帰呼出しでもできそうな気もするが、試してないので以下適当な意見。 再帰で呼出すとき、tensetti(p, NUM) となっているけど、これは tensettei(p, num) の誤記ですよね。 あと、戦略として 100個の点を p[] に格納してから、2点の差を検証して 0.01 を下回ったら、再度 100個の点を p[] に再設定するということですね。 そうであれば、 for の 2重ループで 2点間の最小の差を sa に格納し終ってから loop の外側で if ( sa < 0.01 ) tensettei(p, num); とした方がわかりやすいと思います。 >ループの中である条件が起こったら最初からやり直し、 >てなプログラムはどんな風にすればいいのでしょうか? 私なら最初からやり直すのではなしに、配列 p[] に点を格納する際に、「それまで格納した点と今 rand() 関数で得られた点の最小距離」を計算する関数 distance(point* p, size_t size, point new_pt); を作成して do { p[i].x = ((double) rand()) / ((double) RAND_MAX) * 5; p[i].y = ((double) rand()) / ((double) RAND_MAX) * 5; } while ( distance(p, i-1, p[i]) < 0.01 ); のような感じでループを回すのかなぁ。(これまた適当)
お礼
はい、誤記です。(まぁどっちでも結果は同じですが。) 結局いろいろな人にアドバイスを受けて 「私なら最初からやり直すのではなしに、配列 p[] に点を格納する際に、「それまで格納した点と今 rand() 関数で得られた点の最小距離」を計算する関数distance(point* p, size_t size, point new_pt); を作成して」 に近いプログラムに変更してちゃんと動作するようになりました。 ありがとうございました。
- hayato0210
- ベストアンサー率22% (8/35)
関数用意する時、少数を使う場合は float型とかじゃないとできないとかではないでしょうか?
お礼
いえ、そういうことではないです。
デバッガを使ってエラー位置を特定してみてください >それさえ変えればすればうまく動作しましたので どう変更したらうまく実行できましたか? コンパイルは通っているのですか? コンパイラによるのかもしれませんが tensettei(p, NUM); としなくてもコンパイル通るんでしょうか >ループの中である条件が起こったら最初からやり直し ある条件でgotoを使うとか? 再帰というか再呼び出しでもいいと思いますけども。 結果さえちゃんと順に返してあげれば。
お礼
0.01以下判定部分を削除したら問題なく動作しましたので。 コンパイラは通ってます。 問題は文法とかじゃなくてもっと根本的な考え方の部分にありました。
void tensettei(point p[], int num){ start: int i, j, k; double sa; for(i=0; i < num; i++){ p[i].x = ((double) rand()) / ((double) RAND_MAX) * 5; p[i].y = ((double) rand()) / ((double) RAND_MAX) * 5; } for(j=0; j < (num - 1); j++){ for(k=j+1; k < num; k++){ if(p[j].x > p[k].x) sa = p[j].x - p[k].x; else sa = p[k].x - p[j].x; if(sa < 0.01) goto start; } } for(j=0; j < (num - 1); j++){ for(k=j+1; k < num; k++){ if(mp[j].y > mp[k].y) sa = mp[j].y - mp[k].y; else sa = mp[k].y - mp[j].y; if(sa < 0.01) goto start; } } } のような単一ループはだめですか?
お礼
goto文は出来るだけ使いたくないのですが・・・ 試してみましたが結局結果は同じでした。
お礼
「任意の2点のX座標、Y座標の差が0.01以下」というのは任意の2点のX座標の差が0.01以下、または任意の2点のY座標の差が0.01以下、ということです。 わかりにくくて申し訳ありませんでした。 ただ、プログラム自体は最終的に作った物と同じ感じです。 ありがとうございました。