[Swift] paiza×Qiitaのやつ　（B問題：みんなでしりとり）

## なんかキャンペーンの奴

B問題の「みんなでしりとり」を解いてみました。

https://paiza.jp/works/mondai/b_rank_skillcheck_archive/word_chain


## 準備

入力用関数として以下のものを用意しています。
```swift
// １行文字列
func readStr() -> String {
  readLine()!
}

// １行整数
func readInt() -> Int {
  Int(readLine()!)!
}

// 空白文字で分割された文字列
func readSpStrs() -> [Substring] {
  readLine()!.split(separator: " ")
}

// 空白文字で分割された整数
func readSpInts() -> [Int] {
  readSpStrs().map { Int($0)! }
}

// 空白文字で分割された２つの整数をタプルで
func readInt2() -> (Int, Int) {
  let ints = readSpInts()
  return (ints[0], ints[1])
}

// 空白文字で分割された３つの整数をタプルで
func readInt3() -> (Int, Int, Int) {
  let ints = readSpInts()
  return (ints[0], ints[1], ints[2])
}

// N行分の整数
func readInt(_ n: Int) -> [Int] {
  (1...n).map { _ in readInt() }
}
```
## 解説？
### ルール
問題はあらかじめ用意されたリストの単語を使ってしりとりを行い、最後に残った人数とその番号を表示するというものです。
「ん」の代わりに末尾が「z」の単語はアウトになります。
また一度使った単語は使えません。使った場合はアウトです。

### 単語のリスト
今回の場合、間に合ってしまうのですが、単語の数やログの数が多い場合は`Arra`yを使うとTLEになると思いますので、こういったリストは`Set`にしておくといいでしょう。
今回は間に合うので`Array`のままです。

読み込みは以下の通り。使用した単語を削除していくため`var`で宣言してます。
```swift
// 単語リストを読み込み。
var wordList = (0..<k).map { _ in readStr() }
```

### しりとりの判定

なんだかよくわからない実装です。
```swift
// しりとり判定（アウトならfalse）の関数を返す。
func getJudgeSystem() -> (String) -> Bool {
  
  // ひとつ前の単語の末尾をストア。新たに開始した場合はnil
  var prevEnd: Character?
  
  func judge(_ word: String) -> Bool {
    // しりとり判定
    // OKなら次の頭文字をNGならnilを返す
    func nextHead() -> Character? {
      // 単語リストに存在するかをチェック
      guard let wordIndex = wordList.firstIndex(of: word) else { return nil }
      
      // 単語を使用済みにする（単語リストから削除する）
      wordList.remove(at: wordIndex)
      
      // zで終わるかチェック
      guard let lw = word.last, lw != "z" else { return nil }
    
      // 新たに開始した場合はOK
      if prevEnd == nil { return lw }
    
      // しりとりになっているかチェック
      return prevEnd == word.first ? lw : nil
    }
    
    prevEnd = nextHead()
    
    return prevEnd != nil
  }
  
  return judge
}
```
しりとりを判定する`judge`関数を作りたいのですが、しりとりですので前の単語そのものかその末尾を記録しておく必要があります。
これにクロージャと変数のキャプチャを用いています。
クロージャである内部関数`judge`が前の単語の末尾を記録した変数`prevEnd`をキャプチャすることでしりとりが成立しているかを判定しています。
さらにその内部関数である`nextHead`関数はしりとりが成立していれば次の頭文字を、成立していなければ`nil`を返します。
最終的に`prevEnd`が`nil`であればしりとりは失敗です。


### ログをチェック

最後にログをチェックして勝ち残りを出力します。
すでに変数`s`にログを読み込んでいます。

