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

问题描述

我已经看到了此解决方案:

.loadTrustMaterial

但是,我不了解它,因为我是Haskell的新手。

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,True]

我了解此算法的简化版本只有四位数字。

但是,我不知道如何为任何长度的数字列表编写算法版本。

解决方法

老实说,这个例子很神秘。它过度使用了无点样式,即省略了显式的函数参数。有时 可以使代码简洁明了,但也可以使代码变得更加隐秘。

让我们从这里开始:

     (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

(我避免使用变量名even,它也是检查数字是否为偶数的标准函数的名称!)

或者,您可以在此处调用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

只需要reverse来使False与输入列表的 last 元素对齐,而不是使其头部对齐;这有点难看,但并不严格。

mapzip的组合具有一个标准的快捷方式,可同时执行以下操作:

doubleAndSum = sum
              . zipWith nextStep (cycle [False,True])
              . reverse

至于myLuhn:以无点样式编写实际上是IMO可以的,但我会稍微介绍一下。具体来说,

decimalDigits :: Int -> [Int]
decimalDigits = map (read . (: "")) . show

(:"")的作用是,将单个字符放入单例字符串中。也可以写成read . pure

然后

myLuhn = (0 ==) . (`mod` 10) . doubleAndSum . decimalDigits

myLuhn x = doubleAndSum (decimalDigits x)`mod`10 == 0

可能会发生这样的情况,即单个遍历对性能有好处,但是,如果您在该级别上考虑,那么几乎应该肯定不是列表上的懒惰对折,但在未装箱的矢量上严格左折。无论如何,GHC经常可以将单独的fold-y操作融合为一个遍历。