当前位置: 首页 > 知识库问答 >
问题:

解释Vat数递归函数不能正确工作的原因

慕容明煦
2023-03-14

我试图使一个函数,如果收到的数字是一个有效的增值税号码,返回true,否则False。

为了使数字有效,算法是:

设s为第8位数字乘以2,第7位数字乘以3,第6位数字乘以4,第5位数字乘以5,第4位数字乘以6,第3位数字乘以7,第2位数字乘以8,第1位数字乘以9的乘积之和。设r为s除以11的整数的余数。否则,最后一位数字必须从r中减去11

所以我做了这个函数:

verify :: Int -> Bool
verify x
    | r (s (x`div`10) 2 0) == 0 && x `mod` 10 == 0 = True
    | r (s (x`div`10) 2 0) == 1 && x `mod` 10 == 0 = True
    | x `mod` 10 == (s (x`div`10) 2 0) - 11 = True
    | otherwise = False

s :: Int -> Int -> Int -> Int
s x y acc
    | x `div`10 == x = (acc + x*y)
    | otherwise = ((x `mod` 10)*y + s (x`div`10) (y+1) acc+((x `mod` 10)*y))

r :: Int -> Int
r x = x `mod` 11

但是当我运行验证502618418它应该返回 True 时,但该函数始终返回 False(对于任何数字)。我不明白为什么?

共有1个答案

权韬
2023-03-14

正如保罗·约翰逊在评论中提到的,当前的第一项任务是从增值税号中获取十进制数字列表。

也许将计算数字的逻辑与计算 r 和 s 的逻辑交织在一起不是一个好主意。生成的代码可能难以调试。

让我们尝试使用ghci解释器以交互方式开发代码。

为此,我们可以先作弊一点,只需对整数使用 Show 实例即可。

$ ghci
 GHCi, version 8.8.4: https://www.haskell.org/ghc/  :? for help
 λ> 
 λ> vat1 = 502618411
 λ> 
 λ> vat1
 502618411
 λ> 
 λ> show vat1
"502618411"
 λ> 

但是在Haskell中,< code>String只是< code>Char的列表,所以这与:< code>['5 ',' 0 ',' 2 ',' 6 ',' 1 ',' 8 ',' 4 ',' 1']是一回事。

 λ> 
 λ> show vat1 == ['5', '0', '2', '6', '1', '8', '4', '1', '1']
 True
 λ> 

我们可以使用 Data.Char 模块中的 ord 函数从字符中获取数字 ASCII 代码:

 λ> 
 λ> import qualified Data.Char as Ch
 λ> :type Ch.ord
 Ch.ord :: Char -> Int
 λ> 
 λ> map Ch.ord (show vat1)
 [53,48,50,54,49,56,52,49,49]
 λ> 

由于从0到9的数字连续出现在ASCII序列中(从48开始),我们通过减去字符“0”的代码得到这些数字:

 λ> 
 λ> map  (\c -> (Ch.ord c) - (Ch.ord '0'))  (show vat1)
 [5,0,2,6,1,8,4,1,1]
 λ> 

所以我们有了数字列表。让我们定义函数:

 λ> 
 λ> getDigits vat = map  (\ch -> (Ch.ord ch) - (Ch.ord '0'))  (show vat)
 λ>
 λ> getDigits vat1
 [5,0,2,6,1,8,4,1,1]
 λ> 

但是如果我们不想使用Show实例呢?

然后我们可以使用纯数值递归函数。让我们将已经生成的数字列表,例如<code>dgs</code>,作为累加器。在ghci中使用多行工具:

 λ> 
 λ> :{
|λ>  getDigitsR dgs n = let  (q,r) = (divMod n 10)
|λ>                     in   if (q==0) then (r : dgs)
|λ>                                    else getDigitsR (r : dgs) q
|λ> 
|λ>  :}
 λ> 

测试

 λ> 
 λ> getDigitsR [9,9,9] vat1
 [5,0,2,6,1,8,4,1,1,9,9,9]
 λ> 
 λ> getDigitsR [] vat1
 [5,0,2,6,1,8,4,1,1]
 λ> 

因此,我们可以编写getDigits的替代定义:

 λ> 
 λ> getDigits vat = getDigitsR [] vat
 λ> 

根据规范,一个数字的权重从9开始,每一步递减1。重量低于2时,求和停止。这导致根据vat号的数字计算< code>s的递归定义如下:

 λ> 
 λ> :{
|λ> getSr w [] = 0
|λ> getSr w (dg:dgs) = if (w < 2) then  0  else  (w*dg + getSr (w-1) dgs)
|λ> :}
 λ>
 λ> 
 λ> digits1
 [5,0,2,6,1,8,4,1,1]
 λ> 
 λ> getSr 9 digits1
 146
 λ> 

