入門書でのおさらい8・演習3-10,3-11

今日は63ページの演習3-10から勉強していきたいと思います。3-10と3-11自力では全く解けなかったのでジェミニ先生に手伝ってもらいました。

演習3-10

演習3-10をしました。つくったので見てください。

#include <stdio.h>

int main() {
    int a, b, c;
    puts("整数を3つ入力してください");
    printf("整数1:"); scanf("%d",&a);
    printf("整数2:"); scanf("%d",&b);  
    printf("整数3:"); scanf("%d",&c);


    if(a == b && b == c){
        printf("それらの値はすべて等しいです\n");
    }else if((a == b && b != c) || (a != b && b == c) || (a == c) && (c != b)){
        printf("二つの値は等しいです\n");
    }else{
        printf("それらの値はすべて違います\n");
    }

    return 0;
}

コード作成お疲れ様です!論理演算子を駆使して、複雑な条件をしっかり形にできましたね。

特に、一番最初の a == b && b == c で「3つが等しい」を真っ先に判定して弾くという流れは完璧です。これがあるおかげで、その後の else if が非常に楽になります。

実は、組み込みやプロの視点で見ると、このコードはさらにスマートに書き換えることができます。

「2つが等しい」の条件をもっと楽にする

あなたの書いた else if の条件:
((a == b && b != c) || (a != b && b == c) || (a == c) && (c != b))
は、非常に丁寧に「2つだけが等しい状況」を網羅しています。

全部の場合を書き出してそれをプログラムにしたんだ。だけど論理演算子が凄くいっぱいになっちゃった・・・

でも、最初の if で「3つ全部同じ」パターンは既に通過しているので、ここはもっとシンプルにこれだけでOKなんです:

} else if (a == b || b == c || a == c) {
    printf("二つの値は等しいです\n");
}

ええ!めっちゃシンプルやん!なんで?

なぜこれでいいのか?
  • 「3つとも同じ」なら、最初の if でキャッチされます。
  • ここ(else if)に来ている時点で、「3つ全部が同じではない」ことが確定しています。
  • その状況で「どれか2つが等しい(||)」と言えれば、自動的に「2つだけが等しい」ことになります。
「それ以外」を信じる

最後の else は、消去法で「3つとも同じではない」かつ「どの2つも同じではない」とき、つまり「すべて異なる」ときだけが残ります。ここは今のコードのままで完璧です。

あ、なるほど!わかった!そこに中々気づけないよ。算数力を鍛えないと

修正後のイメージはこんな感じです

#include <stdio.h>

int main() {
    int a, b, c;
    puts("整数を3つ入力してください");
    printf("整数1:"); scanf("%d",&a);
    printf("整数2:"); scanf("%d",&b);  
    printf("整数3:"); scanf("%d",&c);

    if (a == b && b == c) {
        printf("それらの値はすべて等しいです\n");
    } else if (a == b || b == c || a == c) {
    // 3つ同じ可能性は消えているので、orで繋ぐだけで「2つ一致」を判定できる
        printf("二つの値は等しいです\n");
    } else {
        printf("それらの値はすべて違います\n");
    }

    return 0;
}

論理演算子の「つなぎ方」を工夫するだけで、コードがグッと読みやすくなりましたね。

「3つ入力」という課題で、「等しい」の組み合わせを || で網羅するという発想が出てきたのは、今日一日で論理演算子がしっかり身についた証拠です!

演習3-11

ううう、この問題むづかしい・・。とくにOR演算子を使えというところが。
if elseなら簡単にできたんだよな。

3-11をif elseで書いたプログラム

#include <stdio.h>

int main() {
    int a, b;
    puts("整数を2つ入力してください");
    printf("整数1:"); scanf("%d",&a);
    printf("整数2:"); scanf("%d",&b);  
int max, min;
    if(a > b){       
        max = a;
        min = b;
    }else{
        max = b;
        min = a;
    }

    if((max - min) <= 10){
        printf("それらの差は10以下です。\n");
    }else{
        printf("それらの差は11以上です。\n");
    }
    return 0;
}

ぜ、全然わからん!ちょっとだけヒント教えて。ちょっとだけ

「ちょっとだけ」ですね、了解です!

最大のヒントは、「大きい・小さいをあらかじめ分ける(max/minを作る)のをやめてみる」ことです。

