Hatena::Grouphaskell

すばらしき functional programming

2008-07-23合理的な預かり金額(Part1)

100円のお釣りのとき「はい、お釣り100 まんえ~ん」とか言っていたオバチャンはもう絶滅したのだろうか。まあ、別に惜しくはないけどね(絶滅前提)。

100万円はともかく、レジ(正確にはPOS端末?)の打ちまちがいで、とんでもない金額がお釣りとして表示されていたのは見たことがあります。打った本人の、コンビニのバイトのお姉ちゃんもビックリ。かなり前のことなので確かな記憶ではないのですが、レジの「000」のキーを連打してしまったため、100万円の預かり金額としてお釣りが計算されてしまったようです。なんてフォローしたらよいのやら。いや、こっちは客なのだからフォローする必要はないのですが、場の雰囲気としては笑いがほしいところ。そして私の口をついて出た言葉は

「すごーい」

でした。

しかし、あのレジ。100万円なんてありえない金額を平然と受け付けて、平然とお釣りを計算するその神経がわかりません。そこで、

「その買い物金額に対して、その預かり金額は正しいの?」

つまり、

「打ちまちがいじゃないの?」
合理的な預かり金額 :: 金額 -> 金額 -> Bool

って問題を考えます。

たとえば、251円の買い物に300円、301円、500円、1001円はありうるけど、270円、400円、20000円などはありえません。

[つづく...]

2008-07-14クレジットカードのチェックディジット

クレジットカードの最後の桁はチェックディジット(検査桁)です。アルゴリズムももちろん公開されています。たとえば、http://www.merriampark.com/anatomycc.htm (中盤以降)。

1桁置きに2倍して(10以上になった場合は9をひく)足し合わせ、10の倍数になっているかを確認すればよいのですが、ミソはチェックディジットである最後の桁を2倍しないように奇数桁目を2倍するのか偶数桁目を2倍するのか決めること。クレジットカード番号の長さは13桁~16桁で、偶数か奇数も決まっていない (知ってました? 奇数桁のものでお目にかかるのは AMEX くらいだと思うのですが、各種申込書なんかではいかにも 4桁-4桁-4桁-4桁 でさせたそうなものもあります。AMEX のカードには 4桁-6桁-5桁 でプリントされていますよ) ので、前述のURLのサンプルプログラム(java)でも最後の桁から最初の桁に向かって処理しています。

Haskell だと、こんな感じ?

{-
 - valid card  -- validate credit card check digit
 -}

cardNumber :: String -> [Int]
cardNumber [] = []
cardNumber (d:ds) | isDigit d = (fromEnum d - fromEnum '0'):cardNumber ds
                      where isDigit d = '0' <= d && d <= '9'
cardNumber (d:ds) = cardNumber ds  -- remove space or any other chars.


luhn :: [Int] -> [Int]  -- note that card digits are reversed
luhn []      = []
luhn (a:[])  = [a]
luhn (a:b:x) = a:oddDigit b:luhn x
                 where oddDigit n = if n < 5 then n*2 else n*2 - 9

validCard :: String -> Bool
validCard x = sum (luhn (reverse (cardNumber x))) `mod` 10 == 0

前述 URL からの例題

Main> validCard "4417 1234 5678 9112"
False
Main> validCard "4417 1234 5678 9113"
True
Main>