提出期限を過ぎている提出されている解答がある。 提出期限は授業前日の午前中である。これを過ぎても提出自体は可能だが、 たとえ評価がOKであっても大きく減点されるので、注意すること。
for(i=0; i<=n; i++) { … if (i==n-1) break; }
if (x1*UNIT < n*UNIT) …それぞれ、
for(i=0; i<n; i++) { … }
if (x1 < n) …と書くべきである。 (いちいち面倒なのでOKにはした。)
120という定数は定数マクロとして#define
する方が良い。
(ただし今回は減点せず。)
#define LEN 120; int main(void) { … int step = LEN/n; for (i=0; i<=LEN; i+=step) { … } … }誤差が累積して最後が120ちょうどにならないことがある。
step
やi
をdouble型にしても、
やはり誤差が生じる可能性がある。
cf. 教科書 p.174
List 7-9)
効率が悪いように見えても、この場合繰返し毎に座標を計算するほうが良い。
for (i=0; i<=n; i++) {
int x = i*LEN/n;
…
}
(ただし今回は減点せず。)
recipsum
の値が常に1になってしまい、
その原因がわからなかった人は
結局third.cを本質的に理解していなかったということである。
本質的な理解をせずに、試行錯誤してうまくいけばOKという学習態度では、 プログラミングの能力は身につかない。
次のようなプログラム:
double recipsum(int n) { double sum=0; for( ; n>0; n--) { sum += 1/n; } return sum; }がうまく動作しなければ、原因を探るために次のように
printf
を挿入してみる。
double recipsum(int n) {
double sum=0;
for( ; n>0; n--) {
sum += 1/n;
printf("n=%dのとき、sum=%f\n", n, sum);
}
return sum;
}
sum
が増えていかないことがわかるので、
1/n
の値を表示してみる。
double recipsum(int n) { double sum=0; for( ; n>0; n--) { double tmp = 1/n; sum += tmp; printf("n=%dのとき、1/n=%f, sum=%f\n", n, tmp, sum); } return sum; }このように問題点を絞りこんでいけば、 うまく動作しない原因を突き止めることができる。
ここまで自分で突き止めて、
「1/n
が常に0になります。なぜですか?」
という質問なら答えることができる。
仮引数の他に変数を1つだけという条件が満たされない解答が多かった。
double recipsum(int n) {問題を良く読んでいないのか、それとも未完成でも出さないよりは 良いということか? 後者の場合は、必ずコメントの形で未完成である旨を明示して欲しい。int i;double sum=0; for(i=1; i<=n; n++) { sum+=1.0/i; } return sum; }
recipsum
の型は、
double recipsum(int n)
と指定されているので、
もちろんこれを勝手に変えてはいけない。
double recipsum(int n, int i) { double sum=0; for(i=1; i<=n; n++) { sum+=1.0/i; } return sum; }
do〜while
文を使う場合、
do {
…
} while (--n>0);
フツーは while
のまえに空白を入れる。
ただし、この問題の場合 do〜while
よりも
while
を使う方が良い。
下の例のようにn
を減算するタイミングを間違えると、
while(n-- > 0) { sum += 1.0/n; }1.0/0.0を計算することになり、プログラムが異常終了をする。
while
やfor
などの繰返しを使ってしまうと、
問題の指定する漸化式に従って効率良く計算することは難しい。
int powerrec(double m, int n) { /* version A. -- NG */ double x=1; for( ; n>0; n--) { x*=m; } return x; }
また、下の再帰のようにnを1ずつ減らすなら、
int powerrec(double m, int n) { /* version B. -- NG */ if (n==1) { return m; } else { return m * powerrec(m, n-1); } }まだ、Version Aのように繰返しを使う方が効率の点ではましである。 (関数呼出しのたびにメモリが必要なため)
以下の定義:
int powerrec(double m, int n) { /* version C. -- 惜しい */ if (n==1) { return m; } if (n%2==0) { return powerrec(m, n/2)*powerrec(m, n/2); } else { return powerrec(m, n/2)*powerrec(m, n/2)*m; } }のように同じ引数で関数を2回呼び出しては呼出し回数に関する制限を 満たすことはできない。例えば
power(3, 16)の場合、
5回で済むはずのところ、
powerrec(3, 16)
powerrec(3, 8)
powerrec(3, 4)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
powerrec(3, 4)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
powerrec(3, 8)
powerrec(3, 4)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
powerrec(3, 4)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
powerrec(3, 2)
powerrec(3, 1)
powerrec(3, 1)
結局、31回呼出しをすることになる。
(powerrec
の先頭にprintf
を挿入すれば、
何回呼び出されているかはすぐに調べることができる。)
Version Cのように答えた人は、
問題の意図と再帰の考え方は正しく理解している。
ただ、皮肉なことに Version A や Bよりも2倍効率が悪い。
出力などをせず、純粋に値だけを求めるための関数で、
引数が全く同じ呼出しが隣にあるなんて気持ち悪い、
という感覚が湧くようにならなければならない。
端点の座標をそれぞれ(x1, y1)
, (x2, y2)
とすると、図の各頂点の座標は、
#define C 0.288675 /* sqrt(3)/6 */ … double xA = (2*x1 + x2) / 3, yA = (2*y1 + y2) / 3; double xM = (x1 + x2) / 2, yM = (y1 + y2) / 2; double xB = xM + C * (y2-y1), yB = yM - C * (x2-x1); double xC = (x1 + 2*x2) / 3, yC = (y1 + 2*y2) / 3;という式で計算できる。
これ以外の部分の定義は比較的素直なので、ぜひ再挑戦して欲しい。