module Luhn(
addLuhnDigit,
checkLuhnDigit,
prop_checkLuhn,
prop_checkSingleError
) where
import Data.Digits
import Test.QuickCheck
enumerate :: Integral n => [a] -> [(n, a)]
enumerate xs = enumerate' 0 xs
where
enumerate' _ [] = []
enumerate' counter (a:as) =
(counter, a) : enumerate' (counter + 1) as
addLuhnDigit :: Integral n
=> n
-> n
addLuhnDigit num = num * 10 + checkDigit
where
checkDigit = (10 total `mod` 10) `mod` 10
total = sum $ concat $ map doubleEven (enumerate $ digitsRev 10 num)
doubleEven :: Integral n => (Int, n) -> [n]
doubleEven (i, n) = if odd i
then [n]
else digitsRev 10 (2 * n)
checkLuhnDigit :: Integral n
=> n
-> Bool
checkLuhnDigit num = total `mod` 10 == 0
where
total = sum $ concat $ map doubleOdd (enumerate $ digitsRev 10 num)
doubleOdd :: Integral n => (Int, n) -> [n]
doubleOdd (i, n) = if odd i
then digitsRev 10 (2 * n)
else [n]
prop_checkLuhn
:: Integer
-> Property
prop_checkLuhn i = i > 0 ==> (checkLuhnDigit . addLuhnDigit) i
prop_checkSingleError
:: Integer
-> Integer
-> Integer
-> Property
prop_checkSingleError i modDigit replace = i > 0 ==>
let checkNum = addLuhnDigit i
checkDigits = digits 10 checkNum
modDigit' = modDigit `mod` fromIntegral (length checkDigits 1)
start = take (fromIntegral modDigit') checkDigits
rest = drop (fromIntegral (modDigit' + 1)) checkDigits
newDigits = start ++ [replace `mod` 10] ++ rest
newNum = unDigits 10 newDigits
in
(newNum == checkNum) || (not $ checkLuhnDigit newNum)