blog/content/post/1970/01/01/1970-01-01-00000053.md

23 KiB
Raw Blame History

title author date wordtwit_post_info categories
クレジットカードの判別アルゴリズム kazu634 1969-12-31
O:8:"stdClass":13:{s:6:"manual";b:0;s:11:"tweet_times";i:1;s:5:"delay";i:0;s:7:"enabled";i:1;s:10:"separation";s:2:"60";s:7:"version";s:3:"3.7";s:14:"tweet_template";b:0;s:6:"status";i:2;s:6:"result";a:0:{}s:13:"tweet_counter";i:2;s:13:"tweet_log_ids";a:1:{i:0;i:4605;}s:9:"hash_tags";a:0:{}s:8:"accounts";a:1:{i:0;s:7:"kazu634";}}
gauche
Lisp

この前「あなたのクレジットカード番号に潜む規則性 モスマン」を読んでいて、いつか実装しようと思っていたのだけれど、ついに完成。

基本的なアルゴリズムは

  1. クレジットカード番号の後ろから数えて*1奇数桁だけを合計する。
  2. クレジットカード番号の後ろから数えて偶数桁を2倍して合計する。2倍して10以上になるときは9を引く。
  3. 1と2の数字を合計する

正しいカード番号ならこの合計が10で割り切れる。

あなたのクレジットカード番号に潜む規則性 モスマン

です。gaucheで順番に考えていきました。

16桁かどうかの判別

とりあえずカード番号は16桁だから、与えられた引数が16桁の数字かどうかを調べようと考えました。

(define (chkdigit number)
(if (16digit? number)
;; 16桁だったら処理を開始
#f ;; 16桁じゃないから終了))

gaucheだと16桁かどうかというのは、正規表現を使えばいいと考えたのでrxmatchを使います。

(define (16digit? number)
(if (rxmatch #/\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d/ (number->string number))
#t
#f))

奇数桁だけを合計する・偶数桁を2倍して合計するetc

これらはおそらくfoldとmapを使えばできる。

(fold +  奇数桁を集めたリスト)
(fold +  (map (lambda (n) (* 2 n)) 偶数桁を集めたリスト))

リストから奇数桁・偶数桁だけを取り出す

与えられた16桁の数字をリストに変換しようと思ったので、この前Shiroさんに教えていただいた手続きをそのまま使います。

(define (number->list n)
(let loop ((n n) (r '()))
(if (< n 10)
(cons n r)
(loop (quotient n 10) (cons (modulo n 10) r)))))

これで数字をリストに変換できたので、奇数桁と偶数桁だけを取り出す部分を考えます。

奇数桁というのは、リストの要素数が偶数の時の(car リスト)の結果になります。偶数桁というのは、リストの要素数が奇数の時の(car list)の結果になります。それを一つのリストにすればいいので:

(define (even_list source)
(cond
[(null? source) '()]
[(even? (length source)) (even_list (cdr source))]
[(odd? (length source)) (cons (car source) (even_list (cdr source)))]))
(define (odd_list source)
(cond
[(null? source) '()]
[(even? (length source)) (cons (car source) (odd_list (cdr source)))]
[(odd? (length source)) (odd_list (cdr source))]))

このような形になるかと思います。

奇数桁だけを合計する・偶数桁を2倍して合計するetc

奇数桁だけを合計するというのは、foldを使えばできそうです。

(define (proc_odd list)
(fold +  (odd_list リスト)))

「偶数桁を2倍して合計する。2倍して10以上になるときは9を引く」というのは、

  1. 偶数桁のそれぞれを2倍する。2倍して10以上になるときは9を引く
  2. その結果を足し合わせる

足し合わせる部分はfoldを使えばできそうです:

(define (proc_even list)
(fold +  偶数桁のそれぞれを2倍する。2倍して10以上になるときは9を引いたリスト)

問題は「偶数桁のそれぞれを2倍する。2倍して10以上になるときは9を引く」の部分になります。この部分はmapを使えばいいはず。こんな感じで:

gosh> (map
(lambda (n)
(if (<= 10 (* 2 n))
(- (* 2 n) 9) ;; 10以上なら9を引く
(* 2 n))) ;; そうでなければ、2倍する
'(1 2 3 4 5)) ;; 処理対象のリスト
(2 4 6 8 1)

だから結局、

(define (proc_even list)
(fold +  (map
(lambda (n)
(if (<= 10 (* 2 n))
(- (* 2 n) 9)
(* 2 n)))
偶数桁のリスト)))

となります。

正しいカード番号ならこの合計が10で割り切れる

ここまででだいぶ部品ができてきたので、それを一つにまとめます:

(define (judge_card リスト)
(let ((temp
(+ (proc_odd リスト) (proc_even リスト)))) ;; ローカル変数tempに各桁の合計を代入
(if (= (modulo temp 10) ) ;; tempが10で割り切れる
#t ;; 割り切れたらカード番号は正しい
#f))) ;; そうでなければカード番号は不正

まとめ

一番上位の手続きはchkdigitなので、それにこれまでの結果をまとめます。後、カード番号を逆順にするのも忘れずに:

(define (chkdigit number)
(let ((temp (reverse (number->list number))))
(if (16digit? number)
(judge_card temp)
(print "Card Number must be 16 digits.")
)))
(define (number->list n)
(let loop ((n n) (r '()))
(if (< n 10)
(cons n r)
(loop (quotient n 10) (cons (modulo n 10) r)))))
(define (16digit? number)
(if (rxmatch #/\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d\d/ (number->string number))
#t
#f))
(define (even_list source)
(cond
[(null? source) '()]
[(even? (length source)) (even_list (cdr source))]
[(odd? (length source)) (cons (car source) (even_list (cdr source)))]))
(define (odd_list source)
(cond
[(null? source) '()]
[(even? (length source)) (cons (car source) (odd_list (cdr source)))]
[(odd? (length source)) (odd_list (cdr source))]))
(define (proc_odd list)
(fold +  (odd_list list)))
(define (proc_even list)
(fold +  (map
(lambda (n)
(if (<= 10 (* 2 n))
(- (* 2 n) 9)
(* 2 n)))
(even_list list))))
(define (judge_card list)
(let ((temp
(+ (proc_odd list) (proc_even list))))
(if (= (modulo temp 10) )
#t
#f)))

「gauche」に関連する最近のエントリ