银行卡号校验
【问题】你在自动转帐机器上操作,有没有过这样的担心:万一我把卡号输错了一位,把钱打给了一个陌生人可怎么办呢?银行的人会告诉你,上面会显示对方名字中的一个字,比如,对方叫“张伟”,会显示 **伟,可要是碰巧这个陌生人也叫xx伟呢?
其实,完全没有担心的必要,银行卡号如果输错了一位,将会是一个无效的号码,不会对应任何人的帐号。如果输错2位呢?那会有小概率成为他人的帐号(错那么多,你敢说不是故意的?)。
银行卡号采用了一种叫作:Luhn算法的校验规则。其具体为:
- 从右边开始算,第偶数位上的数字乘以2,结果为2位数的,十位与个位相加。
- 第奇数位上的数保持不变。
- 变化后的数字总和模 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]
情况少的时候,枚举是首选
函数式编程一个顺带的好处,可以让我们习惯性地从总体着手。它会逼着你去思考:它首先是个什么? 最终要变成什么?