(030) 条件分岐

Ippei Kishida

Last-modified:2018/09/28 23:35:23.

1 構造化プログラミング

基本的なプログラミング手法として、「構造化プログラミング」というものがある。 その要素は以下の3つである。

これをしっかりおさえておけば、多くの問題をプログラムで解決できるようになる。 順次実行は、当たり前だと思ってもらってもらえるだろう。 それでは、まず条件分岐から見ていこう。

2 条件分岐

最も基本的な条件分岐は if 文である (Fig. 1)。 以下のサンプルコードを実際に試してもらいたい。

値が 10未満なら smallと表示

#if1.rb
x=4 #10より大きな値を入れても試せ
if (x<10)
  print "small\n"
end

値が 10未満なら smallと表示、そうでなければ large と表示

#if2.rb
x=4 #10より大きな値を入れても試せ
if (x<10)
  print "small\n"
else
  print "large\n"
end

値が 10未満なら small と表示、10以上20未満なら medium、いずれでもなければ large と表示

#if3.rb
x=4 #それぞれの範囲の数字を入れても試せ
if (x<10)
  print "small\n"
elsif (x<20)
  print "medium\n"
else
  print "large\n"
end

比較は数値だけでなく、様々なクラスで可能である。 たとえば文字列を使って、 ユーザ名が Ruby ならば “hello”, それ以外ならば “goodbye”

#if4.rb
name = "Ruby"
if name == "Ruby"
  print "hello! "
elsif name == "ruby"
  print "something strange..., "
else
  print "goodbye!, "
end
print name, "!\n"
Figure 1: (fig:fig030_010) if, if-else, if-elsif の処理の流れ。
Figure 1: (fig:fig030_010) if, if-else, if-elsif の処理の流れ。

3 論理値と比較演算・論理演算

3.1 論理値

条件文の中に書かれるような「x < 10」のような文は、 これ自身が「論理値を返す命令」である。 論理値とは真(true) もしくは偽(false) の2種類しかない値のことだ。 条件分岐のためにはこれだけ分かれば十分であり、 他の情報は余分なのでこのような値が用意されている。 1

たとえば以下のコードを実行してみよう。

#bool.rb
bool = (1 < 2)
print bool, "\n"

bool = (2 < 1)
print bool, "\n"

最初の print では true が、次の print では false が出力された筈だ。 一度変数に入れてやらずに以下のようにしても同様の結果が出力される。

print (1 < 2), "\n"

なお、右辺の括弧()は 演算の順序を分かりやすくするために使っている。

bool = 2 < 1

とだけ書かれてすぐに意味が分かる人は稀である。 2

なお、論理値を print で表示させると「true」や「false」などといった文字列として表示されるが、 論理値は “true” といった文字列で情報を保持しているわけではない。 あくまで「真である」という生の情報のみを保持しており、 print文は変数を文字列として評価してその文字列を出力する関数であるため、 論理値 true は文字列 “true” として表示されるわけである。

4 比較演算子

前節では 『「x < 10」は「論理値を返す命令」である』と述べた。 このような命令を表す演算子は比較演算子と呼ばれる。 たとえば「1+2」において加算演算子「+」が 「1と2を作用させて値(3)を返す演算子」であるのと同様、 「1<2」において比較演算子「<」は 「1と2を作用させて(比較して)値(true) を返す演算子」と言える。 他の比較演算子と一緒に Tbl. 1 にまとめる。

Table 1: 比較演算子.
演算子 真となる条件 偽となる条件
== 等しい 異なる
!= 異なる 等しい
< 左が小さい 左が大きいか等しい
<= 左が小さいか等しい 左が大きい
> 左が大きい 左が小さいか等しい
>= 左が大きいか等しい 左が小さい

4.1 比較演算子 == と代入演算子 =

== と = はプロフェッショナルなプログラマであってもよく間違える。 しかも一度間違ってしまったら、それをなかなか見つけることができない。 目を皿のようにしてコードをチェックした結果、 10時間後に1個の演算子を間違っていたことを発見した、ということもしばしばある話だ。

if (10 == x) のように左辺に比べる対象を置くというのは良いお作法である。 もしこれを間違って if (10 = x) のように書いてしまったとしても、 Syntax Error(文法エラー)で止められる。 初心者のうちは Syntax Error が怖ろしく感じ、 なんとか動くコードを書きたいと思う気持ちが強いものだ。 しかしプログラミングにおいて一番怖ろしいのは、 「何となく動いてしまうけれども、動作がおかしい」というものだ。 Syntax Error は問題のある箇所を Ruby が行番号で教えてくれるので、 すぐに箇所を特定できるし、文書を当たれば正しい書き方はすぐに分かる。 何を怖れることがあろうか。 それよりも、問題の箇所が分からずに 数百行、そして時に数千・数万行のコードを 全部当たらなければならない状況を想像してみたら、 その怖ろしさが実感できるだろう。

