Haskell 对于初学者|整数列表的luhn算法

Haskell 对于初学者|整数列表的luhn算法,haskell,luhn,Haskell,Luhn,我已经看到了这个解决方案: doubleAndSum :: [Int] -> Int doubleAndSum = fst . foldr (\i (acc, even) -> (acc + nextStep even i, not even)) (0,False) where nextStep even i | even = (uncurry (+) . (`divMod` 10) . (*2)) i | otherwise

我已经看到了这个解决方案:

doubleAndSum :: [Int] -> Int
doubleAndSum = fst . foldr (\i (acc, even) -> (acc + nextStep even i, not even)) (0,False)
  where 
    nextStep even i
        | even      = (uncurry (+) . (`divMod` 10) . (*2)) i
        | otherwise = i 

myLuhn :: Int -> Bool
myLuhn = (0 ==) . (`mod` 10) . doubleAndSum . (map (read . (: ""))) . show

testCC :: [Bool]
testCC = map myLuhn [49927398716, 49927398717, 1234567812345678, 1234567812345670]
-- => [True,False,False,True]
然而,我不明白,因为我是哈斯克尔的新手

luhn :: [Int] -> Bool
luhn w x y z = (luhnDouble w + x + luhnDouble y + z) `mod` 10 == 0

luhnDouble :: Int -> Int
luhnDouble x | 2* x <= 9 = 2*x
             | otherwise = (2*x)-9
luhn::[Int]->Bool
luhn w x y z=(luhnDouble w+x+luhnDouble y+z)`mod`10==0
luhnDouble::Int->Int

luhnDouble x | 2*x老实说,这个例子相当神秘。它过度使用无点样式,即省略显式函数参数。这有时可以使代码变得漂亮和简洁,但也可以使代码变得相当神秘

让我们从这里开始:

     (uncurry (+) . (`divMod` 10) . (*2)) i
首先,由于您只是将所有内容应用于参数
i
,因此不需要真正的合成管道——您不妨编写它

     uncurry (+) $ (`divMod` 10) $ (*2) i
   ≡ uncurry (+) $ (`divMod` 10) $ i*2
   ≡ uncurry (+) $ (i*2)`divMod`10
   ≡ let (d,r) = (i*2)`divMod`10
     in d+r
因此,可以编写
nextStep

    nextStep isEven i
        | isEven     = d+r
        | otherwise  = i
     where (d,r) = (i*2)`divMod`10
(我避免使用变量名
偶数
,这也是检查数字是否偶数的标准函数名!)

或者,您也可以在此处调用
luhnDouble
函数,该函数实际上以更详细的方式计算相同的内容:

    nextStep isEven i
        | isEven     = luhnDouble i
        | otherwise  = i
那你就有这个折叠。它基本上做了三件互锁的事情:1。在偶数和奇数之间切换2。对每个列表元素应用
nextStep
,以及偶数3。总结结果

我不同意用一次折叠来完成所有这些是个好主意†;写出来要清楚得多:

doubleAndSum = sum
              . map (\(isEven, i) -> nextStep isEven i)  -- or `map (uncurry nextStep)`
              . zip (cycle [False, True])   -- or `iterate not False`
              . reverse
只需将
False
与输入列表的最后一个元素(而不是其头部)对齐,就可以使用
反向
;这有点难看,但没有批评

map
zip
的组合有一个标准的快捷方式,可以在一个步骤中同时执行这两项操作:

doubleAndSum = sum
              . zipWith nextStep (cycle [False, True])
              . reverse
至于
myLuhn
:我实际上可以用无点的风格来写,但我会稍微打断一下。具体来说,

decimalDigits :: Int -> [Int]
decimalDigits = map (read . (: "")) . show
(:“”)
所做的是,它将单个字符放入单个字符串中。也可以写入
读取。纯

那么



†可能有这样一种情况,即单个遍历对性能有好处,但是如果您在该级别上思考,那么几乎可以肯定,它不应该是列表上的惰性右折叠,而应该是未绑定向量上的严格左折叠。无论如何,GHC通常可以将单独的折叠y操作融合到单个遍历中。感谢您解码这段自命不凡的代码。:-)而且,可能比
read更好。(:“”)
至少有两种方式。当然,我怎么会错过呢!
myLuhn = (0 ==) . (`mod` 10) . doubleAndSum . decimalDigits
myLuhn x = doubleAndSum (decimalDigits x)`mod`10 == 0