```swift
// 生存者リスト。ここに最後まで残ったものが回答
var survivor = Array(1...n)

// 現在のプレーヤーの↑の配列でのインデックス
var currentPlayerIndex = 0

let judge = getJudgeSystem()
for word in s {
  if judge(word) {
    // プレーヤーインデックスを進ませる
    currentPlayerIndex = (currentPlayerIndex + 1) % survivor.count
    continue
  }
  
  // プレーヤーを除外
  survivor.remove(at: currentPlayerIndex)
  
  // プレーヤーインデックスを進ませる
  // 現在のプレーヤーが削除されたので次のプレーヤーインデックスは変わらない
  // ただし、削除対象が配列内で一番最後だった場合0になるように再計算
  currentPlayerIndex = currentPlayerIndex % survivor.count
}

print(survivor.count)
survivor.forEach { print($0) }
```
ここは特に解説は不要でしょう。

## 全プログラムリスト
```swift
// １行文字列
func readStr() -> String {
  readLine()!
}

// １行整数
func readInt() -> Int {
  Int(readLine()!)!
}

// 空白文字で分割された文字列
func readSpStrs() -> [Substring] {
  readLine()!.split(separator: " ")
}

// 空白文字で分割された整数
func readSpInts() -> [Int] {
  readSpStrs().map { Int($0)! }
}

// 空白文字で分割された２つの整数をタプルで
func readInt2() -> (Int, Int) {
  let ints = readSpInts()
  return (ints[0], ints[1])
}

// 空白文字で分割された３つの整数をタプルで
func readInt3() -> (Int, Int, Int) {
  let ints = readSpInts()
  return (ints[0], ints[1], ints[2])
}

// N行分の整数
func readInt(_ n: Int) -> [Int] {
  (1...n).map { _ in readInt() }
}


/** ************************************
  ここから
************************************ **/

let (n, k, m) = readInt3()

// 単語リストを読み込み。
var wordList = (0..<k).map { _ in readStr() }

// ログ読み込み
let s = (0..<m).map { _ in readStr() }

// しりとり判定　アウトならfalse
func getJudgeSystem() -> (String) -> Bool {
  
  // ひとつ前の単語の末尾をストア。新たに開始した場合はnil
  var prevEnd: Character?
  
  func judge(_ word: String) -> Bool {
    // しりとり判定
    // OKなら次の頭文字をNGならnilを返す
    func nextHead() -> Character? {
      // 単語リストに存在するかをチェック
      guard let wordIndex = wordList.firstIndex(of: word) else { return nil }
      
      // 単語を使用済みにする（単語リストから削除する）
      wordList.remove(at: wordIndex)
      
      // zで終わるかチェック
      guard let lw = word.last, lw != "z" else { return nil }
    
      // 新たに開始した場合はOK
      if prevEnd == nil { return lw }
    
      // しりとりになっているかチェック
      return prevEnd == word.first ? lw : nil
    }
    
    prevEnd = nextHead()
    
    return prevEnd != nil
  }
  
  return judge
}


// 生存者リスト。ここに最後まで残ったものが回答
var survivor = Array(1...n)

// 現在のプレーヤーの↑の配列でのインデックス
var currentPlayerIndex = 0

let judge = getJudgeSystem()
for word in s {
  if judge(word) {
    // プレーヤーインデックスを進ませる
    currentPlayerIndex = (currentPlayerIndex + 1) % survivor.count
    continue
  }
  
  // プレーヤーを除外
  survivor.remove(at: currentPlayerIndex)
  
  // プレーヤーインデックスを進ませる
  // 現在のプレーヤーが削除されたので次のプレーヤーインデックスは変わらない
  // ただし、削除対象が配列内で一番最後だった場合0になるように再計算
  currentPlayerIndex = currentPlayerIndex % survivor.count
}

print(survivor.count)
survivor.forEach { print($0) }
```

## まとめ

***内部関数はクロージャです！！***
Swift界隈ではクロージャ式のみをクロージャと呼ぶ傾向がありますが、クロージャの本質は変数のキャプチャにあります。
***通常の内部関数もクロージャです！！***
クロージャ式だけがクロージャでないことに注意しましょう。
***グローバルスコープの関数だってクロージャです！！***