这导致了计算s的这个简单函数:

 λ> 
 λ> getS vat = getSr 9 (getDigits vat)
 λ> 

但是如果使用库的风格是首选呢?

人们普遍认为,随着Haskell程序员获得更多的经验,他们倾向于使用更少的直接手动递归,而更多的是内部递归库函数。

如果我们想在这里这样做,我们需要用数字压缩权重:

 λ> 
 λ> ws = [9,8,7,6,5,4,3,2]
 λ> digits = getDigits vat1
 λ> 
 λ> zip ws digits
 [(9,5),(8,0),(7,2),(6,6),(5,1),(4,8),(3,4),(2,1)]
 λ> 

在这一点上,我们可以做乘法并求和它们的结果:

 λ> 
 λ> map (\(w,d) -> w*d) (zip ws digits)
 [45,0,14,36,5,32,12,2]
 λ> 
 λ> sum $ map (\(w,d) -> w*d) (zip ws digits)
 146
 λ> 

库函数zipwith使简化成为可能:

 λ> 
 λ> sum $ zipWith (*) ws digits
 146
 λ> 

因此,我们可以编写< code>getS函数的替代版本:

 λ> 
 λ> :{
|λ> getS vat = let  ws     = [9,8,7,6,5,4,3,2] 
|λ>                 digits = getDigits vat
|λ>            in   sum $ zipWith (*) ws digits
|λ> :}
 λ> 
 λ> getS vat1
 146
 λ> 

注意,这种样式使处理任意权重变得更容易。

因此,我们的工作基本上已经完成:

 λ> 
 λ> vat1
 502618411
 λ> getS vat1
 146
 λ> r = mod 146 11
 λ> r
 3
 λ> 

通过选择样式和提供类型签名,将代码变成最终形状,这是留给读者的练习。

 类似资料:
  • 正如标题所解释的,我有一个非常基本的编程问题,但我还没有找到。过滤掉所有(非常聪明的)“为了理解递归,你必须首先理解递归。”各种在线线程的回复我仍然不太明白。 当我们面对不知道自己不知道的事情时,我们可能会提出错误的问题或错误地提出正确的问题。我将分享我的“想法”。我的问题是希望有类似观点的人能够分享一些知识,帮助我打开递归灯泡! 以下是函数(语法用Swift编写): 我们将使用2和5作为参数:

  • 我正在尝试在XSLT 2.0中编写一个尾递归函数,它遍历日期的多值变量并返回最早的一个。出于某种原因,我的函数未被SaxonHE9.4识别为尾递归,当输入文件有超过150-200个条目左右时,我会收到以下错误: tail\u rec\u测试第73行出错。xsl:嵌套函数调用太多。可能是由于无限递归。内置模板规则 以下是我的xml输入: 这就是我的xsl-file的样子: 如何将其转换为正确的尾部递

  • 各种各样的书籍、文章、博客帖子表明,将递归函数重写为尾部递归函数可以加快速度。毫无疑问,对于生成斐波那契数或计算阶乘等琐碎情况,它会更快。在这种情况下,有一种典型的重写方法,即使用“辅助函数”和用于中间结果的附加参数。 尾部递归很好地描述了尾部递归函数和非尾部递归函数之间的差异,以及如何将递归函数转换为尾部递归函数。对于这种重写来说什么是重要的-函数调用的数量是相同的(重写之前/之后),不同之处在

  • 我有一个递归函数,需要创建一个由特殊对象组成的数组... 我的自定义对象由此类填充: 这是我的递归函数: 在这个函数中,RandomBool()方法随机返回true/false。。。RandomId()也不重要。。。 问题在于“位置”数组。我希望每个项目都有特定的位置数组,例如: 对于第一步,每个项目都需要有:[0]、[1]、[2]、[3]。。。 下一步,假设我们选择了3:[3,0],[3,1],

  • 让我们举这个例子 js编译器知道所有的函数声明,所以我可以在< code > main < code > main(second())内部调用< code>second。我不明白递归函数是如何在函数声明内部调用同一个函数的 我的思考过程是:好吧,这是函数声明,这是函数所做的,但是如何 即使声明没有完成,我也可以调用相同的函数

  • 问题内容: 我很难弄清楚这里出了什么问题: 例如, 输出。它应该输出6,因为长度看起来像这样:5-> 16-> 8-> 4-> 2-> 1 进行一些调试后,我看到正确返回了,但是递归中出了点问题。我不太确定 谢谢你的帮助。 问题答案: 在这两个块中,进行递归调用后不会返回任何值。您需要在递归调用之前先输入,例如。如果您没有明确声明,该函数将返回。 这样可以解决此问题,但是有一种方法可以使您的代码更