Skip to content

Instantly share code, notes, and snippets.

@oyakata
Last active December 19, 2015 12:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oyakata/5955744 to your computer and use it in GitHub Desktop.
Save oyakata/5955744 to your computer and use it in GitHub Desktop.
Luhnアルゴリズム(やってみた Python2.7)

Luhnアルゴリズムとは?

クレジットカード番号のチェックデジットなどに使う簡易的なアルゴリズム。

以下、Wikipediaから引用

:

Luhnアルゴリズムは、チェックディジットを含む数が正しいかどうかを検証する。

チェックディジットは通常、一意な番号に付与され、チェックディジットを含めた全体を認証番号などに使う。

この番号は以下のようにして検証できる。

  • 右端のチェックディジットを1番目として、偶数番目の桁を2倍にする。
  • 2倍にしていない桁も含め、各数字の総和を求める(2倍にした桁が2桁になった場合は、それぞれを別々の数字として加える)。
  • この総和の下1桁が0なら(つまり、10で割り切れる場合)、この番号はLuhnアルゴリズムでは正しく、そうでない場合は正しくない。

例として、49927398716 という番号を検証する場合を考える。

  • 右端から偶数番目の桁をそれぞれ2倍する: (1×2) = 2, (8×2) = 16, (3×2) = 6, (2×2) = 4, (9×2) = 18
  • それぞれの数字の総和を計算する(括弧で囲まれた数字は上のステップで2倍した桁): 6 + (2) + 7 + (1+6) + 9 + (6) + 7 + (4) + 9 + (1+8) + 4 = 70
  • 10で割りきれるか調べる: 70 mod 10 = 0 なので、この番号は正しい。

参考

Luhnアルゴリズム(Wikipedia) http://ja.wikipedia.org/wiki/Luhn%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0

ECサイトの動作テストに使える、クレジットカードのテスト番号一覧 http://www.webcreatorbox.com/tech/creditcard-test-numbers/

brainstorm Luhnアルゴリズムでクレジットカードの番号をチェック http://d.hatena.ne.jp/yuheiomori0718/20130427/1367074612

目的

Wikipediaのコード例以外の方法でPythonで書く。

「奇数番目と偶数番目で処理を分ける」、これをどう表現するかが考えどころ。

2で割り切れる(i % 2 == 0)という表現を使わずに書きたい。

# -*- coding:utf-8 -*-
import itertools
def check_digits(text):
"""itertools.cycleを使って実装。"""
def even_value(x):
y = x * 2
return y - 9 if y > 9 else y
values = (int(x) for x in reversed(text))
switcher = itertools.cycle((lambda x: x, even_value))
digits = (func(x) for func, x in itertools.izip(switcher, values))
return sum(digits) % 10 == 0
if __name__ == "__main__":
import unittest
class CheckDigitsTest(unittest.TestCase):
def _callFUT(self, value):
return check_digits(value)
def test(self):
"""Luhnアルゴリズム通りに値を評価すること。
参考 http://www.webcreatorbox.com/tech/creditcard-test-numbers/
"""
items = (
# Master Card
("5555555555554444", True),
("5105105105105100", True),
# Visa
("4111111111111111", True),
("4012888888881881", True),
# JCB
("3530111333300000", True),
("3566002020360505", True),
# Diners Club
("30569309025904", True),
("38520000023237", True),
# American Express
("378282246310005", True),
("371449635398431", True),
("378734493671000", True),
# Discover
("6011111111111117", True),
("6011000990139424", True),
# Invalid Digits
("6011000990139425", False),
)
for value, expect in items:
self.assertEqual(self._callFUT(value), expect, value)
unittest.main()
# -*- coding:utf-8 -*-
import itertools
def check_digits(text):
"""シーケンスのスライスとitertools.chainで実装。"""
def even_value(x):
y = x * 2
return y - 9 if y > 9 else y
odds, evens = text[::-2], text[:-1][::-2]
odds = map(int, odds)
evens = (even_value(int(x)) for x in evens)
return sum(itertools.chain(odds, evens)) % 10 == 0
# -*- coding:utf-8 -*-
import itertools
def check_digits(text):
"""itertools.cycleをrepeatとflattenで代用して実装。"""
def even_value(x):
y = int(x) * 2
return y - 9 if y > 9 else y
flatten = itertools.chain.from_iterable
repeater = flatten(itertools.repeat([1, 0]))
values = itertools.izip(
text[::-1],
(int if x else even_value for x in repeater)
)
return sum(func(x) for x, func in values) % 10 == 0
# -*- coding:utf-8 -*-
import itertools
def check_digits(text):
"""イテレータを共有し、itertools.izip_longestを使って実装。"""
def even_value(x):
y = x * 2
return y - 9 if y > 9 else y
def numbers(items):
values = iter(items)
odds = (int(x) for x in values)
evens = (even_value(int(x)) for x in values)
for x, y in itertools.izip_longest(odds, evens):
yield x + (y if y is not None else 0)
digits = numbers(text[::-1])
return sum(digits) % 10 == 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment