Swiftの条件分岐(if、switch、where)

条件分岐

条件分岐です。条件分岐は英語ではconditional statementsと言います。Swiftの条件分岐ではifswitch構文が使用できます。この2つの構文はどう使い分けたら良いでしょうか?公式マニュアルにも書いてありますが、

  • 条件分岐が少ない場合、ifが最適
  • 条件分岐が多い場合、またパターンマッチ(pattern matching)を取り扱う場合はswitchが最適

とあります。「少ない」とはどれくらいか?という疑問に答えるのは難しいですが、実際に色々使ってみて、これはif文だと難しいと思えばswitch文に切り替えたり、その逆も可能です。基本的には、どちらの条件分岐もほぼ同等の処理が可能ですから、好みで使っていくのも良いかと思います。

If文

If文の最も単純な基本構文

If文の基本的な構文は

// Ifの基本的な構文
if condition {
  statements
}

のような形になります。ifの中身statementsは、conditiontrueの時のみ実行されます。もしconditionfalseの場合、ifの中身は実行されることなくプログラムは続行します。

If文の使用例

公式マニュアルのif文の使い方例では温度の単位に華氏(ファーレンハイト、F)を用いていますが、馴染みのある摂氏(セルシウス、C)で書き直してみましょう。

// If文の最も単純な使用例
var temperatureInCelsius = -5
if temperatureInCelsius <= 0 {
  print("とても寒いです。こたつに入ることを勧めます。")
}
//"とても寒いです。こたつに入ることを勧めます。"と表示

この例では、温度が0度以下の場合にif文の本体を実行する、という条件になっています。もし、温度が0度よりも大きい場合はif文の本体が実行されません。試しに温度を色々と変更してみると良く分かると思います。

Else文の使用例

もしifの条件がfalseだった場合、何か処理をしたい場合があります。そういう時はelse構文が使えます。先程のサンプルコードを流用します。

// else文の使用例
temperatureInCelsius = 10
if temperatureInCelsius <= 0 {
    print("とても寒いです。こたつに入ることを勧めます。")
} else {
    print("それほど寒くありません。腹巻きで十分です")
}
//"それほど寒くありません。腹巻きで十分です"と表示

今、温度を10度にセットしたので、最初のifの条件はfalseになります。したがって、else内のstatementsが実行されます。

elseを使った場合、ifelse内の処理のどちらかが必ず実行されます。

Else if文の使用例

条件分岐はもっと細かく設定することもできます。

// else if文の使用例
temperatureInCelsius = 32
if temperatureInCelsius <= 0 {
    print("とても寒いです。こたつに入ることを勧めます。")
} else if temperatureInCelsius >= 30 {
    print("とても暖かいです。半袖半ズボンでも大丈夫。")
} else {
    print("それほど寒くありません。腹巻きで十分です")
}
//"とても暖かいです。半袖半ズボンでも大丈夫。"と表示

追加の条件を足す場合はelse if構文を使います。上の条件分岐を図にすると、以下のようになります。

If, else if, else構文のポンチ絵

また、else if構文は複数つなげることも可能です。

// else ifを複数つなげる
temperatureInCelsius = 25
if temperatureInCelsius <= 0 {
    print("とても寒いです。こたつに入ることを勧めます。")
} else if temperatureInCelsius >= 30 {
    print("とても暖かいです。半袖半ズボンでも大丈夫。")
} else if temperatureInCelsius >= 20 {
    print("暖かいです。暖房は必要ありません。")
} else {
    print("それほど寒くありません。腹巻きで十分です")
}
//"暖かいです。暖房は必要ありません。"と表示

elseを使用すると、全ての条件を満たさない場合に実行されるわけですが、それが必要でない場合は取り除いても問題ありません。つまり、elseは必須ではありません(そういう意味ではelse ifも任意です)。

