#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
, что был в Прелюдии.