SwiftのWhile文

While文

For-in文のページでも少し触れましたが、while文はループを止めるタイミングが最初から分からない場合に使うのが有効です。Swiftのwhile文は2種類、whileループとrepeat-whileループ、が用意されています。

どちらのループも条件判定がfalseになるまで続きますが、2つのwhileループの大きな違いは

  • whileでは、各ループの最初で条件判定を行う
  • repeat-whileでは、各ループの最後で条件判定を行う

です。ここではまずwhileループの使い方について少し詳しく説明した後、repeat-whileの項でwhileとの違いを説明していきます。

Whileループ

Whileループの基本構造

whileループの基本的な構文は以下のようになります。

// whileループの基本的構文
while condition {
  statements
} 

先程も述べましたが、whileループでは、先ずconditionで条件判定が行われてから、本体であるstatementsが実行されます。whileループでは、上記のconditionfalseになるまでループ処理statementsを続行します。

したがって、while文は、

あらかじめ繰り返し回数の決まっていないループを回す

のに向いていると言えます。

Whileループで超簡易版人生ゲーム

公式マニュアルではwhileループ使用例としてSnakes and Laddersというボードゲームを使っていますが、個人的には余り馴染みがないので、ここでは超簡単な人生ゲームとします。

超簡易版人生ゲームの概念図

これは単にサイコロを振ってゴールマスに到達するだけのゲームですから、人生ゲームとすら呼べる代物ではないですが、先ずは単純化した方が理解しやすいと思いますのでご了承下さい。このゲームをプログラム化すると、こんな感じになります。

// 超簡易版人生ゲーム(whileループ)
// ボードをセット
let maxBoardNumber = 33
// 各ボードにスコアを設定
//var board = [Int](count:maxBoardNumber, repeatedValue:1000) // Swift 2.x
var board = Array(repeating: 1000, count: maxBoardNumber) // Swift 3以降

// スタートだけは0
board[0] = 0

// 現在位置とスコアを初期化
var position = 0
var score = 0
print("スタート!")
while position < maxBoardNumber-1 {
    // サイコロをふる 1...6
    let diceRoll = Int(arc4random_uniform(6))+1

    // 出た目の数だけススム
    position += diceRoll

    // 得点を加算
    if position < maxBoardNumber {
        score += board[position]
    }
    
    // サイコロの目、現在の位置、スコアを表示
    print("サイコロの目:\(diceRoll)| 今は\(position)マス目です| スコア=\(score)")
}
print("ゴール! あなたのスコアは\(score)点です\n")

プログラムとしては、公式マニュアルに掲載されているようなボードゲームとほぼ同じです。

実際に実行すると、以下のような出力になります。後で説明しますが、擬似乱数を使っているので実行するたびに結果が変わると思います。

スタート!
サイコロの目:6| 今は6マス目です| スコア=1000
サイコロの目:2| 今は8マス目です| スコア=2000
サイコロの目:1| 今は9マス目です| スコア=3000
サイコロの目:6| 今は15マス目です| スコア=4000
サイコロの目:1| 今は16マス目です| スコア=5000
サイコロの目:3| 今は19マス目です| スコア=6000
サイコロの目:1| 今は20マス目です| スコア=7000
サイコロの目:3| 今は23マス目です| スコア=8000
サイコロの目:1| 今は24マス目です| スコア=9000
サイコロの目:4| 今は28マス目です| スコア=10000
サイコロの目:6| 今は34マス目です| スコア=10000
ゴール! あなたのスコアは10000点です

では、順番に解説していきます。

ボードをセット

先ずボードを設定します。全部で33マス必要ですので、定数でボードの数を指定します。

// ボードの総数
let maxBoardNumber = 33

次に、各マス目に得点を付与するためにIntで配列を作ります。今回は全てのボードに一律で1000点を設定します。

// 各ボードのスコア
//var board = [Int](count:maxBoardNumber, repeatedValue:1000) // Swift 2.x
var board = Array(repeating: 1000, count: maxBoardNumber) // Swift 3以降

// スタートだけは0
board[0] = 0

上記の書き方で、全ての配列要素を一度に初期化(ここでは1000で初期化)できるので便利です。Swift 3から配列の初期化方法が少し変わっているので、比較のために以前のバージョンでの仕様も残しています。

配列の初期化をもっと面倒くさく実装すると

// Arrayの初期化
var board = [Int]()
for id in 0..<maxBoardNumber {
    // 配列リテラルで要素を追加
    board += [1000]
    // またはappend()関数で追加
    //board.append(1000)
}
    
// スタートだけは0
board[0] = 0

という具合になります。

一様乱数でサイコロをふる

今回の例ではここが一番難しいと思います。プログラム言語では一般的に(擬似)乱数と呼ばれる機能が実装されています。これは簡単に言うと、実行するたびにランダムな数字を返す機能(関数)です。

サイコロは1から6までの目があって、サイコロを振るとその目が同じ確率(1/6)で出るような道具ですよね。したがって、これをプログラム上で実装したい場合は一様乱数という擬似乱数が使えます。一様乱数は、ユーザが指定した範囲の数値をランダムかつ一様に返してくれる乱数です。

Swiftではarc4random_uniform()という一様乱数生成関数が用意されていますので、それを使います。

// サイコロをふる 1...6
let diceRoll = Int(arc4random_uniform(6))+1