// elseは任意
temperatureInCelsius = 13
if temperatureInCelsius <= 0 {
    print("とても寒いです。こたつに入ることを勧めます。")
} else if temperatureInCelsius >= 30 {
    print("とても暖かいです。半袖半ズボンでも大丈夫。")
} else if temperatureInCelsius >= 20 {
    print("暖かいです。暖房は必要ありません。")
}
//何も表示されない

上記の例では、設定温度はどの条件にも当てはまりませんので、何も表示されることなくプログラムを続行します。

Switch文

Switch文は条件分岐の一つで、指定したい条件が多い場合には、ifよりも有効です。

Switchの基本構文

Switchの基本構文は以下のようになります。

// Switchの基本的な構文
switch some value to consider {
  case value 1:
    respond to value 1
  case value 2, value 3:
    respond to value2 or value 3
  default:
    otherwise, do something else
}

少し複雑ですが、ifとの比較で見ると理解しやすいと思います。

switchには複数のcaseが必要で、これはif文で言う所のifelse ifに対応します。switchの直後、some value to considerには、条件判定したい変数や定数を指定します。

caseには、分岐させる条件を指定します。

  case value 1:

はif文でのif conditionに対応しますが、value 1はブール値でなく、何かしらの値です。

1つのcaseで複数の条件を指定したい場合は

  case value 2, value 3:

のようにコンマ,で区切って指定できます。

値が複数行にまたがっても問題ありません。

  case value 2,
  value 3:

指定したい値が多い場合には、このような書き方になるかもしれません。

全ての条件を細かく指定することも可能ですが、ある特定の条件のみ指定して残りは気にしない場合はdefaultというキーワードが使えます。これはif文ではelseに対応するものです。

Switchの使用例

実際の使用例を見てみましょう。

// switchの使用例
var someCharacter: Character = "あ"
switch someCharacter {
    case "あ","い","う","え","お":
        print("\(someCharacter)は母音です")
    case "か","き","く","け","こ":
        print("\(someCharacter)は子音です")
    default:
        print("\(someCharacter)は母音でも子音でもありません")
}
//"あは母音です"と表示

少し手抜きの例ですが、文字型であるsomeCharacterに対して、switchの条件分岐で、母音か子音を判定しています。上記の例の場合、子音に「か行」しか指定していませんので、それ以外の文字をインプットとした場合、一番最後のdefaultが実行されます。

Swiftのcaseは賢い (No Implicit Fallthrough)

C言語などに精通されている方は、上記の例に少し違和感を覚えたかもしれません。Swiftでは、caseで条件判定が実行された場合、その他のcaseを無視(スキップ)してswitchを抜ける仕様になっています。C言語では、明示的にbreakを挿入しなければ、条件判定の結果に関わらず、全ての条件分岐を調べていくような仕様になっています。

また、Swiftのcaseは必ず実行文が(少なくとも1つ)必要です。したがって、実行文なしのcaseはコンパイルエラーになります。

// switchの使用例、実行文は必ず入れる
var someCharacter: Character = "あ"
switch someCharacter {
    case "あ","い","う","え","お":
        print("\(someCharacter)は母音です")
    case "か","き","く","け","こ":
        print("\(someCharacter)は子音です")
    case "さ","し","す","せ","そ":

    default:
        print("\(someCharacter)は母音でも子音でもありません")
}
//コンパイルエラー:'case' label in a 'switch' should have at least one executable statement

範囲演算子を使ったInterval Matching

子音を指定するのに、ひらがなを全部書き下すのは面倒くさいです。そんな時は範囲演算子を使って楽をすることができます。

// 範囲演算子でinterval matching
someCharacter = "た"
switch someCharacter {
    case "あ","い","う","え","お":
        print("\(someCharacter)は母音です")
    case "か"..."ん":
        print("\(someCharacter)は子音です")
    default:
        print("\(someCharacter)は母音でも子音でもありません")
}
//"たは子音です"と表示

範囲演算子"か"..."ん"で全ての子音を指定しています。もちろん、公式マニュアルの例のように、整数の範囲指定も可能です。

