【问题标题】:Explain why a Vat Number Recursive Function isn't Working Correctly解释为什么增值税号递归函数不能正常工作
【发布时间】:2021-09-30 04:50:07
【问题描述】:

我正在尝试创建一个函数,如果收到的号码是有效的增​​值税号码,则返回 True,否则返回 False。

对于有效的数字,算法是:

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

所以我做了这个函数:

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

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

【问题讨论】:

  • 函数r总是在每一步调用自己,不管参数是什么。堆栈溢出是绝对可以预料的。
  • 首先尝试将号码作为数字列表获取。然后考虑计算校验和。
  • 你在r 中递归下降,没有转义条件。您需要定义一个条件,在该条件下 r 解析为 Int 而无需递归调用自身(将其视为底部)。

标签: haskell recursion


【解决方案1】:

正如 Paul Johnson 在评论中提到的,手头的第一个任务是从增值税号中获取十进制数字列表。

也许将计算数字的逻辑与计算 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 中,String 只是 Char 的列表。所以这与:['5', '0', '2', '6', '1', '8', '4', '1', '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 实例怎么办?

然后我们可以使用纯数字递归函数。 让我们将已经生成的数字列表(例如dgs)作为累加器。使用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
 λ> 

计算r和s:

根据规范,一个数字的权重从 9 开始,每一步减 1。低于 2 的权重,求和停止。这导致以下递归定义用于从增值税号的数字计算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
 λ> 

所以我们可以编写 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
 λ> 

通过选择样式并提供类型签名将代码放入最终形状,留给读者作为练习。

【讨论】:

  • 我知道divMod 在前奏曲中。
猜你喜欢
  • 2018-06-17
  • 2022-12-24
  • 2017-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多