import Data.Char
{- toDigits returns the digits of a number as an array.
It returns an empty list if n is zero or less. -}
toDigits :: Integer -> [Integer]
toDigits n
| n > 0 = map (\c -> toInteger $ digitToInt c) $ show n
| otherwise = []
-- toDigitsRev is like toDigits, except it reverses the array.
toDigitsRev :: Integer -> [Integer]
toDigitsRev n = reverse $ toDigits n
{- doubleAtEvenIndex is a helper function for doubleEveryOther.
It doubles a number (the second argument) if the index (the first argument)
is even. -}
doubleAtEvenIndex :: (Integer, Integer) -> Integer
doubleAtEvenIndex (index, n)
| index `mod` 2 == 0 = n * 2
| otherwise = n
-- doubleEveryOther doubles every other integer in an array.
doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther [] = []
doubleEveryOther v = map doubleAtEvenIndex (zip [1..] v)
-- sumDigits sums the digits in a list of integers.
sumDigits :: [Integer] -> Integer
sumDigits [] = 0
sumDigits (x:xs) = sum (toDigits x) + sumDigits xs
{- validate validates the creditCardNumber using the Luhn formula:
- Double the value of every second digit beginning from the right.
- Add the digits of the doubled values and the undoubled digits from the
original number.
- Calculate the remainder when the sum is divided by 10.
- If the result equals 0, then the number is valid. -}
validate :: Integer -> Bool
validate creditCardNumber = digitSum `mod` 10 == 0
where
digitSum = sumDigits $ doubleEveryOther $ toDigitsRev creditCardNumber
{- isValid exists purely for cosmetic reasons. It returns a string with the
credit card number and whether it is valid according to the Luhn algorithm
(see `validate`). -}
isValid :: Integer -> String
isValid creditCardNumber = case validate creditCardNumber of
True -> "Credit card number " ++ show creditCardNumber ++ " is valid!"
False -> "Credit card number " ++ show creditCardNumber ++ " is invalid."
main :: IO ()
main = do
putStrLn $ isValid 4012888888881881 -- Valid
putStrLn $ isValid 4012888888881882 -- Invalid