沖の雑記帳

基本的には趣味に絡んで雑多な内容を色々と

awkでマイナンバーのチェックデジットを検証・集計

先日、Windowsバッチファイルでチェックデジットを計算するプログラムを書きました。

Windowsバッチファイルでマイナンバーのチェックデジットを計算 - 沖の雑記帳

元の記事と同様に参照元は以下のQiitaの記事です。

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

でも、集計するならawkとか使ったほうが楽だよね。それにまだawk版はなさそうじゃん?ってことで書きました。
ただ、awkって実計算部分は他の言語と大差ないんだよね
なので今回は解説らしい解説も特には不要かな?

検証環境

Windows7
GNU Awk 3.1.8

awkで気をつけること

配列や、文字数の開始は1からで0ではないこと。この辺は他の言語と違っています。
変数は全てグローバル。関数や、各パターンマッチで同じ変数名使うと書き換わるよ。*1
パターンマッチした時の処理にnextとか書いとかないと次に一致したパターンも延々処理していくよ。*2
と、こんなところでしょうか?

それでは続いてコードを貼り付けましょうね。

awkマイナンバーのチェックデジット検証・集計

# サマリ出力するデータの初期化
BEGIN {
    invalid_count = 0
    valid_count = 0
    unexpected_count = 0
}

# 全入力データを保存
{
    mynumber[FNR] = $0
}

# 数値のみの行にマッチ
/^[0-9]+$/ {
    sum = 0
    if (length($0) == 12) {
        print "mynumber : " $0
        digit = calc_checkdigit(substr($0,1,11))
        print "digit    : " digit
        if (digit == substr($0,12,1)) {
            print "validate : true"
            valid_count++
            result[FNR] = 1
        }
        else {
            print "validate : false"
            invalid_count++
            result[FNR] = 0
        }
    }
    else {
        print $0 " :requires 12-digit."
        unexpected_count++
    }
    print ""
    next
}

# 非数を1つでも含む行にマッチ
/[^0-9]+/ {
    print $0 " : Not be used non-numeric.\n"
    unexpected_count++
    next
}

# どのパターンにもマッチしてこなかった場合は不正データ
# 改行のみの場合などはここに来る
{
    print "Unexpected data.\n"
    unexpected_count++
}

# 検証結果のサマリ出力
END {
    print "Summary:"
    print "  Number of data : " FNR
    print "  Valid data     : " valid_count
    for (i = 1; i <= FNR; i++) {
        if (result[i]) {
            print "\t" mynumber[i]
        }
    }
    print "  Invalid data   : " invalid_count
    print "  Illegal data   : " unexpected_count
# 不正入力を出したい時だけ
#    for (i = 1; i <= FNR; i++) {
#        if (!result[i]) {
#            print "\t" mynumber[i]
#        }
#    }
}

# チェックデジットの計算
# 入力は11桁の数値列
function calc_checkdigit(number){
    for (i = 1; i < 12; i++) {
        p = substr(number,12-i,1)
        if (i <= 6) {
            q = i + 1
        }
        else {
            q = i - 5
        }
        sum += p * q
    }
    remainder = sum % 11
    if (remainder <= 1) {
        return 0
    }
    else {
        return 11 - remainder
    }
}

基本的にはコメントの通りです。
今回はそれぞれの入力に大してチェックデジットやtrue/falseなどを出力していますが、どちらかというと正しいものだけを抽出して出力したり表形式で正否を出力したりといった使い方の方が実用性があるでしょう。

一部を除いてほぼC言語なんかと変わらないですね。

以下検証関連
最後に入力データ数、チェック結果の数と、正しいデータのみを出力しています。

検証用データ

checkdata.txt

1234567890123
123456789010
123456789011
123456789012
123456789013
123456789014
123456789015
123456789016
123456789017
123456789018
123456789010
023456789013
1234567890

abcdefghijkl
12b45671b999
abcd111
486855818850
874413748700
700971250770
789947831400

検証結果

1234567890123 :requires 12-digit.

mynumber : 123456789010
digit    : 8
validate : false

mynumber : 123456789011
digit    : 8
validate : false

mynumber : 123456789012
digit    : 8
validate : false

mynumber : 123456789013
digit    : 8
validate : false

mynumber : 123456789014
digit    : 8
validate : false

mynumber : 123456789015
digit    : 8
validate : false

mynumber : 123456789016
digit    : 8
validate : false

mynumber : 123456789017
digit    : 8
validate : false

mynumber : 123456789018
digit    : 8
validate : true

mynumber : 123456789010
digit    : 8
validate : false

mynumber : 023456789013
digit    : 3
validate : true

1234567890 :requires 12-digit.

Unexpected data.

abcdefghijkl : Not be used non-numeric.

12b45671b999 : Not be used non-numeric.

abcd111 : Not be used non-numeric.

mynumber : 486855818850
digit    : 0
validate : true

mynumber : 874413748700
digit    : 4
validate : false

mynumber : 700971250770
digit    : 3
validate : false

mynumber : 789947831400
digit    : 5
validate : false

Summary:
  Number of data : 21
  Valid data     : 3
    123456789018
    023456789013
    486855818850
  Invalid data   : 12
  Illegal data   : 6

需要あるかはわかりませんがプロジェクト

GitHub - okiworks/awk-mynumber: initial release

追記(2016-04-11)

このページも含めリンクしてくださっている、yumedotoさんのページリンクを追加
様々な言語での実装状況まとめも作成されています。

qiita.com

*1:良い書き方ではないけど関数に関しては避ける方法はある

*2:特に何もなければ上から下まで全てのパターンをなめる