1+ -- {-# LANGUAGE ScopedTypeVariables #-}
2+
3+ module Luhn where
4+
5+ import Data.Char (digitToInt )
6+ import Control.Arrow ((>>>) )
7+
8+ {- -
9+ Reverse the order of the digits in the number.
10+ Take the first, third, ... and every other odd digit in the reversed digits and sum them to form the partial sum s1
11+ Taking the second, fourth ... and every other even digit in the reversed digits:
12+ Multiply each digit by two and sum the digits if the answer is greater than nine to form partial sums for the even digits
13+ Sum the partial sums of the even digits to form s2
14+ If s1 + s2 ends in zero then the original number is in the form of a valid credit card number as verified by the Luhn test.
15+
16+ For example, if the trial number is 49927398716:
17+
18+ Reverse the digits:
19+ 61789372994
20+ Sum the odd digits:
21+ 6 + 7 + 9 + 7 + 9 + 4 = 42 = s1
22+ The even digits:
23+ 1, 8, 3, 2, 9
24+ Two times each even digit:
25+ 2, 16, 6, 4, 18
26+ Sum the digits of each multiplication:
27+ 2, 7, 6, 4, 9
28+ Sum the last:
29+ 2 + 7 + 6 + 4 + 9 = 28 = s2
30+
31+ s1 + s2 = 70 which ends in zero which means that 49927398716 passes the Luhn test
32+
33+ Task
34+ Write a function/method/procedure/subroutine that will validate a number with the Luhn test, and
35+ use it to validate the following numbers:
36+
37+ 49927398716
38+ 49927398717
39+ 1234567812345678
40+ 1234567812345670
41+ --}
42+
43+
44+
45+ {- -
46+ luhn :: String -> Bool
47+ luhn = (0 ==) . (`mod` 10) . sum . map (uncurry (+) . (`divMod` 10)) . zipWith (*) (cycle [1,2]) . map digitToInt . reverse
48+ ---}
49+
50+ luhn :: String -> Bool
51+ luhn =
52+ reverse >>> map digitToInt >>>
53+ zipWith (*) (cycle [1 ,2 ]) >>>
54+ map (uncurry (+) . (`divMod` 10 )) >>>
55+ sum >>>
56+ (`mod` 10 ) >>>
57+ (0 == )
58+
59+
60+ {- -
61+ 1. Reverse the order of the digits in the number.
62+ 2. Take the first, third, ... and every other odd digit in the reversed digits and sum them to form the partial sum s1
63+ 3. Taking the second, fourth ... and every other even digit in the reversed digits:
64+ 4. Multiply each digit by two and sum the digits if the answer is greater than nine to form partial sums for the even digits
65+ 5. Sum the partial sums of the even digits to form s2
66+ 6. If s1 + s2 ends in zero then the original number is in the form of a valid credit card number as verified by the Luhn test.
67+ --}
68+ luhnTest :: String -> Bool
69+ luhnTest n =
70+ let (odds, evens) = (oddsEvens . map digitToInt . reverse ) n -- 1. and 3.
71+ s1 = sum odds -- 2.
72+ s2 = sum (map (crossSum . (* 2 )) evens) -- 4. and 5.
73+ in (s1 + s2) `rem` 10 == 0 -- 6.
74+
75+ oddsEvens :: [Int ] -> ([Int ], [Int ])
76+ oddsEvens xs = foldr collectOddsEvens ([] , [] ) (zip xs [1 .. ])
77+ where
78+ collectOddsEvens :: (Int , Int ) -> ([Int ], [Int ]) -> ([Int ], [Int ])
79+ collectOddsEvens (x, i) (odds, evens)
80+ | odd i = (x : odds, evens)
81+ | otherwise = (odds, x : evens)
82+
83+ crossSum :: Int -> Int
84+ crossSum = uncurry (+) . (`divMod` 10 ) -- sum . map digitToInt . show
85+
86+ main :: IO ()
87+ main = do
88+ let testcases = [" 49927398716" , " 49927398717" , " 1234567812345678" , " 1234567812345670" ]
89+ print $ map luhnTest testcases
90+ print $ map luhn testcases
0 commit comments