IBM MOD-10
。该代码的解释可以在瑞典语Wikipedia和英语Wikipedia上找到。
以下是该算法工作原理的说明:
给出一个9位数字的字符串,
abcdefghi
计算结果: sum
。然后结果是您需要添加多少才能使其被10整除。例如,如果
a*2
则结果是16
,即1 + 6
。 array = [a*2, b, c*2, d, e*2, f, g*2, h, i*2]
测试代码:
import Data.Char
sumOfChars :: String -> Int
sumOfChars "" = 0
sumOfChars str = digitToInt(str !! 0) + sumOfChars(tail str)
twoMult :: Char -> String
twoMult c = show (digitToInt(c) * 2)
identificationSum :: String -> Int
identificationSum "" = 0
identificationSum str = if length str `mod` 2 == 1 then sumOfChars(twoMult(str !! 0)) + identificationSum(tail str)
else digitToInt(str !! 0) + identificationSum(tail str)
remainingToTens :: Int -> Int
remainingToTens x = ceiling(fromIntegral x / 10) * 10 - x
determineLastDigit :: String -> Int
determineLastDigit str = remainingToTens(identificationSum(str))
打印件:
determineLastDigit("811228987")
由于这是我第一次在Haskell中取得成功,因此欢迎提出任何意见。即使是第一次,也可以随意撕开我的代码,并提出任何高级建议,但我始终渴望学习。
#1 楼
这是一个好的开始!我将对您的代码进行两次传递,一次传递是为了解决样式问题,另一次传递是利用Haskell Prelude中的函数来减少代码长度并使其更符合典型的Haskell用法和习惯用法。Style
您的
sumOfChars
函数是尾递归的一个很好的例子,但是在Haskell中,我们将通过模式匹配将String
分开。 Haskell String
实际上是一个字符列表或[Char]
,因此我们将其分解为一个列表。sumOfChars :: [Char] -> Int -- String is a synonym for [Char]
sumOfChars [] = 0 -- The empty list is the same value as the empty string, i.e. [] == ""
sumOfChars (c:cs) = digitToInt c + sumOfChars cs
:
是列表cons运算符,c
是列表的开头,cs
是结尾。因此,c == str !! 0
和cs == tail str
。 (并且head
是我们通常用于获取列表中索引0处元素的函数。)twoMult
没错,但是Haskell中的函数应用程序不需要任何括号。括号用于分组,因为函数应用程序具有所有操作的最高优先级。twoMult :: Char -> String
twoMult c = show (digitToInt c * 2)
在函数的顶层使用
if
语句通常表明您可以使用警卫。保护有点像多路if
。identificationSum :: [Char] -> Int
identificationSum [] = 0
identificationSum s@(c:cs) -- The 's@' part is an as-pattern, explained below
| length s `mod` 2 == 1 = sumOfChars (twoMult c) + identificationSum cs
| otherwise = digitToInt c + identificationSum cs
每个保护都以管道(
|
)开头,并求值为Bool
。首先进行模式匹配,然后按顺序评估防护。 otherwise
只是True
的同义词,即当评估达到时总是成功的防护。我在其中使用了as-pattern将
c:cs
的值绑定到标识符。这是一个方便的快捷方式,如果同时需要原始值及其分解,则最好在各处编写c:cs
。remainingToTens
有点奇怪,当编译器抱怨Fractional Int
没有实例时,我猜想您能找到答案了,是吗?整数除法使用一个名为div
的函数,/
用于Fractional
的值(例如Float
或Double
)(是的,这是您第一次遇到这个问题有点奇怪)。 >并且determineLastDigit
只是有一些额外的括号。remainingToTens :: Int -> Int
remainingToTens x = (x `div` 10 + 1) * 10 - x -- Edit: This is incorrect, use version below
惯用的Haskell
您执行的许多操作都可以使用一些我们在Prelude中使用了更高阶的函数,并以一种函数形式进行了编码。
determineLastDigit :: String -> Int
determineLastDigit s = remainingToTens (identificationSum s)
sumOfChars
的类型是sum
,即它具有一个函数-本身具有map
类型的元素并返回类型为map
的东西-一个列表,并返回一个列表,其中该函数已应用于每个elem ent of the first list。 (a -> b) -> [a] -> [b]
将所有值添加到数字列表中。我们可以对该函数做的另一件事是以无点样式编写它。毫无意义地编写函数毫无疑问是Haskell风格的一个方面,但是除非您对语言的基础有扎实的了解,否则不要过多地关注它。我在这篇文章的底部提供了附录A,其中的函数已被编写成无点的供您参考。
a
可以用多种方式编写(某些方式在下面的附录中),但是鉴于b
,sum
的上下文以及算法的规范,我倾向于将功能分为两个阶段。首先,基于每隔一个数字加倍构造一个新的字符串,然后对所有数字求和。为此,我将介绍一个新功能。sumOfChars :: [Char] -> Int
sumOfChars cs = sum (map digitToInt cs)
然后
identificationSum
是sumOfChars
和twoMult
的组成。doubleAlternating :: [Char] -> [Char]
doubleAlternating [] = []
doubleAlternating (c:[]) = twoMult c
doubleAlternating (c:d:cs) = twoMult c ++ [d] ++ doubleAlternating cs
identificationSum
确实可以从一些模块化算法中受益。使用doubleAlternating
函数。identificationSum :: String -> Int
identificationSum s = sumOfChars (doubleAlternating s)
我将在附录中内联一点,使它更进一步,但就目前的情况而言,这是一个非常易读的翻译,因为有人说。为了简洁起见,与问题域的紧密对应可能比简洁的代码更有价值。
附录A:Pointfree
我喜欢Pointfree的一件事风格是它迫使您根据功能,高阶抽象和数据管道进行思考。呈现,无需进一步评论。
remainingToTens :: Int -> Int
remainingToTens x = negate x `mod` 10
附录B:高尔夫代码
理解这将有助于您探索序曲,并希望对它有所了解。功能组成。我承认这有点花哨。 ;-)
sumOfChars :: [Char] -> Int
sumOfChars = sum . map digitToInt
twoMult :: Char -> String
twoMult = show . (* 2) . digitToInt
identificationSum :: String -> Int
identificationSum = sumOfChars . doubleAlternating
determineLastDigit :: String -> Int
determineLastDigit = remainingToTens . identificationSum
#2 楼
首先是简单的问题...我建议您更明确地说明要导入的内容。例如,
import Data.Char (digitToInt, intToDigit)
remainingToTens x = ceiling(fromIntegral x / 10) * 10 - x
可以更简单地写为remainingToTens x = 10 - x `mod` 10
。在输入中支持
'-'
和'+'
字符会很好。 。如果输入包含除十进制数字或
'-'
或'+'
之外的任何其他字符,则失败可能会很好。实际上,digitToInt
还接受'a'
至'f'
作为您要拒绝的十六进制数字。在Haskell中不是惯用语言。通常,Haskell列表可以无限长。因此,Haskell成语强调懒惰,这意味着从头到尾的遍历。对于这样的用于处理短字符串的代码,它没有什么区别,但是您应该养成养成从头到尾遍历列表的习惯,只要有可能就一次。
,最好避免调用诸如
!!
之类的函数,这些函数会一直遍历到列表的末尾。尤其是当您在递归函数if-then-else
中这样做时,则不是这样。 (但是事实证明,毕竟可能需要length
-参见下文。)我不喜欢
identificationSum
的工作方式。 length
调用sumOfChars(twoMult(str !! 0))
对加倍的数字进行字符串化处理,然后twoMult
立即将其转换回数字域。您应该能够在不进行字符串化的情况下处理计算。Helper函数(如果仅被一个函数使用过)应该是作用域。您可以使用
show
子句或sumOfChars
表达式。第一个建议的实现
结合上面的想法…
import Data.Char (digitToInt, intToDigit)
sumOfChars :: String -> Int
sumOfChars str = sumOfChars' double id str
where
sumOfChars' f f' "" = 0
sumOfChars' f f' (c:cs)
| c == '-' || c == '+' = sumOfChars' f f' cs
| '0' <= c && c <= '9' = (f (digitToInt c)) + sumOfChars' f' f cs
double n
| n < 5 = 2 * n
| n < 10 = 2 * n + (1 - 10)
lastDigit :: String -> Char
lastDigit str = intToDigit $ 10 - (sumOfChars str) `mod` 10
我已经重新定义了
where
,以便它以let helper = … in …
而不是lastDigit
的形式返回数字。函数的用户应该只处理字符串和字符,因为瑞典语个人ID是字符串,而不是整数。 (例如,前导零和标点符号很重要。)并发症
显然,ID有时在生日日期字段中用四位数的年份书写。但是,瑞典语Wikipedia示例(“ 19811218-9876”)建议,即使在世纪之前,校验和仍基于使用两位数年份的规范表示。这使实现变得更加棘手:您可能需要重新引入对
Char
的调用(请注意,在非递归上下文中只是一个调用),或者进行大量的模式匹配。sumOfChars :: String -> Int
sumOfChars str@(_:_:cs)
| length str > 11 = sumOfChars' double id cs
| otherwise = sumOfChars' double id str
where
sumOfChars' f f' "" = 0
sumOfChars' f f' (c:cs)
| c == '-' || c == '+' = sumOfChars' f f' cs
| '0' <= c && c <= '9' = (f (digitToInt c)) + sumOfChars' f' f cs
double n
| n < 5 = 2 * n
| n < 10 = 2 * n + (1 - 10)
替代API建议
我想常见的用例可能是附加校验和数字。也许此替代API可能更有用?一个功能可以同时照顾
Int
和length
,并且其所有辅助功能都可以在其内部灵活调整范围。completeID :: String -> String
-- Support four-digit-year variant representation
completeID (y1:y2:y3:y4:m1:m2:d1:d2:n1:n2:n3:[]) = y1:y2:completeID (y3:y4:m1:m2:d1:d2:n1:n2:n3:[])
completeID (y1:y2:y3:y4:m1:m2:d1:d2:'+':n1:n2:n3:[]) = y1:y2:completeID (y3:y4:m1:m2:d1:d2:'+':n1:n2:n3:[])
completeID (y1:y2:y3:y4:m1:m2:d1:d2:'-':n1:n2:n3:[]) = y1:y2:completeID (y3:y4:m1:m2:d1:d2:'-':n1:n2:n3:[])
completeID cs = completeID' double id 0 cs
where
completeID' f f' sum "" = [intToDigit $ 10 - sum `mod` 10]
completeID' f f' sum (c:cs)
| c == '-' || c == '+' = c : completeID' f f' sum cs
| '0' <= c && c <= '9' = c : completeID' f' f (sum + f (digitToInt c)) cs
double n
| n < 5 = 2 * n
| n < 10 = 2 * n + (1 - 10)
评论
\ $ \ begingroup \ $
我喜欢你的回答!当然,这给了我很多东西要检查。我相信,尽管x%10 == 0时,remainingToTens x =(x`div` 10 + 1)* 10-x或剩余toTens x = 10-x`rem` 10会给出错误的结果,但应为0,并且在那种情况下不是10。这就是为什么我采用上限方法。 (我想用您的方法很难解决)。是的,我确实得到了小数整数消息的实例。做了一些谷歌搜索来解决。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年7月24日,0:07
\ $ \ begingroup \ $
啊!您对的剩下的权利是正确的!我只用您给出的单个示例进行了实际测试。真可惜这似乎有效。 negate x`mod` 10很高兴您发现我的回答很有帮助!
\ $ \ endgroup \ $
– bisserlis
14年7月24日在1:52