Объясните, почему Рекурсивная функция номера Ндс работает неправильно

#haskell #recursion

Вопрос:

Я пытаюсь создать функцию, которая возвращает значение 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 amp;amp; x `mod` 10 == 0 = True
    | r (s (x`div`10) 2 0) == 1 amp;amp; 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 (для любого числа). И я не понимаю, почему?

Комментарии:

1. Функция r всегда вызывает саму себя, на каждом шаге, независимо от того, каковы параметры. Переполнение стека абсолютно ожидаемо.

2. Сначала попробуйте получить номер в виде списка цифр. Затем подумайте о вычислении контрольной суммы.

3. Вы рекурсивно спускаетесь r без условия побега. Вам нужно определить условие, при котором r выполняется преобразование в Int без рекурсивного вызова самого себя (думайте об этом как о дне).

Ответ №1:

Как упоминалось в комментарии Пола Джонсона, первой задачей, которая стоит перед нами, является получение списка десятичных цифр из номера НДС.

Возможно, не стоит смешивать логику, вычисляющую цифры, с логикой, вычисляющей r и s. Полученный код может быть трудно отладить.

Давайте попробуем разработать код в интерактивном режиме, используя ghci интерпретатор.

Получение списка десятичных цифр:

Для этого мы можем сначала немного схитрить, просто используя Show экземпляр для целых чисел.

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

Но в Хаскелле String » ы «- это просто списки Char «ы». Так что это то же самое, что и: ['5', '0', '2', '6', '1', '8', '4', '1', '1'] .

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

We can get numeric ASCII codes from the characters using the ord function from the Data.Char module:

  λ> 
 λ> 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
 λ> 
 

Приведение кода в окончательную форму путем выбора стилей и предоставления подписей типов остается в качестве упражнения для читателя.

Комментарии:

1. Знал divMod , что был в Прелюдии.