arc4random_uniform()は引数(ひきすう)に整数をとります(正確にはUInt32)。引数(パラメータとも言います)というのは関数のかっこ()の中に入る値のことです。今回の例では6を引数としていますので、このarc4random_uniform(6)

「0から5までの整数をランダムかつ一様に返す」

ものになります。サイコロの目は1から6までなので、最後に+1を入れることによって、1から6までの数値を振れるサイコロを実装しています。

ちなみにarc4random_uniform()UInt32を返すので、型を合わせるためにIntにキャストしています。

出た目の数だけ進み、スコアを加算する

サイコロを振れるようになったので、次は出た目の数だけ進んで、止まったマス目に割り振られている得点を現在のスコアに加算する機能を実装します。

// 出た目の数だけススム
position += diceRoll

// 得点を加算
if position < maxBoardNumber {
    score += board[position]
}

サイコロの目の数だけ進む部分は簡単だと思います。得点を加算する際には、現在位置がボードの総数を超えないように、if文で条件をつけています。これは配列boardの要素数を超えてアクセスすることを未然に防ぐためです。

ゴールマスに到達したかチェック

ここまでで「サイコロをふる」、「スコアを加算する」という機能が実装されました。後はゴールに到達したかどうかの判定ができれば、超簡易版人生ゲームは完成です。

今回は32マス目をゴールと指定しましたので、「現在地が32マス目である」という条件を満たせばゴールです。

var position = 0
while position < maxBoardNumber-1 {
    ...
}

ここでようやくwhileの出番です。今ループを抜ける条件はposition < maxBoardNumber-1となっています。これは

「現在地が32マス未満であればループ続行」

という条件です。厳密に言うと、32マス目にピッタリ止まった場合がゴールになりますが、サイコロの目の出方によっては32マスを超える場合もあり得ます。したがって、上記のような条件判定にしてあります。

この例のように、サイコロを振ってゴールするまでの試行回数は毎回ランダムですので、このような繰り返し処理にはwhile文が最適です。

Repeat-Whileループ

Repat-Whileループの基本構造

repeat-whileループはwhileループと違って、ループの最後に条件判定を行います。

// repeat-whileループの基本的構文
repeat {
  statements
} while condition

つまり、最初の1回だけは無条件でstatementsが実行されます。

超簡易版人生ゲームをrepeat-whileで記述

先ほどの超簡易版人生ゲームをrepeat-whileループを使って書いてみます。

// 超簡易版人生ゲーム(repeat-whileループ)
// ボードをセット
let maxBoardNumber = 33
// 各ボードにスコアを設定
//var board = [Int](count:maxBoardNumber, repeatedValue:1000) // Swift 2.x
var board = Array(repeating: 1000, count: maxBoardNumber) // Swift 3以降

// スタートだけは0
board[0] = 0

// 現在位置とスコアを初期化
position = 0
score = 0
var diceRoll = 0
print("スタート!")
repeat {
    // 得点を加算
    score += board[position]

    // サイコロの目、現在の位置、スコアを表示
    print("サイコロの目:\(diceRoll)| 今は\(position)マス目です| スコア=\(score)")

    // サイコロをふる 1...6
    diceRoll = Int(arc4random_uniform(6))+1
    
    // 出た目の数だけススム
    position += diceRoll
} while position < maxBoardNumber-1
print("ゴール! あなたのスコアは\(score)点です\n")

結果を見てみると、

スタート!
サイコロの目:0| 今は0マス目です| スコア=0
サイコロの目:6| 今は6マス目です| スコア=1000
サイコロの目:4| 今は10マス目です| スコア=2000
サイコロの目:2| 今は12マス目です| スコア=3000
サイコロの目:5| 今は17マス目です| スコア=4000
サイコロの目:4| 今は21マス目です| スコア=5000
サイコロの目:6| 今は27マス目です| スコア=6000
ゴール! あなたのスコアは6000点です

という感じになります。大きな構造はwhileループと同じですが、最大の違いはスコアの加算部分です。

repeat-whileループの中身のコアな部分だけ抜粋してみます。

repeat {
    // 得点を加算
    score += board[position]

    // サイコロをふる 1...6
    diceRoll = Int(arc4random_uniform(6))+1
    
    // 出た目の数だけススム
    position += diceRoll
} while position < maxBoardNumber-1

先ず、スコアを加算する場所の条件判定がなくなっていて、かつスコア加算がループの先頭に移動しています。これは、条件判定がループの最後になったので、positionが配列要素の最大値である32を超えることがなくなったからです。

diceRollを変数にしたのは、print()で結果を表示したいためなので、print()を使用しないのであれば定数でも構いません。

WhileとRepeat-Whileはどう使い分ける?

今回はrepeat-whilewhileの違いを示すためにわざと中身を変えましたが、whileループの中身をそのままrepeat-whileループで流用しても問題ありません。

極論を言うと、どちらを使っても余り違いはありません。好みで変えても問題ないと思います。ただし、プログラムのロジック上、どちらか一方が明らかに有効な場合は、ケースバイケースで使い分けができるようになると良いですね。

まとめ

  • whileループは繰り返し回数が分からない場合に使用できる
  • whilerepeat-whileの違いは、最初に条件判定するか(while)、最後に条件判定するか(repeat-while)