Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

説明

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

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

動作環境

  • Xcode 7.0
  • Swift 2.0

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

ソース

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

true/falseを返すパターン

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を1文字ずつ分割
    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))
}
結果
validateMyNumber("1234567890123") // => false
validateMyNumber("123456789010") // => 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を返すパターン

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)
}
結果2
validateMyNumber2("1234567890123") // -> Failure(ValidationErrorType.WrongLength(13))
validateMyNumber2("123456789010") // -> 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 する形で書きなおしたものを載せておきます。
// 上記ソースの後に貼っても動くように便宜上"__"をつけておきます。
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で計算する
C++ C++でマイナンバーのチェックデジットを計算する
C# C#でマイナンバーのチェックデジットを計算する
Ruby マイナンバーのチェックデジットを計算する
Python Python マイナンバー検証用モジュールを公開
PHP マイナンバーのチェックデジットを計算する PHP版
SQL マイナンバーのチェックデジットを計算する(SQLで)
Perl Perl5,Perl6でマイナンバーのチェックデジットを計算する
PowerShell WindowsPowerShell & C#マイナンバーのチェックデジットを計算する
Java マイナンバーのチェックデジットを計算する(Java編)
JavaScript JavaScriptとGroovyでマイナンバーのチェックデジットを計算する
Haskell Haskellでマイナンバーのチェックデジットを計算する
Haskell マイナンバー数字列の確認
Go マイナンバーのチェックデジットをGoで計算する
C, Go, Bash, Excel etc... マイナンバーのチェックデジットの検算をざっくり1割くらい高速化する(計ってないので知らんけど)

参考

マイナンバーに関しての省令等に関して。

sgr-ksmt
Software Engineer. Skill: iOS / Swift / Firebase / TypeScript / React
https://me.sgr-ksmt.org/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away