银行卡号校验

【问题】你在自动转帐机器上操作,有没有过这样的担心:万一我把卡号输错了一位,把钱打给了一个陌生人可怎么办呢?银行的人会告诉你,上面会显示对方名字中的一个字,比如,对方叫“张伟”,会显示 **伟,可要是碰巧这个陌生人也叫xx伟呢?

其实,完全没有担心的必要,银行卡号如果输错了一位,将会是一个无效的号码,不会对应任何人的帐号。如果输错2位呢?那会有小概率成为他人的帐号(错那么多,你敢说不是故意的?)。

银行卡号采用了一种叫作:Luhn算法的校验规则。其具体为:

  1. 从右边开始算,第偶数位上的数字乘以2,结果为2位数的,十位与个位相加。
  2. 第奇数位上的数保持不变。
  3. 变化后的数字总和模 10 为 0,则校验通过。

写个程序,并拿自己的银行卡号校验一下吧。

这个问题比较容易解。如果用命令式语言,多半会是 if (i%2==0) 来判断是否为偶数位。haskell中善长使用 List 变换、组合,可以不用这样。
下面是 haskell 解法:

import Data.Char (digitToInt)
cardCheck :: String -> Bool
cardCheck  s = 
    let t = [ f x i | (x,i) <- zip (reverse s) (cycle [1,2]) ]
        f x i = let t1 = digitToInt x * i
                    t2 = if t1 > 9 then t1 - 9 else t1
                in t2
    in sum t `mod` 10 == 0

main :: IO ()
main = do 
    print $ cardCheck "6225760008219524"
    print $ cardCheck "6225760008219525"

很有趣的是,它构造了一个 1,2,1,2,1,2 … 的无穷列表,与待处理的数位做 zip运算。
不变等价于乘1, 变的是乘 2
乘 2 后是两位数,数位相加。这与 减 9 是等价的(可以自已证明一下)。

再仔细考虑一下,偶数位的变换好像挺神秘的。其实,不管它有多少步骤,最终都是对 10 个数字变换成另一组 10 个数字而已。 这么少的情况,还算什么啊? 直接枚举好了!!

cardCheck' :: String -> Int
cardCheck' s = 
    sum [ f (digitToInt x) y | (x,y) <- zip (reverse s) (cycle [False,True]) ] 
    where
    f x False = x
    f x _ = t !! x
    t = [0,2,4,6,8,1,3,5,7,9]
情况少的时候,枚举是首选

函数式编程一个顺带的好处,可以让我们习惯性地从总体着手。它会逼着你去思考:它首先是个什么? 最终要变成什么?