前書きとか前提
「プログラムを書く人」とか、そういう類の職種に向いて動き出して二年が経つ(にねんしかたってない)僕です。
が。
同じ学校に通っている(ハズ)の人のコードとか、ネットで質問してる人のコードとか見て、
「こういう書き方はしねーだろ(冗長的等々)」とか、「間違ってるよ!」とか、
目につくと「えぇ...(困惑)」みたいなのを if文 で見かける。
見間違えじゃねーです。
if文で 見かける。
正確には、 else キーワードを交えた文に多い。
間違ってるパターンの場合は、モノによっては「刺身にタンポポ乗せる仕事でもやってろ」1とまでは言わない(言えるほどの立場でもない)が、「あー...あーあ...」という、かなり残念だなぁと思ったときにでる、かなり残念な空気漏れのような心の声が出ざるを得ないときもある。
概要
この記事は、そういう、他人の書いた残念な if 文や else 文について、
ちょっと飲み込みがいいだけのスポンジが、
たかが にねん 専門学校でコーディングに絞って2学習した程度(実践は少ない)で、
「残念だなぁ」みたいな、「俺でも間違えないぞ」みたいな下衆感情で調子に乗って、
「こういう考え方...だよね?」という確認をしつつ、間違ってたら「間違ってんじゃんプギャー」と言ってもらおうみたいな、
そういう、やっぱりざんねんなこころもちで書かれる記事です。
対象者は、
そうですね...
- else を使った if 文を書き間違えるとか
- else if文に自信がないとか人から指摘されたことあるとか
- 条件を逆に書いちゃうとか
- 条件「A」について、
if(A)処理;else if(not A)処理2;
って書いちゃうとか - 「そもそも else って何」とか
そういう人に読んでもらいたいんですけど、たぶん該当者の半分くらいが「俺わかってるし大丈夫」って言うと思うので、
このミスしたときに「そういやあんな記事あったな」くらいで気になったら読んでください。
(大丈夫って思ってる人はこんな記事探さn(ry
よんでほしいところ
if文書き間違えるとか条件逆に書いちゃう人は「ベン図」について触ってるところぜーんぶ読んでほしい(ただの願望)
恒例のアレ
この記事は中学生半年分くらいの学習を二年かけて学んだ程度で、実戦経験のないガキが、他人のコードに難癖をつけるという、
かなり残念かつ正確性への信頼が限りなく0に等しい記事となってます。
お読みになられる場合はそれなりの警戒をして注意深くお読みください。
間違ってたら遠慮なく指摘していただいて結構です。むしろ教えていただけるなら感謝の嵐です。
また、見出しがめちゃくちゃ長くなっちゃってますが、気にしないでください()
本文の構成(目次的な何か)
最初に「else if 文
」をif文単体から、else節を加えたり、コードブロックの省略について触れたりしつつ、
「日本語でわかりやすい言い方に翻訳したらこんな感じだろう」ってところに触れていきます。
逆に言えば、if文などのコードに『翻訳』するときに
「日本語で思考するならこんな言い換え方をしたらいいんじゃない?」という提案です。
この辺、言語化することにあまり意味はないと思いますし、
英語ペラペラなら英語で思考すればすむ話ですし、
個人的な試みに近いので、
すっ飛ばしてくれても構いません。
(実例として取り上げた、「お金のやり取り」もちょっとビミョウだった。)
集合で から
次に、数学の世界でいう「集合」を使って、
「if文を含むコードを見た時、どこを処理する集合か」とか
「集合で考えるならベン図が使えるんじゃないか」とか
「書いたif文が思ったような処理をするか確認するためにベン図を書く」とか
そんなことしてます。
「ベン図、書こうぜ!」が主題でしょうか。
気になっちゃった事例ズ から
最後に、僕が冒頭で挙げた「他人のif文を見て残念に思った」パターンや実例を見せていきます。
「elseの使いどころについて考えてみる」とか
「先のベン図確認法を試してみる」とか
「書いたベン図を基にif文を書いてみる試み」とか
そんな内容です。
ここについては人それぞれの考え方や、状況によって大きく意見の分かれるところかもしれません。
正直僕本人は、if書くたびにベン図書くようなことはしてませんし(オイ)
ただ、「それは本当にelse節(或いはthen節)にあるべきものか」みたいなことを考えてほしいのかもしれない。
...それならフローチャートでいいじゃん(忘れてた)
if
条件分岐。条件によって処理内容を変化させる。
分岐命令 - Wikipediaとか、if文 - Wikipedia なんかを読んでもいいかもしれない。
とりま使用例
if(条件式P) {
then節の処理
}
「then節」というのが、if文の中の条件式P
を満たす(真のとき)に読み込まれる部分だ。
「then」って書く言語もあるけど、書かない言語もある。「~~のとき」くらいの意味だ
条件式Pの部分の処理3をも含めて、
- 条件式P が 真 のときの処理順は、「条件式P → then節の処理」
- 条件式P が 偽 のときの処理順は、「条件式P」
でも、簡単な言葉で書いたら、
難しい言葉使わないなら僕ならこう書く。
もし『こんなとき』は『これ』やって。
ただ日本語にしただけ。
if文の中の
-
if
を 「もし」に言い換える。 -
条件式P
を「『こんなとき』は」に言い換える。 -
then節の処理
を「『これ』やって」に言い換える。
日本語でプログラムを書ける「ひまわり」とか「なでしこ」とかも似たような書き方をするようだ。
ケーススタディ、いこっか。
「もし『支払い金額が請求金額より多いとき』は『お釣り払って』。」
{
if(支払い金額が請求金額より多いとき) お釣りを払う;
}
{/* ↓ 計算できるとことか比較演算子にできるとこ書き換え ↓ */
if(支払い金額 > 請求金額) お釣り(= 支払金額 - 請求金額 )を払う;
}
{/* ↓ プログラミング言語っぽく書き換え ↓ */
if(支払い金額 > 請求金額) {
お釣り= 支払金額 - 請求金額;
お釣りを払う(お釣り);
}
}
これだけ。かんたん。
脱線ズ
脱線しちゃったし折りたたんだ
アセンブラでの分岐
アセンブリ言語の分岐ってクソめんどいよね
アセンブリ言語での分岐
アセンブリ言語の世界では、条件分岐はレジスタの状態に応じて行う「jump」命令(「goto文」のほうが伝わるか)で行ったりするみたいで、
僕のような凡人の頭脳ではやってる途中で破裂するんじゃないかって感じですよね。
試しにこんなのかいてみた。
load num
set result , num -'5'
jumpIfMinus LOW
jumpIfZero AVG
output "high!"
end
AVG:
output "avg!"
end
LOW:
output "low!"
end
こんな言語どこにもありません。某資格試験のためにCASLを指でつついたときの気持ちで書いた。
ですが「フインキ」こんなです。左側に命令書いて、右側に目的語書いていく感じ。
以下はこのエセ言語のなんちゃって仕様
-
load
は「目的語
を読み込む。読み込んだ値についての情報をフラグレジスタに反映する」 -
calc
は「目的語2
を計算して結果を目的語1
に代入する(ここではカンマ区切り)。計算結果についての情報をフラグレジスタに反映する」 -
jumpIfMinus
とかは、「もしフラグレジスタが負を示すときは、目的語
の部分へjumpする」
あくまで条件はフラグレジスタから読む -
名前:
は、jump命令の指定する文の位置を示す。- 「goto n」でn行目に飛ぶみたいのがあるときもある。
- メモリの番地を直接指定することもできたりできなかったり(あいまい)
という感じ
if(A)
処理x
elseif(B)
処理y
else
処理z
endif
処理finally
を記述するなら、
load A
jumpIf Atrue
load B
jumpIf notAbutB
do 処理z
jump FINAL
Atrue:
do 処理x
jump FINAL
notAbutB:
do 処理y
FINAL:
do 処理finally
とかか。
分岐をジャンプで書くから、if文のthen節が遠くにいっちゃうあたり死ねる。
「場合分け」
イメトレには向いてるかも
場合分け
中高生で習う数学の解法に、「場合分け」という手法がある。
場合分け - 青空学園数学科 の説明が日本語での説明では端的でいいか。
英語だとProof by exhaustion - wikipedia の、Exhaustionか?
いくつか紹介している、「case analysis」等の他の呼称に該当があるかもだが。
自分の言葉で説明するなら、
全体で一般化できないことを、部分にわけてそれぞれで一般化して、それを網羅する
といったところか。
場合分けするのに、言葉を知らないだけで条件分岐を扱う。
「部分に分けて」の作業をするときに、例えば
- 「n が 0 以下の場合は常に偽 (中高生だと「定義域外」みたいな扱いか)」
(if(n<=0)then false(または何もしない); else ...
) とか、 - 「もし n が 1 の場合」(
if(n == 1)then ...
) とか、 - 「(上記以外の)任意の n について」(
... else ...
)とか。
書くわけだ。
else
elseだけ触ってる場所なんてないねんな。
elseっていうのは「他」とか「それ以外」とかって意味。
プログラム言語では、「何の」それ以外であるかは「if文の条件式」にかかっている。
とりま使用例
if(条件式P) {
then節の処理
} else {
else節の処理
}
「else節」というのは、if文の中の条件式P
を満たさない(偽)ときだけ読み込まれる部分だ。
条件式Pの部分の処理3をも含めて、
- 条件式P が 真 のときの処理順は、「条件式P → then節の処理」
- 条件式P が 偽 のときの処理順は、「条件式P → else節の処理」
でも、簡単な言葉で書いたら、
難しい言葉使わないなら僕ならこう書く。
もし『こんなとき』は『これ』やって。そうじゃないときは『あれ』やって。
前述の読み替えに加えて、さらにelseに関する言い換えを加えただけだ。
すなわち、if文の中の
-
else
を「そうじゃないときは」に言い換える。 - 「else節の処理」を「『あれやって』」に言い換える。
ケーススタディ、いこっか。
「もし『支払い金額が請求金額より多いとき』は『お釣り払って』。そうじゃないときは『"ありがとうございました"って言って』。」
{
if(支払い金額が請求金額より多いとき) お釣りを払う;
else "ありがとうございました"と言う;
}
{/* ↓ 計算できるとことか比較演算子にできるとこ書き換え ↓ */
/* ↓ プログラミング言語っぽく書き換え ↓ */
if(支払い金額 > 請求金額) {
お釣り= 支払金額 - 請求金額;
お釣りを払う(お釣り);
} else {
言う("ありがとうございました");
}
}
これだけ。かんたん。
でもこれだと、お金足りないときも「ありがとうございました」って言うドM商店になっちゃう。
じゃあ、else if文を書こう。
else節に if を書く
if(条件式P){ // P then節
処理X
} else { // P else節
if(条件式Q) { // Q then 節
処理Y
}
}
(条件A , 条件B)の形で、
- (真 , 真) 又は (真 , 偽) のときの処理順は、「条件式P → 処理X」
- (偽 , 真) のときの処理順は、「条件式P → 条件式Q → 処理Y」
- (偽 , 偽) のときの処理順は、「条件式P → 条件式Q」
...これを、今までの「難しい言葉を使わずに書く」手法を使って書くならこうなる。
もし『こんなとき』は『これ』やって。そうじゃないときは『もし『こんなとき_2』は『これ_2』やって。』やって。
「もし『こんなとき』は『これ』やって。そうじゃないときは『あれ』やって。」の『あれ』に、
「もし『こんなとき』は『これ』やって。」を代入して、「『こんなとき』は」と「『これ』やって」を区別できるようにナンバーをふっただけ。
...「『こんなとき』は」と「『これ』やって」がわからない?→ここ
コードブロック{}
の省略
さて、改行が処理の区切りを示さない、C言語やJavaでは、「終端記号」と呼ばれる(日本語wikipedia参照)記号が定義されている。
セミコロン;
だ。
英語だとなんて呼ばれてるのかなぁと思って C (programming_language) - Wikipedia を見たところ
The semicolon [ ; ] terminates the statement.
とだけ記述があった。特に呼び名ないのかね。
初めてのうちはつけ忘れてエラーを吐いて、原因わからずに泣いた人も一定数いるだろう。
そしてさらに大きい区切りとして、「コードブロック」と呼ぶものがある。
中カッコ{ }
で区切るこれは、処理のかたまりを意味していて、
endif
とかfi
のような、if文等の終わりを明確にキーワードで表さない言語では、
「{}
の中全部then節だからネ」というふうに扱わせることが多い(多いかどうかは知らんが、CやJavaはそう)
CやJavaを学習した人たちは、たぶん
「then節やelse節が一区切りの処理だけのときは、コードブロックを意味する{}
が省略できる。」とか、
「でも読みにくくなるからあまり省略しない」とか、
習ったんじゃなかろうか。
例えば、こうだ。
if(anInt == 0) { //then節
print("ゼロ");
} else { //else節
print("ゼロじゃない");
}
// then節もelse節も一行だけ↓
if(anInt == 0) print("ゼロ");
else print("ゼロじゃない");
// なんなら else も同じ行でいいよ
if(anInt==0)print("ゼロ");else print("ゼロじゃない");
でも、たぶんこれ逆なのでは。
アセンブラ言語での動きを「脱線」の中で雑に書いたけど、分岐式はもともと「『こうだったら』『この処理をするところへ飛べ』」だった。
つまり、「飛ぶ」処理だけ、一行だけを書いていたのだ。
これが、高級言語では複数行書きたい場面が発生して、
{コードブロックを;使って;いっぱい;書ける!;}
ようになったり
if (true) then
"endif"までは
一区切りだからな
この野郎!
endif
とかなったり
したのではなかろうか、と。
閑話休題
さて、僕がここで触れたいのは、特に...} else if(P) {...
みたいに、elseとifの間にスペースが入ってないといけないような言語について言えることだが、
if(p){X;Y;...}
は、一区切りとして扱える状態のもの、「一行として扱える状態のもの」だ。
ここで、さっきの「else節に if を書く」を見てほしい。
条件Pで分岐しているif文の、else節の中は「一行」だ。
それでは張り切って、
else if 文
if(条件P){ // P then節
処理X
} else /* P else節 */ if(条件Q) { // Q then 節
処理Y
}
処理順はelse節に if を書くときと同じだ。
これを、今までの「難しい言葉を使わずに書く」手法を使って書くには、
「else if」の言い換えが必要だろう。
もし『こんなとき』は『これ』やって。そうじゃないときにもし『こんなとき_2』は『これ_2』やって。
else if
を、「そうじゃないときにもし」と言い換えた。
コードブロック{}
がなくなったのに合わせて、「『あれ』」の二重カッコも取り去ってみた。
さて。
「もし『支払い金額が請求金額より少ないとき』は『"足りないよ"って言って』。そうじゃないときにもし『支払い金額が請求金額より多いとき』は『お釣り払って』。そうじゃないときは『"ちょうどいただきます"って言って』。最後に『"ありがとうございました"って言って』」
...見出しなっが。
「ありがとうございました」を、if文がからまないところに持ってった。
{
if(支払い金額が請求金額より少ないとき) "足りないよ"と言う;
else if (支払い金額が請求金額より多いとき) お釣りを払う;
else "ちょうどいただきます"と言う;
"ありがとうございました"と言う;
}
{/* ↓ 計算できるとことか比較演算子にできるとこ書き換え ↓ */
/* ↓ プログラミング言語っぽく書き換え ↓ */
if (支払い金額 < 請求金額) {
言う("足りないよ");
// 請求するところにもどる
} else if (支払い金額 > 請求金額) {
お釣り= 支払金額 - 請求金額;
お釣りを払う(お釣り);
} else {
言う("ちょうどいただきます");
}
言う("ありがとうございました");
}
これだけ。かんたん。
余談~これelse if ちゃう。whileや~
...まあここまで勢いで書いて、いまさら気づいたのは、「お金が足りないときは引き続き請求しなきゃいけない」というところで、
これはwhile文の案件でしたね、ハイ。
「『支払い金額が請求金額より少ないとき』は『"足りないよ"って言う』ことをし続けて、そうじゃなくなったらやめて。もし『支払い金額が請求金額より多いとき』は『お釣り払って』。そうじゃないときは『"ちょうどいただきます"って言って』。最後に『"ありがとうございました"って言って』」
といったところか。
do {
言う("足りないよ");
お金を払ってもらう(追加支払金額);
}while(支払い金額 < 請求金額);
if (支払い金額 > 請求金額) {
お釣り= 支払金額 - 請求金額;
お釣りを払う(お釣り);
} else {
言う("ちょうどいただきます");
}
言う("ありがとうございました");
else if 関係なくなっちゃったぁ。()
else ifの後のelse
if(p){
do(x);
}else if(q)
do(y);
} else { // ここだよここ!ここ!ここよだここ
do(z);
}
って書いたときの、「ここ」の部分の else 節は、どこにかかっているのか。
if(q)
だろう。
if(p)のelse節の中のif文 if(q)のelse節
というのが彼の呼び名だ。
極端に書けば、こうだ。
if(p){
do(x);
} else {
if(q){do(y);} else {do(z);}
}
オマケ: for文のブロックも省略しちゃったぁ
for(int i=0;i<3;i++) for(int j=0;j<3;j++) {
System.out.print(i+j);
}
012123234
と出力されるだろう。
あっ(for(初期化式;条件式;ループ時
処理)
)
おっ(後置インクリメントは前置より優先度高い)
ふーん
for(int i=0;i<3;i++)for(int j=0;j<3;System.out.print(i+j++));
for(int i=0,j;i<3;i++)for(j=0;j<3;System.out.print(j+++i));
さすがにfor文の中にfor文をぶち込ませてはくれなかった。
何?三文字毎に改行したいだぁ?
for(int i=0;i<3;i++)for(int j=0;j<3;System.out.print(i+j+(++j==3?"\n":""))); //インクリメントしつつ改行判定。三項演算
int i=-1,j;while(++i<3)for(j=0;j<3;System.out.print(i+j+(++j==3?"\n":""))); //while使ってみた
for(int i=0,j;i<3;i++)for(j=0;j<3;System.out.print(i+j+(++j==3?"\n":""))); //一個目のforの中でj宣言しても、いいっす
さすがにコードブロック使って初期化式や処理式を二行書かせてはくれなかった。
一時まとめ
ここまではif文やelse if文を日本語化するという試みだった。
逆に、普段日本語で生活している僕らは、この「言い換え」と似たような文にまで持ってくることで、if文へ翻訳しやすくなる...かもしれない。
というか英語圏の人は
if his payment less than the amount, then say "your payment is not enogh!"
てな感じで思考するわけで、なんかもうこのままかけちゃうよねうんって感じ。
ズルい。もっとひまわりとか頑張ってくれ(なくていいです)
集合で
本来プログラムの「流れ」はフローチャートで追うことが普通だが、
ここでは集合を交えて考えてみる。
プログラムを「幾度も」実行した時、
入力内容等によって発生する「分岐」について、
- 「各々の実行の試行(実行結果)」を要素(集合論の専門用語使うなら「元」)
- 「ifの中の条件文」はそのまま集合を縁取る条件
としてとらえて、
「分岐等によって生じる処理内容の違い」を「ベン図へ色を塗ったときの塗られ方の違い」
と言い換えてみる試みをしている。
「平面図形(ベン図)に時間軸(プログラムの行番号?)を加えて処理を追っかける」というのが個人的イメージ
「行番号」は、特に「thenやelseのある位置」が区切りになる。
つっても言いたいことは、「ベン図書くとラクよー」レベルの話だ。
ifの前
if(条件p){
then節
}else{
else節
}
ifの後
と書いたとき、
-
全体集合ALL
はif文が読まれる直前での状態とする。 - 条件pにあてはまる要素群を示す
ALL
の部分集合P
を取り出して、-
P
に対してthen節
を実行する。 -
ALLかつ(Pでない)
に当てはまる部分集合に対してelse節
を実行する
-
-
ifの後
では全体集合ALL
の状態に戻る
みたいに考えてもいいんじゃないかなということで。
まあ例えば「関数」って言葉も集合の写像がどうのとかって話の中で使われるような言葉だしね。
else ifやelseを書くと、
ifの前
if(条件p){
処理X
}else if(条件q){
処理Y
} else {
処理Z
}
ifの後
-
全体集合ALL
はif(p)が読まれる直前での状態とする。 - 条件pにあてはまる要素群を示す
ALL
の部分集合P
を取り出して、-
P
に対して処理X
を実行する。 -
ALLかつ(Pでない)
に当てはまる部分集合(仮にALLnotP
と呼ぶ)に対して、
条件qに当てはまる要素群を示すALLnotP
の部分集合Q
を取り出して、- Qに対して
処理Y
を実行する。 -
ALLnotPかつ(Qでない)
に当てはまる部分集合(賞味ALLかつ(Pでない)かつ(Qでない)
)に対して処理Z
を実行する。
- Qに対して
-
-
ifの後
では全体集合ALL
の状態に戻る
ALLかつ(Pでない)かつ(Qでない)は、記号(否定を!
「かつ」 を &&
)を使って書けば、
ALL && (!P) && (!Q)
となる。 ()
は、言語によって実行順が違ったりするので、保険で書いといた。
ちなみに、この条件式、ド・モルガンの法則4を適用できることにお気づきだろうか。
全体集合の否定は一応、空集合∅ということにしてもらって、
!P && !Q == !( P || Q )
だから、ALL && (!P) && (!Q) == ALL && !( P || Q )
これは、...}else if(){...}else if(){...
と続いた最後で、...}else{
としたときの、else節の状態を指すので、
「PとQとのいずれにも当てはまらないとき5」と言えるわけだ。
「いずれにも当てはまらない」を、
「どんな条件にも当てはまらないハズレもの」として扱うか、
「あのダメパターンでもこのダメパターンでもなく、まさしくこの場合に」と扱うかは、
条件文の書きやすさとかと応相談でしょうね。
ベン図もどきを書いてみる。
if文で条件式をつかって部分集合をとっていく(要素をこしとっていく)さまは、
ベン図もどきを書くのをイメージするとわかりやすいかもしれない。
(あくまで手続き型みたいな、上から読んでくときに限りますが)
さっきも取り扱った以下のコードを元に考えてみよう。
白紙と黒ペンと色付きペンを三色用意する。ここでは仮に赤と青と緑とする。
windowsでいうところの「ペイント」みたいな、お絵かきアプリでもいいが、大きめに書けたほうがラク。スマホはちょっと向かないかな。
PCなら共通して、webアプリを利用してもいいかも。
僕はwindowsのペイント使った。
ベン図の書き方は「囲んで集合を表す」「囲まれた集合に名前を付ける」のふたつだけできればOK。
ほぼほぼこれがすべて。
ifの前
if(条件p){
赤を塗る
}else if(条件q){
青を塗る
} else {
緑を塗る
}
ifの後
ifの前
ifの前に来る可能性のある要素群を全体集合とする。
あまり難しく考えなくていい。全体です。全部です。「全可能性」がたぶん近いかなーというのが正直な気持ちですが、「全部」で結構。
白紙に大きく四角を書きます。できるだけ大きく書いて、
「全部」を意味する言葉を、それの名前だとわかるように書き加える。
形は四角以外でも構いませんが、四角以外で書いている図を見かけませんね。
集合を示すので最低限閉じている必要がある。
名前は、数学で使う「U」とかでもいい。
if(条件p)
条件pに当てはまる要素群の集合をPと名付けて、
ベン図に集合を書き加える。
「全部」の集合に完全に含まれるように(はみ出ないように)丸を書いて、「P」と名前を付ける。
形は丸以外でも構わない。ただし、「全部」をはみ出てはいけないし、集合を示すので閉じていなきゃいけない。
赤を塗る
塗りましょ。Pの中を赤で塗る。几帳面にびっちり塗んなくていいぞ。
境界線キワキワはある程度塗ろうな。キワキワ。キワキワ。ワキワキ
ぶっちゃけ色選んでからバケツツール選んでポチるだけ
else if (条件q)
「Pじゃないとき(else)、もしqなら」という文言 。
条件qに当てはまる要素群の集合をQと名付けて、
ベン図に集合を書き加える。
全体の中で、pには当てはまらないが、qには当てはまる要素群の集合に処理が行われる。
PとQを使って書くと、 Pには含まれず、Qには含まれる。
集合Qを書き加える。全部をはみ出てはいけないし、閉じている必要がある。名前も書く。
また、Pと重なる部分を作る。
青を塗る
塗りましょ。Pには含まれないけど、Qの中ではある部分を青で塗る。
既に塗ってある部分はPに含まれているので、塗ってないところからQに含まれるところ探して塗ろう。
キワキワも塗ろう。
else
「それ以外」。Pではなく、Qでもない部分の集合
特に図に書き加えることはない
が、Pではなく、Qでもない部分がどんな状態か着目しておこう。
緑を塗る
塗りましょ。Pでもなく、Qでもない部分を緑で塗る。
PとQ以外に集合ないしえいえいって感じで塗ってOK
おっと、全体集合ははみ出ちゃダメだぞ。既に塗ってあるところも塗っちゃいけない。
既に塗ってあるところは、(P||Q)
だ。今塗るのは!(P||Q)
だったな。
「全部」の集合の中で、色が塗られていない部分がないなら、「網羅されている」ということだ。
ifやelse ifの一番最後にelse節があれば、確実に網羅できる。
(余談だけど、バケツべちゃーってやるとPとかQの名前が見えにくくなっちゃうので、薄い色のペンで名前を囲んでからバケツ流すと上図みたいにできる。)
それなりに頭が回る人なら、特に、中高生時代「場合分け」の問題で苦労したことがないとかそういう人なら、
「こんなんしなくてもわかるわ!」となるだろうが、
逆にその辺とかでつまづく人や、if elseで条件分岐を作るとき、「このときはこうなってほしくて...アレ?」となるなら、
これ以上に有効な方法は多分ないだろう。
「含む」とか「含まれる」とか、「属す」とかの言葉も意識できると、いいぞ。
(前ふたつは集合と集合との間で成り立つ関係。最後のは、集合と要素との間で成り立つ関係。)
ifの外で処理書くと、色以外で処理を表現しなきゃいけないかも。
網掛けとかしてどうぞ。
実例: うるう年では、ベン図からif文を作る方法の一例を書いている。
気になっちゃった事例ズ
case 1: else節を書かないことについて if(P){~~}else if(not P){~~}
(elseドコー?)
if(){}else if(){} else if(){...
として、最後にelseを書かないとき、
先に述べた「どれにも当てはまらない」を無視してしまっていることにお気づきだろうか。
「該当なし」の場合には特に処理が無いというなら、それもいいだろうが、
「該当なしだったよー」っていうアラートが出るなり「該当なし」ってグルーピングがされてもいいと思うのよね。うん。
例えば
体重を入力
...
if(体重 > 100kg) print "重たいですねぇ";
else if (体重 > 60kg) print "太り気味ですかい?";
else if (体重 > 40kg) print "ふつー";
else if(体重 > 5kg) print "やせすぎでしょ";
else if(体重 > 0kg) print "新生児カナー";
みたいなコードがあったとき、
このコードでは「体重が0kg以下です」と主張する人を「アホか」と返すことができないよね。
else節を書こうね。
else print "アホか";
んで、ここで一定数の人が
else if(体重 <= 0kg) print "アホか";
って付け足すんですよね。
前提として、体重<=0kg
と!(体重>0kg)
とが指す条件が同じだよってことをわかってほしい。
わかったうえで次のcaseだ。
case 2: おんなじ条件式を二回書いちゃう if(P){~~} if(not P){~~}
!
をnotを意味する演算子として、次のコードをみてほしい。
if(P){
処理X
} else {
if(!P){
処理Y
}
}
...失礼。else{if(){}}
はelse if(){}
にできるのはここまで説明したとおりだ。
if(P){
処理X
} else if(!P){
処理Y
}
さてこれを日本語に直してみよう。
「もしPだったらXをやって。もしPじゃないときにもしPじゃなかったら処理Yをやって」
...「PじゃないときにPじゃない」のは自明だし省略しないか?
if(P){
処理X
} else {
処理Y
}
うん。最初に見たコードより簡単になった。
以下のような場合の時もある。
if(P){
処理X
}
// ここには特になんの処理もなし。
if(!P){
処理Y
}
if(P)ってやってるんだからそのelseでいいよね。
- ごくまれに見かける特殊パターン
if(P){/* 処理無し */}
else {
処理
}
/* 上と下で結果は同じ */
if(!P){
処理
}
この書き方は、「特に意識してこうしたいから」という、確たる意思がある場合もあるらしい。
まあif文の中を素直に読んだ後、「じゃないとき」ってあとからelseを読めるから、より自然な感じになる場合もあるかもしれない。
以下は余談だけど、
then節にある処理Xがめっちゃなげーときは、elseがどのif文にかかるかわからなくなる時もあるだろうから、
そういう時はコメントくらいは書いてもいいかもしれない
あと、処理Yがなっげーときとか、コードブロックとかの終端}や)や;
がいっぱい並んでるとどれがどれの終端だかわからんくなるときがまれにあるので、僕は最後にもつけるときがある。
if(P){
なっげー処理X
} else { // if !P
処理Y
} // if (P) else /
}}});}}// カッコの終わりがいっぱい!ラムダ式あると});とかなるよね
エディタの機能で、カッコの終わりにカーソルがあってると、カッコの先端がどんな風か表示してくれるものもある。IDEAとかね。
実例:PHPとMySQLで新規登録とログインを実装する(PDO使用)
PHPでログイン画面作りたくて、参考になるページあさってて見つけたページ。
PHPとMySQLで新規登録とログインを実装する(PDO使用)
当時僕はPHP経験ゼロだったので、こういうの読みながらPHPの言語仕様をつまみ食いしてたわけですが。
こんなコードを記事中に見つけた。
if (条件P) {
処理1
} else if (条件Q) {
処理2
} else if (条件R) {
処理3
}
if (!条件P && !条件Q && !条件R && 条件S) {
処理4
}
おわかりだろうか。
if(条件R){処理3}
の後、elseと続けて書けば、そこの状態がまさに(全体) && !条件P && !条件Q && !条件R
なので、
あとから文を改めてif(!条件P && !条件Q && !条件R ...)
なんて書かなくていいのである。
というか、条件判定の処理をする必要があるので、もし条件PQRが全部falseだとしたら、同じ処理を二回ずつする部分がでてきてしまう。
(実際にそうなっているかは言語や処理系によって違ったりするが。最適化が強いコンパイラとか言語だと、同じ処理見かけたらローカル変数みたいなところに一度放り込むみたいにしたり、勝手にelse ifで書かれたときと同じ処理になったりするものかしらん?)
なにはともあれ、書き直そう。
if (条件P) {
処理1
} else if (条件Q) {
処理2
} else if (条件R) {
処理3
} else if (条件S) {
処理4
}
おっと。これでは、どれにも当てはまらなかったときのelseがない...あれ、
あった。
僕が見ている部分での、条件Sとは「ダブルチェックのために、二回入れてもらったパスワードが合致した時」というものだ。
それより前の、Pや、Qや、Rは、「ユーザーネームが入力されてないとき」「パスワードが入力されてないとき」など、
「ダメなとき」を排除してっているのに、
突然「良い時、こうする」って書いて最後に「ダメなとき、こうする」と書いてるのだ。(伝わりにくい)
分岐後の処理内容が短くて済むほうが上にくるようにif文を書いたほうが、条件式があとから見返しやすくて、優しい気持ちになれると思う。
自分が使うコードでは、ここのif文は前後を逆にして書いた。(というかコメントでも書いちゃった)
case 3: else ifを使っては、いけない。(戒め)~条件に当てはまるとき確実にするべき処理~
「ベン図を書いてみよう!」の項で書いたように、else if を使ったとき、「それより前の条件に当てはまっていて」「else ifで当てはまる」場合に処理がされるだろうか。つまり、
if(p){
処理x
}else if(q){
処理y
}
となっていて、pとq両方当てはまるときには、処理yは実行されない。
先のベン図色塗りで言うなら、「Pの中には青は塗らない」
処理xと処理yとを両方実行するパターンを作りたいなら、
if(p){
処理x
}
if(q){
処理y
}
と書く。
他にも、if文(p)のthen節
にif(q)
を書けば、QだけどPじゃない
部分には(if文の中では)処理をしない分岐になる。
自分の望む分岐を狙うとき、「ほんとに自分の書いたif文の組み合わせで望む分岐が実現できているだろうか」と気になるなら、またベン図を書いてみるといい。
逆に、ベン図を書いてから文に起こすことも可能。(説明するのはかなりめんどくさそーのでご勘弁くだせぇ)
ifの中の条件を簡単に書けそうなところから書いていけばおっけー
上でやった事例みたいに、丸で書いてるなら、丸の中が全部同じ状態になってる部分集合のif(条件式)
を最初に書く。
同じ場所に複数の処理を必要とする場合(重ね塗りとか網掛け使うとき)は、if文を切って複数書くパターンかも。
後述している実例: うるう年では、ベン図からif文を作る方法の一例を書いている。
if文をつづけて書くのは、それぞれの条件(ベン図で、線で区切られた部分集合)すべてについて処理が必要なとき等だ。
例えば「なんかに使う、姓と名をランダムに決める装置」があったとして、
「ご希望があれば入力してどうぞ」となってたとしよう。
両方に希望があれば、両方入れる人もいるだろうし、両方に希望がない人も、姓だけ、名だけ、希望がある人もいるだろう。
つまり、4つの場合分け全部で処理がちょいとずつ違うわけだ。
僕は多分こんなコードを書くだろう。
姓を入力
名を入力
if(姓にデータなし) 姓 = randomStr();
if(名にデータなし) 名 = randomStr();
print("generated! 姓:" 姓 "名:" 名);
処理の流れが4通りあり得ることがわかるだろうか。
さっきと同じベン図の分け方で、4箇所すべて別の色で塗ることになるだろう。
else節を書き加えた場合には、「処理」が色塗りだけでは足りなくなるだろうが、処理の流れのパターンはやはり4通りだ。(yesかnoしかないからね。)
case 4: 間違ってるよ!(間違ってるよ!)
これについてはもうなんとも...
if文書き終わった後に、一度コーディングしてたときの記憶すっからかんにしたうえで、
自分の書いたif文がどういう処理順をするか、場合分けして考えるといいんだけども。
それが難しいなら、
if文書き終わった後に、一度コーディングしてたときの記憶すっからかんにしたうえで、
きっちりベン図書いて、「ほんとにこのベン図であってるかなぁ」って考えれば、
いいんじゃないかな(あきれとあきらめとざんねんさを交えつつ諦観)
場合分けして考えるのとベン図書いて考えるのと、割とやってること近いのよ。
ベン図書いたら手間はかかるけど、確実だよね。うん。
(まあ色塗りでは再現しきれないときもあるだろうから、文字で「処理x」とか書くことになるだろうけど)
適用してる処理が正しいかどうかは、場合分けで考えたほうがやりやすいかなぁ。
※追記
フローチャートでも、全然いいと思います。というか普通、フローチャート書くと思います。
実例: うるう年
うるう年かどうか判定する条件、みなさんご存じだろうか。
4で割れる年、だけではたりない。
- 原則的に、4で割れる年はうるう年なの!!
- でもでも。100で割れる年はうるう年じゃないの!!!
- でもでもやっぱり、400で割れる年はうるう年なの!!!!!
やかましいわ! (初めて知ったときほんとにこう思った)
ちなみに教えてgooによると、一年の長さは正確には365日5時間48分46秒。
単位を「日」にすると、約 365.2422 日である。
「4年に一度日付を足す」を「一年に1/4日足す」に言い換えれば、このグレゴリオ暦によるうるう年の定義がどれだけ近いか計算できる。
+ 1/4 - 1/100 + 1/400 = 0.25-0.1+0.0025 = 0.2425 (日)
0.0003日/年 すなわち 26秒/年 くらいのペースでズレるようだ。
さて、僕は専門学校の二年制学科に所属しているのだが。
同じ学校、同じ分野で四年制の学科に通う人のUSBに入ってたコードがこちら。
main(){
int year;
scanf("%d", &year);
if(year % 100 == 0){
if(year % 400 == 0){
printf("うるう年");
}
if(year % 4 == 0){
printf("うるう年");
} else {
pritf("うるう年じゃない");
}
printf("うるう年じゃない");
}
}
ちょーっとこまかいとこわすれたけど、こういうレベルでめちゃくちゃだった。...うん。
if(year %100 ==0)
が最初だったことと、
「うるう年」「うるう年じゃない」が両方出るとか、何にも表示されないとかが発生するコードだったことと
は確か。
ベン図、いってみよっか。
今回は特に、「100で割れるときは4で割れる」「400で割れるときは100で割れる」ところにも着目しよう。
かなり横着だが、「nで割り切れる数の集合」を「%n=0
」で表現した。
「x %n ==0
」とか書くでしょ?書くよね?(%
が剰余記号の言語の場合)
...うん。うるう年のときを青、うるう年じゃないときは赤で塗るつもりだったんだけど、「両方表示」を紫で塗ったら、紫しか塗らなかったっていうね。
まじめにこのレベルだった。一周まわって感動したよね。
さて、正しいベン図はこうだ!
赤はうるう年じゃない。青はうるう年。
(図1)
(↑図1)
あるいはこうだ!(100で割れたら4で割れるとか考えないで書いたらこうなる)
赤はうるう年じゃない。青はうるう年。
(図2)
(↑図2)
「4で割れるけど100で割れない部分があるぞ!」とか言う人には「空集合」という言葉を置いておきますね。
※ベン図 - wikipediaによると、図1のように、領域の区別が2のn乗個にならないものは正確には「ベン図」とは呼ばないそうな。
こういう書き方をするときは「オイラー図」というようだ。
また、図2は「ベン図」になっているが、「4で割れるけど100で割れない」等の空集合は射線を入れるのが「ベン」先生流とも書かれている。
下に、ウィキペディアのように「元が無い」部分を(射線ではなく、)黒く塗った例を書いてみた。
(↑黒塗りベン図)
- 100で割れる かつ 4で割れない
- 400で割れる かつ 100で割れない
- (400で割れる かつ 4で割れない)(1と2で十分)
を示す部分を黒く塗った図。
解法の1:塗りやすいとこから塗る
まず、
- 複数の処理を必要とする部分はこの図にはなく、
- 全体集合の中が全部塗られていて、
- 同じところに別の処理をする可能性がない
ので
「if(p){x}else if(q){y}...else{z}
」のパターンで書ける。
上記の図(どっちでもいい)を見た時、丸の中ぜんぶ処理が共通しているのは、%400=0
の部分だ。
処理内容は、「青を塗る」。ここでは、該当する数の年をうるう年だとすることだ。
コードの一番上に以下を書く
if(year %400 ==0){うるう年}
以降は、%400=0
の中を塗らないので、else
を続けて書く。
if(year %400 ==0){
printf("うるう年");
}else...
%400=0
は塗らないことに留意しながら、再び図を見ると、
複雑な条件式を書かなくてもいいのは%100=0
の部分。
((%100=0)かつ(%4=0)
の部分が、100のほうに含まれているからね。)
処理内容は、「赤を塗る」。ここでは、該当する数の年をうるう年ではないとすることだ。
先のコードに続けて、
if(year %100 ==0){うるう年ではない}
と書く。
ここまでで塗った部分に色を塗ることはもうないので、やはり続けてelse
を書く。
if(year %400 ==0){
printf("うるう年");
}else if(year %100 ==0){
printf("うるう年ではない");
}else...
%400=0
や%100=0
は塗らないことに留意しながら、再び図を見る。
%4=0
の部分だけを青で、他を(elseを
)赤で塗る。
必要となる条件式は、4で割れるかどうか。
割れた時はうるう年。割れないときはうるう年でない。という情報を処理する(表示する)
先のコードに続けて、
if(year %4 ==0){うるう年}
と書く。
**該当しない部分(else
)**はすべてうるう年ではないので、
続けてelse{うるう年ではない}
とも書く
if(year %400 ==0){
printf("うるう年");
}else if(year %100 ==0){
printf("うるう年ではない");
}else if(year %4 ==0){
printf("うるう年");
}else {
printf("うるう年ではない");
}
終わり。
入力させるとかmainとかは言語仕様の理解だと思うので書かないゾ
解法の2:大きい囲いから取り出していって、除外すべき部分を先に処理する。
こっちでやる場合は図2だとむつかしい。空集合を意識しながら書く必要がある。
図1を見ながら読んでください。(追記した「黒塗りベン図」を用いてもいい)
全体集合の中で最初に見える条件式が%4=0
なので、書いちゃう。
処理は...条件によって違うナー。→まだ書かない。
でもelse節は書ける。外全部真っ赤だからね。
書いちゃう。
if(year %4 ==0){...}else{うるう年ではない}
if(year %4 ==0){
...
}else printf("うるう年ではない");
この(%4=0
)くくりの中で最初に見える条件式は%100=0
書いちゃおうか。
処理は、やっぱり条件によって違うのでまだ書かない。
else節は書ける。前に塗った部分は、今書いてる部分からは見えない部分だから処理をしない。
青を塗ろう(うるう年である)
if(year %100 ==0){...}else{うるう年}
if(year %4 ==0){
if(year %100 ==0){
...
}else printf("うるう年");
}else printf("うるう年ではない");
今このくくり((%4=0)かつ(%100=0)
すなわち%100=0
)の中で最初に見える条件式は%400=0
中は青、外は赤。 (鬼は外、福は内の逆っぽくないか?)
中はうるう年。外はうるう年ではない。
if(year %400 ==0){うるう年}else{うるう年ではない}
if(year %4 ==0){
if(year %100 ==0){
if(year %400 ==0){
printf("うるう年");
}else printf("うるう年ではない");
}else printf("うるう年");
}else printf("うるう年ではない");
解法の3:図1で、not演算子を使って一番外から塗っていく
こっちでやる場合は図2だとむつかしい。空集合を意識しながら書く必要がある。
図1を見ながら読んでください。(追記した「黒塗りベン図」を用いてもいい)
図1で、%4=0
の外側、つまり!(%4=0)
の部分は、全部赤なので、うるう年ではない。
中はうるう年だったり、じゃなかったりするので、あとで考える。
if(year %4 !=0){うるう年ではない}else...
if(year %4 !=0) printf("うるう年ではない");
else ...
今考える部分(else節。%4=0
の中)で、%100=0
の外側は全部青なので、うるう年。
if(year %100 !=0){うるう年)else...
if(year %4 !=0) printf("うるう年ではない");
else if(year %100 !=0) printf("うるう年");
else...
今考える部分(if(!(%100=0))のelse節
)で%400=0
の外側は全部赤。中は青。
外はうるう年ではない。中はうるう年。
(ここで逆に、中を先に塗ってもいい)
if(year %400 !=0){うるう年ではない)else{うるう年}
又は
if(year %400 ==0){うるう年)else{うるう年ではない}
if(year %4 !=0) printf("うるう年ではない");
else if(year %100 !=0) printf("うるう年");
else if(year %400 !=0) printf("うるう年ではない");
else printf("うるう年");
または
if(year %4 !=0) printf("うるう年ではない");
else if(year %100 !=0) printf("うるう年");
else if(year %400 ==0) printf("うるう年");
else printf("うるう年ではない");
まあ最後だけ逆の条件式書くと見返した時混乱するだろうし、止したほうがいいんじゃないかな。
比較とか
if(year %400 ==0){
printf("うるう年");
}else if(year %100 ==0){
printf("うるう年ではない");
}else if(year %4 ==0){
printf("うるう年");
}else {
printf("うるう年ではない");
}
if(year %4 ==0){
if(year %100 ==0){
if(year %400 ==0){
printf("うるう年");
}else printf("うるう年ではない");
}else printf("うるう年");
}else printf("うるう年ではない");
if(year %4 !=0){
printf("うるう年ではない");
}else if(year %100 !=0){
printf("うるう年");
}else if(year %400 !=0){
printf("うるう年ではない");
}else {
printf("うるう年");
}
解法1と解法3は逆になってるだけで、かなり見た目が近いことがおわかりだろうか。
-
else if
の、-
else
で塗り終わった(処理の記述を書き終わった)部分を除外しつつ、 -
if(のthen節)
で条件式(ifの中身)でよりわけた部分に色を塗り(処理を記述し)、
-
- 最後に
else
で「残り全部」の処理を記述する。
書き方になっている。
これらが常に正解とは限らないが、これくらい単純なケースのうちはめちゃめちゃキレイで読みやすいコードになってると思う。
また、今回は「%100=0 は %4=0 に含まれる
」「%400=0 は %100=0 に含まれる
」が成立するので、
解法1と解法3が「記述順が真逆」になっていて、
「else」が示す条件式(if(P){}else{}
で言うところの!P
)を後ろからif
に書いていくことをやっているように見えるが、
先の「含まれる」の条件が無い時は、今回空集合だった部分で不備が生じるだろう。
解法2は異質な空気を漂わせている。
else if()...else{}
を使わないと、網羅できているかどうかとても不安な気持ちになる。
ifが入れ子になっている部分に、余分な処理ブロックが発生しているので、余計な処理を記述してしまうリスクも高まるだろう。
条件文とそれに対応する処理内容が微妙に離れてしまっているのも気持ち悪い。
解法そのものに問題はないかもしれないが、_今回については_この記述の仕方はしたくない。
どれが平均処理スピードが速いかとか、特にいらないと思うし、この記事の主旨からはかけ離れてるけど、
より要素数の大きいyear %4 !=0
とかを最初のほうで処理して、その後ほかのif文を読まなくていい解法3が、平均スピードは速いんじゃないかな(計測してないしあんま興味ない)
オマケ: %100=0
から書く
そもそも最初の「間違ったコード」を書いた人は「100で割れる年の特異性」に注目したくて、year %100 ==0
を最初に書いて、混乱したのかもしれない。
これはこれで(アプローチとしては)間違っているとは言いきれないので、書いてみる。
図から書き起こす場合は「図1」や、小さく載せた「黒塗り入りベン図」を参考にするとやりやすい。
(「4で割り切れるが400で割り切れない」は常に偽となるので、該当する部分集合は空集合になる。)
(図からコードを起こす過程は省略させていただく。)
if(year %100 ==0){
if(year %400 !=0){ // 2行目
printf("うるう年ではない");
}else{
printf("うるう年");
}
}else if(year %4 ==0){
printf("うるう年");
}else{
printf("うるう年ではない");
}
if(year %100 ==0)
と「うるう年ではない」を表示する行をできるだけ近づけてみた。
代償として、%400=0
だけ、他とは違い否定形!=
になっている。(2行目)
これを==
で書く場合、then節とelse節が入れ替わり、
「100で割り切れてかつ400でも割り切れる」部分の処理記述が最初にくる。
「100で割り切れるときにうるう年でないパターンがある」ことを、より強調する目的に寄り添うなら、
上記のコードが適していると思う。
もっとオマケ
year%4==0&&year%100!=0||year%400==0
!(year%4)&&year%100||!(year%400)
ここでやりたかったことは、
集合論を無理くり絡ませて、ベン図でif文をいじくるお遊び。
ベン図のお絵かきからifが書けるっていうのも割とアリな気がした。
ただ、まぁ。
いきなり画面に向かってif文書いて間違えるくらいなら、
ベン図なりフローチャートなりから書き起こす訓練してるうちに身に着けていったほうが、
多分ラクだし場合によってはどこまで行ってもそのほうが早くなるかもだよという話。
正直に告白すると、うるう年の問題は、彼の「間違った」コードを見た時に、「正しくはこうだろう」と自分でやってみたのだが、
後出しであることを差し引いても、ベン図を書いてからやったほうが混乱せずに済んだ。
できる人はこんなの書いてないと思うけど、頭の中でこれと似たような処理してるはずだから、
if文の流れとかで困るんだったら書いてみたらいいと思う。
-
おそらく、この記事は「最低限これはできてくれ頼む」っていう悲鳴をぶちまけただけで、本当にタンポポ乗せとけって意味ではないと思うが。「これができないならやめたほうがいい」っていう最終戦力外通告ではあるので、心に留めて...?...肝に銘じて...?...意識に残しておきたい。 ↩
-
大学では、特に「情報科学」という専門分野では、集合論や回路等々の知識を使って、学者サイドの言葉と思考でもってプログラムを触るようだが。専門学校では「そういう言葉」を知らなくてもわかるように、なんならちょっと興味のある中学生とかなら半年くらいで学習完了できるような内容を、二年かけてじっくり()教える。僕は大学を「教養課程はいやよいやよ」と言って中退し、専門学校で学習しだした身なので、集合論等々に関する体系的な知識はない。幸い、僕の大学時代の学科は数学科で、入門書が読めるための入口に立つための知識を教えるような教科書が手元にあるので、ぼちぼちと読んでいる。 ↩
-
C等の言語では、条件式が入るべき部分で、真偽値以外のものを真偽値として解釈して判定したりするので、処理の結果値(真偽値以外)を使ったりできちゃう。
if(intArray.length)
って書いて、intArrayが値を持っているときだけ処理するみたいな書き方ができちゃったり。Javaなんかは、条件式が入るべき部分は最終的な値は「boolean以外メッ」としているので、if(intArray.length)
はコンパイル(前)エラーになるが、処理を書くこと自体はできる。int[] intArray;if((intArray=new int[]{9,5,3}).length>0)print(intArray[0]);
とかは一応通るし、9が出力される。 ↩ ↩2 -
ド・モルガンの法則とは、集合PとQについて、
!(P || Q) == !P && !Q
が成り立つというものだ。ウィキペディア等に詳しい。高校数学の美しい物語なんかにも解説が書いてある。 ↩ -
「
||
」つまり、or(又は)演算子は左右の条件式について、「どちらか一方が真のとき」真を返す演算子だ。条件式「P||Q
」は、「PやQのどちらか一個にでも当てはまるとき」。その否定「!(P||Q)
」なので、「PとQとのいずれにも当てはまらないとき」となる。「!P && !Q
」は「Pに当てはまらず、Qにも当てはまらない」とでも訳そうか。前者のほうがくどくない。言い方が違うだけで、指しているものは同じであるが。 ↩