5 字下げ, インデント

4年次進級に必要な条件が Tbl. 2 のようになっている学科があるとする。

Table 2: ある学科の4年次進級に必要な単位。
総単位数 110単位以上
総合, 基礎, 外国語, スポーツ科目 合計50単位以上(外国語科目10単位以上を含む)
専門教育科目 50単位以上(3年次までの専門科目の必修科目 31単位以上を含む)

これを素直にプログラムコードに直せば以下のようになるだろう。

# promotionCondition.rb
sougou  = 20
kiso    = 20
gaikoku = 10
sports  = 10
senmon_hisshu = 32
senmon_not_hisshu = 20

if (110 <= sougou + kiso + gaikoku + sports + senmon_hisshu + senmon_not_hisshu) #総単位数
if (50 <= sougou + kiso + gaikoku + sports) #共通教育科目 合計単位数
if (10 <= gaikoku)
if (50 <= senmon_not_hisshu + senmon_hisshu)
if (31 <= senmon_hisshu)
print "進級条件をクリアしています。\n"
else
print "専門教育科目 必修科目の単位数が足りません\n"
end
else
print "専門教育科目の単位数が足りません\n"
end
else
print "外国語科目の単位数が足りません\n"
end
else
print "共通教育科目の合計単位数が足りません\n"
end
else
print "総単位数が足りません\n"
end

はっきり言って、個々の if, else, end のどれがどれと 対応しているのか分からず、見るのに嫌気が差すだろう。 条件分岐はよく使われるので、書き方を工夫してやることで if, else, end の対応を見易くすることができる。

# promotionCondition.rb
sougou  = 20
kiso    = 20
gaikoku = 10
sports  = 10
senmon_hisshu = 32
senmon_not_hisshu = 20

if (110 <= sougou + kiso + gaikoku + sports + senmon_hisshu + senmon_not_hisshu) #総単位数
  if (50 <= sougou + kiso + gaikoku + sports) #共通教育科目 合計単位数
    if (10 <= gaikoku)
      if (50 <= senmon_not_hisshu + senmon_hisshu)
        if (31 <= senmon_hisshu)
          print "進級条件をクリアしています。\n"
        else
          print "専門教育科目 必修科目の単位数が足りません\n"
        end
      else
        print "専門教育科目の単位数が足りません\n"
      end
    else
      print "外国語科目の単位数が足りません\n"
    end
  else
    print "共通教育科目の合計単位数が足りません\n"
  end
else
  print "総単位数が足りません\n"
end

このコードのように、if のレベルに従って 行頭に空白を入れて開始桁を揃えてやることを、「字下げ」とか「インデント」などと呼ぶ。 同じ桁から始まる if, else, end がワンセットである。

6 課題

ある学科の卒業に必要な単位は Tbl. 3 の通りである。 これをプログラムコードに書き直せ。 必ず字下げをすること。

Table 3: ある学科の卒業に必要な単位。
全学共通科目 58単位以上
(内訳)
総合教育科目 16単位以上
基礎教育科目 29単位以上 (必修科目16単位、準必修科目8単位以上、基礎教育科目の実験科目3単位以上を含む)
外国語科目 10単位以上 (英語6単位、新修外国語4単位以上を含む)
健康スポーツ科学科目 3単位以上 (講義2単位、実習1単位以上を含む)
専門教育科目 76単位以上 (必修科目 47単位、選択科目29単位以上を含む)
合計単位数 134単位以上

  1. 論理値の扱いはプログラミング言語によって若干の差異がある。 たとえば標準的な C 言語では論理値型は定義されておらず、 整数値を使う (0以外ならば真、0ならば偽)。 C++ では論理値(bool)型を使用できる。 比較的新しい言語では大抵論理値を扱う仕組みがあるので、それを利用した方が良い。

  2. このあたりは演算子の優先順位という概念が入ってくる。 たとえば「1+2*3」と「1*2+3」を人間が計算する場合を考えれば、 どの演算を優先すべきかを人間が判断していることが分かるだろう。

    プログラム中に現れる全ての演算子の優先順位を記憶しておける人ならば、 このようなコードを書く資格はある。 しかしプログラムコードは他人も読みうるものであることを常に意識しなければならないため、 そのような人でもまぎらわしい書き方は避けるべきだ。 また、そのような有能な人でも 演算子の優先順位を覚えるよりももっと自分に鍛えるべき点があるだろう。