マイナンバーのチェックデジットをSwiftで計算する

<!--link refs-->
[link_C++]: http://qiita.com/yumetodo/items/600ca0df422010cbc4c1
[link_C#]:http://qiita.com/whitebell/items/5982055e6768960496db
[link_Ruby]:http://qiita.com/qube81/items/fa6ef94d3c8615b0ce64
[link_Python]:http://qiita.com/haminiku/items/bcf4bac82bd1ca62c746
[link_PHP]:http://qiita.com/rana_kualu/items/f36275032bbc6a3f18b4
[link_SQL]:http://qiita.com/damassima/items/873a5ba95c32d971eb26
[link_Perl]:http://qiita.com/B73W56H84/items/fa7fcadf7779ac7dbe8b
[link_PowerShell]:http://qiita.com/shoooo/items/42bbdd9023c14661da0a
[link_Java]:http://qiita.com/deaf_tadashi/items/2e7249371f2138a6a7fc
[link_JavaScript]:http://qiita.com/mas0061/items/19ffdda24a9df5f4e6e2
[link_Haskell]:http://qiita.com/lotz/items/e1f3c649227271c381f6
[link_Haskell2]:http://qiita.com/nobsun/items/fc151dea83114146ad51
[link_go]:http://qiita.com/qube81/items/f66a38b28ec58bc5c4da
[link_C_Bash_Excel]:http://qiita.com/genta/items/fb25ce50592134d3fbb8
[link_objc_c]:http://qiita.com/yangci/items/04eacf6236716b7ea8d2
[参考1]:http://law.e-gov.go.jp/announce/H26F11001000085.html
[参考2]:http://qiita.com/qube81/items/fa6ef94d3c8615b0ce64

<!--        -->
## 説明
たまには息抜きも必要と思って、
[マイナンバーのチェックデジットを計算する][link_Ruby]を参考にさせて頂いて、Swiftにて書いてみました。

もっと短く簡略化出来るよ！とか、こうしたほうがもっとスッキリ書けるよ！
とかあればコメントお願いします！

## 動作環境
- Xcode 7.0
- Swift 2.0

PlayGroundで試したので、ソースを貼り付ければそのまま確認できると思います！

## ソース

折角Swiftで書くということなので、true/falseを返すパターンともう一つ、
拙いながらenumを使ってよくある**Result**の真似事をして*Success/Failure*を返すパターンと用意しました。
(*ErrorType*とかの勉強もかねて...)
内部の計算ロジックは同じです。
また、今回は半角数字12桁のみとして、全角数字の場合は除いています。

### true/falseを返すパターン

```swift:true/falseを返すパターン
func validateMyNumber(numstr: String) -> Bool {
    
    // 桁数チェック
    let length = numstr.utf8.count
    if length != 12 {
        return false
    }
    
    // 正規表現を使って数字のみかどうかチェック
    let exp = try! NSRegularExpression(pattern: "^[0-9]+$", options: [])
    if exp.matchesInString(numstr, options: [], range: NSMakeRange(0, length)).count == 0 {
        return false
    }
    
    // 扱いやすいように一旦Stringを１文字ずつ分割
    var characters = numstr.characters.map{ String($0) }
    // チェックデジットをcharactersからpop
    // pop後はcharactersの中身は12->11個になります。
    let checkDigit = Int(characters.removeLast())!
    
    // 検査用数字の計算
    var pq = 0
    
    for (index, num) in characters.reverse().enumerate() {
        let n = index + 1
        let p = Int(num)!
        let q = (n >= 7) ? n - 5 : n + 1
        pq += p * q
    }
    
    // 検査
    let remainder = pq % 11
    return (remainder <= 1) ? (checkDigit == 0) : (checkDigit == (11 - remainder))
}
```

```swift:結果
validateMyNumber("1234567890123") // => false
validateMyNumber("１２３４５６７８９０１０") // => false
validateMyNumber("A23456789010") // => false
validateMyNumber("123456789010") // => false
validateMyNumber("123456789011") // => false
validateMyNumber("123456789012") // => false
validateMyNumber("123456789013") // => false
validateMyNumber("123456789014") // => false
validateMyNumber("123456789015") // => false
validateMyNumber("123456789016") // => false
validateMyNumber("123456789017") // => false
validateMyNumber("123456789018") // => true
validateMyNumber("123456789019") // => false
validateMyNumber("023456789013") // => true
```

### Success/Failureを返すパターン

```swift:Success/Failureを返すパターン
enum ValidationResult {
    case Success
    case Failure(ValidationErrorType)
}

enum ValidationErrorType: ErrorType {
    case WrongLength(Int) // マイナンバーの桁数(文字数)が不正の場合
    case NotANumber // 数字以外が含まれている場合
    case InvalidNumber // 検査して不正なマイナンバーだった場合
}

// 便宜上関数名末尾に2をつけています。
func validateMyNumber2(numstr: String) -> ValidationResult {
    
    let length = numstr.utf8.count
    if length != 12 {
        return ValidationResult.Failure(.WrongLength(length))
    }
    
    let exp = try! NSRegularExpression(pattern: "^[0-9]+$", options: [])
    if exp.matchesInString(numstr, options: [], range: NSMakeRange(0, length)).count == 0 {
        return ValidationResult.Failure(.NotANumber)
    }
    
    var characters = numstr.characters.map{ String($0) }
    let checkDigit = Int(characters.removeLast())!
    
    var pq = 0
    
    for (index, num) in characters.reverse().enumerate() {
        let n = index + 1
        let p = Int(num)!
        let q = (n >= 7) ? n - 5 : n + 1
        pq += p * q
    }
    
    let remainder = pq % 11
    let valid = (remainder <= 1) ? (checkDigit == 0) : (checkDigit == (11 - remainder))
    return valid ? ValidationResult.Success : ValidationResult.Failure(.InvalidNumber)
}
```

```swift:結果2
validateMyNumber2("1234567890123") // -> Failure(ValidationErrorType.WrongLength(13))
validateMyNumber2("１２３４５６７８９０１０") // -> Failure(ValidationErrorType.WrongLength(36))
validateMyNumber2("A23456789010") // => Failure(ValidationErrorType.NotANumber)
validateMyNumber2("123456789010") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789011") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789012") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789013") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789014") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789015") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789016") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789017") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("123456789018") // => Success
validateMyNumber2("123456789019") // => Failure(ValidationErrorType.InvalidNumber)
validateMyNumber2("023456789013") // => Success
```

後者のパターンだと、何故だめだったのかわかるので、Failureの中のErrorTypeに応じて処理を行う場合にはいいのかなと。

有効/無効なマイナンバーかわかれば良い場合と、
なぜ無効になっているのかまで必要な場合で使い分ければいいのかなって思います！

## 追記(2015/11/16)
@yumetodo さんより、ご指摘頂いたのも踏まえて、

- マイナンバーが合ってる/合ってない →boolで返却
- 桁数が不正/数字以外が含まれる → エラーをthrow
する形で書きなおしたものを載せておきます。

```swift
// 上記ソースの後に貼っても動くように便宜上"__"をつけておきます。
func __validateMyNumber(numstr: String) throws -> Bool {
    
    let length = numstr.utf8.count
    if length != 12 {
        throw ValidationErrorType.WrongLength(length)
    }
    
    let exp = try! NSRegularExpression(pattern: "^[0-9]+$", options: [])
    if exp.matchesInString(numstr, options: [], range: NSMakeRange(0, length)).count == 0 {
        throw ValidationErrorType.NotANumber
    }
    
    var characters = numstr.characters.map{ String($0) }
    let checkDigit = Int(characters.removeLast())!
    
    var pq = 0
    
    for (index, num) in characters.reverse().enumerate() {
        let n = index + 1
        let p = Int(num)!
        let q = (n >= 7) ? n - 5 : n + 1
        pq += p * q
    }
    
    let remainder = pq % 11
    return (remainder <= 1) ? (checkDigit == 0) : (checkDigit == (11 - remainder))
}

func tryValidation(numstr: String) {
    do {
        let valid = try __validateMyNumber(numstr)
        print("\(numstr) is",(valid ? "valid" : "invalid"))
    } catch ValidationErrorType.WrongLength(let length) {
        print("\(numstr) is wrong length: \(length)")
    } catch ValidationErrorType.NotANumber {
        print("\(numstr) is not a number")
    } catch {
        print("unknown error occurred")
    }
}

tryValidation("123456789012") // => "123456789012 is invalid\n"

```

今回は*tryValidation:*メソッドにてdo-try-catchでそれぞれ捕捉して結果を出力してますが、
実際にはユーザーに入力をさせて、エラーをcatchしたら入力をリトライしてもらう、そうでない場合は合ってるか合ってないかをユーザーに伝えるような処理とかになるかなと思います。


## 他の言語で実装されている記事
結構ありますね..!これでマイナンバー届いてもしっかり確認できますね！

| 言語 | 記事 |
| :-----------: | :----------- |
|Objctive-C|[マイナンバーのチェックディジットをObjective-Cで計算する][link_objc_c]|
| C++ | [C++でマイナンバーのチェックデジットを計算する][link_C++] |
| C# | [C#でマイナンバーのチェックデジットを計算する][link_C#] |
| Ruby | [マイナンバーのチェックデジットを計算する][link_Ruby] |
| Python | [Python マイナンバー検証用モジュールを公開][link_Python] |
| PHP | [マイナンバーのチェックデジットを計算する PHP版][link_PHP] |
| SQL | [マイナンバーのチェックデジットを計算する（SQLで）][link_SQL] |
| Perl | [Perl5,Perl6でマイナンバーのチェックデジットを計算する][link_Perl] |
| PowerShell | [WindowsPowerShell & C#マイナンバーのチェックデジットを計算する][link_PowerShell] |
| Java |[マイナンバーのチェックデジットを計算する（Java編）][link_Java] |
| JavaScript | [JavaScriptとGroovyでマイナンバーのチェックデジットを計算する][link_JavaScript] |
| Haskell | [Haskellでマイナンバーのチェックデジットを計算する][link_Haskell] |
| Haskell | [マイナンバー数字列の確認][link_Haskell2] |
| Go | [マイナンバーのチェックデジットをGoで計算する][link_go] |
| C, Go, Bash, Excel etc... | [マイナンバーのチェックデジットの検算をざっくり1割くらい高速化する(計ってないので知らんけど)][link_C_Bash_Excel] |
### 参考
マイナンバーに関しての省令等に関して。

- [行政手続における特定の個人を識別するための番号の利用等に関する法律の規定による
通知カード及び個人番号カード並びに情報提供ネットワークシステムによる
特定個人情報の提供等に関する省令][参考1]

- [マイナンバーのチェックデジットを計算する][参考2]