LoginSignup
6
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-11-03

元ネタ: マイナンバーのチェックデジットを計算する のアルゴリズムを他の言語に翻訳するのが流行っているらしいのでHaskellでもやってみました。マイナンバーの解説とかは書かないので元記事をぜひ読んでください。短いし読みやすいのですぐ読めると思います


validateMyNumber :: Int64 -> Bool
validateMyNumber myNumber
  | length (show myNumber) <= 12 =
      let (x:pn) = take 12 . map (`mod` 10) . iterate (`div` 10) $ myNumber
          qn     = [if n <= 6 then n + 1 else n - 5 | n <- [1..11]]
          y      = (`mod` 11) . sum . zipWith (*) qn $ pn
      in  x == if y <= 1 then 0 else (11 - y)
  | otherwise = False

1行ずつ解説していきます

validateMyNumber :: Int64 -> Bool

マイナンバーが正しいかどうかを判定する関数はマイナンバー(Int64)を受け取って正しいかどうか(Bool)を返します。副作用はありません

validateMyNumber myNumber

受け取ったマイナンバーに myNumber という名前をつけます。

  | length (show myNumber) <= 12 =

ガードを使って12桁以下の場合で分岐します。他にも書き方はあると思いますがわかりやすそうな実装にしました。

      let (x:pn) = take 12 . map (`mod` 10) . iterate (`div` 10) $ myNumber

let 節の始まりです。まずiterateでmyNumberを10で何回も割ったリストを作ります。具体例で見ると

>>> iterate (`div` 10) 12345
[12345,1234,123,12,1,0,0,0,0,0,...

このようになり後ろに0が無限個続きます。そしてmapで`各要素の10で割った余りを求めます

>>> map (`mod` 10) . iterate (`div` 10) $ 12345
[5,4,3,2,1,0,0,0,0,0,..

最後に take 12 で必要な個数だけ取ってきます

>>> take 12 . map (`mod` 10) . iterate (`div` 10) $ 12345
[5,4,3,2,1,0,0,0,0,0,0,0]

さらにlet (x:pn) =というパターンマッチを行うことでxに検査用数字を、pnに計算で使う$Pn$を代入しています。

          qn = [if n <= 6 then n + 1 else n - 5 | n <- [1..11]]

定義通りに$Qn$を計算します

          y = (`mod` 11) . sum . zipWith (*) qn $ pn

定義通りに$\Bigl( \displaystyle\sum_{n=1}^{11} P_n \times Q_n \Bigr) \% 11 $を計算してyと名づけておきます。

      in  x == if y <= 1 then 0 else (11 - y)

検査用数字と計算した値が同じかどうかを判定します。この結果がそのまま関数の返り値になります。

  | otherwise = False

桁数が12よりも大きければ正しいマイナンバーではありません。

テストも含めた全てのコードを貼っておきます。[gist]

import GHC.Int

validateMyNumber :: Int64 -> Bool
validateMyNumber myNumber
  | length (show myNumber) <= 12 =
      let (x:pn) = take 12 . map (`mod` 10) . iterate (`div` 10) $ myNumber
          qn     = [if n <= 6 then n + 1 else n - 5 | n <- [1..11]]
          y      = (`mod` 11) . sum . zipWith (*) qn $ pn
      in  x == if y <= 1 then 0 else (11 - y)
  | otherwise = False

main :: IO ()
main = do
    print $ validateMyNumber 123456789010 -- False
    print $ validateMyNumber 123456789011 -- False
    print $ validateMyNumber 123456789012 -- False
    print $ validateMyNumber 123456789013 -- False
    print $ validateMyNumber 123456789014 -- False
    print $ validateMyNumber 123456789015 -- False
    print $ validateMyNumber 123456789016 -- False
    print $ validateMyNumber 123456789017 -- False
    print $ validateMyNumber 123456789018 -- True
    print $ validateMyNumber 123456789019 -- False
    print $ validateMyNumber 023456789013 -- True

他言語での実装

6
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5