I am new to Haskell, and wrote a script to verify credit number. I did some tests, the script worked, but can it be improved further?
isCreditCardNumber :: String -> Bool
isCreditCardNumber number =
0 == creditCardReminder ( creditCardDouble ( blowupCreditCardNumber ( reverse number)))
blowupCreditCardNumber :: String -> [(Int,Char)]
blowupCreditCardNumber creditCardNumber = zip [1..] creditCardNumber
creditCardDouble :: [(Int,Char)] -> [Int]
creditCardDouble [] = []
creditCardDouble ((index,digit):rest)
| even index = (numberDoubleToList ((*2) $ digitToInt digit)) ++ creditCardDouble rest
| otherwise = digitToInt digit : creditCardDouble rest
numberDoubleToList:: Int -> [Int]
numberDoubleToList number
| number > 9 = map digitToInt (show number)
| otherwise = [number]
creditCardReminder :: [Int] -> Int
creditCardReminder xs = sum xs `mod` 10
1 Answer 1
You can eta-reduce blowupCreditCardNumber
:
blowupCreditCardNumber = zip [1..]
Using composition, you can also make isCreditCardNumber pointfree:
isCreditCardNumber = (==0) . creditCardReminder . creditCardDouble . blowupCreditCardNumber . reverse
You don't need to optimize numberDoubleToList
by special-casing the one-digit case.
numberDoubleToList = map digitToInt . show
The explicit recursion in creditCardDouble
can be averted by using library functions that specialize in particular recursive patterns:
creditCardDouble = concatMap foo where
foo (index, digit) | even index = numberDoubleToList $ (*2) $ digitToInt digit
| otherwise = [digitToInt digit]
foo
's name makes foo
look like a crutch, and that is good, because it is one.
I would inline definitions that are only used once and do not deserve to be in a library:
isCreditCardNumber = (==0) . (`mod` 10) . sum . concatMap foo . zip [1..] . reverse where
foo (index, digit) | even index = map digitToInt $ show $ (*2) $ digitToInt digit
| otherwise = [digitToInt digit]
digitToInt digit
is used in both cases of foo, and so can be factored out:
isCreditCardNumber = (==0) . (`mod` 10) . sum . concatMap foo . zip [1..] . reverse where
foo (index, digit) = bar index $ digitToInt digit
bar index | even index = map digitToInt . show . (*2)
| otherwise = pure
In fact, we don't need to generate the index and pass it to foo
if all we do with it is put it into bar
later:
isCreditCardNumber = (==0) . (`mod` 10) . sum . concatMap foo . zip (cycle [pure, map digitToInt . show . (*2)]) . reverse where
foo (doubler, digit) = doubler $ digitToInt digit
foo
is almost trivial, lets get rid of it entirely:
isCreditCardNumber = (==0) . (`mod` 10) . sum . concat . zipWith ($) (cycle [pure, map digitToInt . show . (*2)]) . map digitToInt . reverse
map digitToInt . show
is pure
on single digits, so we can factor it out of that list and then even factor out the (*)
:
isCreditCardNumber = (==0) . (`mod` 10) . sum . concatMap (map digitToInt . show) . zipWith (*) (cycle [1, 2]) . map digitToInt . reverse
Note that if you know the parity of the length of the credit card number, you can get rid of the reverse
.
-
\$\begingroup\$ wow, mate, this is so amazing!!! looks like I have a long way to learn Haskell. \$\endgroup\$anru– anru2016年10月22日 01:50:56 +00:00Commented Oct 22, 2016 at 1:50
-
\$\begingroup\$ hi mate, looks like "concatMap (digitToInt . show)" has change to concatMap ((\y -> map digitToInt y) . show) \$\endgroup\$anru– anru2016年10月22日 03:30:57 +00:00Commented Oct 22, 2016 at 3:30
-
\$\begingroup\$ Ah, of course.
concatMap (map digitToInt . show)
, that is. (Or, if you want,map digitToInt . concatMap show
, but that might give a little bit of a different intuition about what the code does?) Let me edit in that missingmap
in the last code block. \$\endgroup\$Gurkenglas– Gurkenglas2016年10月22日 04:38:49 +00:00Commented Oct 22, 2016 at 4:38