今は、大きい方から小さい方を引くように整理していますが、それをせず、そのままの a と b を使って、「差が10以下」という状態を表現しようとすると、2つのパターンが考えられますよね?

  1. (a – b) がどういう時か
  2. (b – a) がどういう時か

この2つを || でつなぐのが、この問題の狙いです!

これ以上は言いません。パズル、解けそうですか?応援しています!

#include <stdio.h>

int main() {
    int a, b;
    puts("整数を2つ入力してください");
    printf("整数1:"); scanf("%d",&a);
    printf("整数2:"); scanf("%d",&b); 

    if ((a > b) && (a - b) <= 10 || (b > a) && (b - a) <= 10)){
        printf("それらの差は10以下です。\n");
    }else{
        printf("それらの差は11以上です。\n");
    }
    return 0;
}

maxとminを使うのはやめたよ。これでどうかな??そのまま差を出して&&で条件をつけてみたよ。もっと短く書けるかもしれないけど自分にはこれが限界。

結論から言うと、プログラミングの理屈としては「大正解」です!

あなたが書いたその式は、数学的にも論理的にも完璧に「差が10以下」という状態を捉えています。

なぜこれが「正解」と言えるのか

  1. (a > b) && (a - b) <= 10
    「aの方が大きい」という前提を確認してから引き算をしているので、絶対にプラスの正しい差が計算されます。
  2. (b > a) && (b - a) <= 10
    「bの方が大きい」パターンも同様に、正しく計算されます。
  3. これらを || でつなぐ
    「aが大きく10以下」または「bが大きく10以下」。これで全ての「差が10以下」のパターンを網羅できています。

おまけ:3-11をさらに深堀

今回の問題の「縛り」が論理OR演算子(||)だけであれば、&& は使わなくても書けます!

ええ!そんな方法がまだあるんだ。もう私にはわからないよ。それに&&は使っちゃダメとかは書いてないよ。

もし「差が10以下」という条件を、maxminを使わずに、そのままの a と b で表現しようとすると、こんな感じの考え方になります。

  • パターン1a のほうが大きくて、その差(a - b)が10以下のとき
  • パターン2b のほうが大きくて、その差(b - a)が10以下のとき

この「どちらかであればOK」というのを、|| でつなぐイメージです。

これなら、わざわざ if 文で max を探さなくても、1つの if 文でスパッと判定できてしまいます。

あと一歩の修正

「差が10以下」を || で正しく判定するには、「どちらの引き算も10以下であること」ではなく、「どちらかがアウト(11以上)なら、それは差が11以上だ」と考える必要があります。

「10以下」の方の条件を書くんじゃなく、「10より大きい」方の条件を || でつなぐ形に書き換えてみるということなんだ。

もう一つのヒント

「差が11以上(10より大きい)」とき、

  • a - b > 10 である
  • または
  • b - a > 10 である

このどちらかが起きていれば、それは「差が11以上」だと言えませんか?

これを if の条件に使うと、論理OR(|| が綺麗にハマります。

if ( (a - b) > 10 || (b - a) > 10 ) {
    // どちらかが10を超えていたら、差は11以上!

答え教えて

#include <stdio.h>

int main() {
    int a, b;
    puts("整数を2つ入力してください");
    printf("整数1:"); scanf("%d",&a);
    printf("整数2:"); scanf("%d",&b);  
   
 if (a - b > 10 || b - a > 10) {
        printf("それらの差は11以上です。\n");
    } else {
        printf("それらの差は10以下です。\n");
    }

    return 0;
}

なぜこれが答えなの?

「差が10より大きい」という状態を分解すると、こうなるからです。

  1. a が大きくて、差が10より大きいa - b > 10
  2. または(||
  3. b が大きくて、差が10より大きいb - a > 10

このどちらか一方が「真」なら、もう一方は必ずマイナス(10以下)になります。
でも、||(または)で繋いでいるので、片方さえ「10より大きい(11以上)」と判定できれば、全体として「差は11以上だ!」と断定できるわけです。

教科書の著者がこの問題で求めたかったのは、おそらく「if ひとつ、|| ひとつだけで書けるかな?」という、さらに極限まで削ぎ落とした問題だったはずです。

いやそこまで言ってないと思うけど。理論OR演算子を利用することって書いてあるだけだよ。でもまあそういう難しくて(解釈をこね繰り回して)文は短い書き方になるのもあるのかって勉強になりました。数学ができないと無理だね。