Switch文でのTuplesの使用

Tuplesを使うことにより、1つのcaseに対して複数の値を判定させることができます。公式マニュアルの例が非常に分かりやすいので流用します。

// Tuplesを使ったswitchの条件分岐(公式マニュアルより)
let somePoint = (1,1)
switch somePoint {
case (0, 0):
    print("(0, 0)は原点です")
case (_, 0):
    print("(\(somePoint.0), 0)はx軸上です")
case (0, _):
    print("(0, \(somePoint.1))はy軸上です")
case (-2...2, -2...2):
    print("(\(somePoint.0), \(somePoint.1))は箱の中にあります")
default:
    print("(\(somePoint.0), \(somePoint.1))は箱の外にあります")
}
//"(1, 1)は箱の中にあります"と表示

図示すると以下のようになります。下の図と、実際のswitch文の条件を比較して見て下さい。

switch文でのtuplesの使用例

順番に見ていきます。最初の条件(0, 0)は自明で、somePointの値が両方とも0の場合です。

次に(_, 0)のアンダースコア(_)ですが、これはワイルドカードと呼ばれる「正規表現」(regular expression)の1つです。この表現の場合、「最初の要素はどんな値でも良い」という条件ですから、2次元のグラフ上で表現した時の横軸(x軸)に対応します。3番目の例も同様で、(0, _)はy軸を表します。

最後のcase (-2...2, -2...2)は、図で言うと水色で囲まれた正方形の内側になります。Closed range operator(...)で指定していますので、-2と2を含んだ正方形の内側が条件となっています。

上記の例ですと、実は条件(0, 0)というのは他の全てのcaseに該当します。このような場合、Swiftでは一番最初に合った条件に従って処理を行います。上記の例では、case (0, 0)が最初の条件ですから問題ありませんが、caseの順番を入れ替えた場合にどう振る舞うのか、色々とテストしてみるとおもしろいと思います。

Value Bindings

うまい和訳が思いつきませんが、value bindingというのは、switch文のcaseで指定する値を定数・変数に格納することです。

// Value bindings
let anotherPoint = (2, 3)
switch anotherPoint {
case (let x, 0):
    print("\(x)はx軸上にあります")
case (0, let y):
    print("\(y)はy軸上にあります")
case (let x, let y):
    print("(\(x), \(y))はx、y軸上にはありません")
}

このサンプルも公式マニュアルを流用しています。上記の例のように、caseで指定する値を一時定数に格納し、それをprint()関数で表示しています。これらの定数(変数)は、それを宣言したcaseのスコープ内でのみ有効です。

また、このswitch文にはdefaultがありませんが、これは最後のcase (let x, let y)が、その他全ての条件を網羅するためです。

Where

whereは追加の条件を付けたい時に使える構文です。Switch文に限らず、他の構文と組み合わせることも可能です。

// whereの使用例
let yetAnotherPoint = (1, 1)
let radius = 2
switch yetAnotherPoint {
case (let x, let y) where x*x + y*y == radius*radius:
    print("(\(x),\(y))は半径\(radius)の円周上にあります")
case (let x, let y) where x*x + y*y < radius*radius:
    print("(\(x),\(y))は半径\(radius)の円内にあります")
case (let x, let y):
    print("(\(x),\(y))は任意の点です")
}
//"(1,1)は半径2の円内にあります"

whereを使って、半径radiusで指定される円の境界、内側で条件判定を組んでみました。

switchとwhereの併用

この例でも、最後のcaseがその他全ての条件を含むので、defaultは必要ありません。

まとめ

  • 条件分岐が少ない場合はifが便利
  • else ifelseは任意
  • switchcaseで条件判定がtrueの場合、自動的にそれ以外の条件をスキップする(no implicit fallthrough)
  • switch構文は多機能(interval matching、tuples、value bindingsなど)
  • whereは、さらに条件を追加したい時に使える

「Swiftの制御構文」に戻る