23 KiB
title | author | date | wordtwit_post_info | categories | |||
---|---|---|---|---|---|---|---|
クレジットカードの判別アルゴリズム | kazu634 | 1969-12-31 |
|
|
この前「あなたのクレジットカード番号に潜む規則性 – モスマン」を読んでいて、いつか実装しようと思っていたのだけれど、ついに完成。
基本的なアルゴリズムは
- クレジットカード番号の後ろから数えて*1奇数桁だけを合計する。
- クレジットカード番号の後ろから数えて偶数桁を2倍して合計する。2倍して10以上になるときは9を引く。
- 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を引く」というのは、
- 偶数桁のそれぞれを2倍する。2倍して10以上になるときは9を引く
- その結果を足し合わせる
足し合わせる部分は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)))