Ответ 1
Почему не просто
sumd = sum . map Char.digitToInt . show
Я программист на С++, который пытается научить себя Haskell, и это доказывает сложность понимания основ использования функций как типа цикла. У меня большое число, 50!, и мне нужно добавить сумму его цифр. Это относительно легкий цикл в С++, но я хочу узнать, как это сделать в Haskell.
Я прочитал несколько вводных руководств и могу получить 50! с
sum50fac.hs::
fac 0 = 1
fac n = n * fac (n-1)
x = fac 50
main = print x
К сожалению, на данный момент я не совсем уверен, как подойти к проблеме. Можно ли написать функцию, которая добавляет (mod) x 10 к значению, а затем снова вызывает ту же функцию на x/10, пока x/10 не станет меньше 10? Если это невозможно, как я должен подходить к этой проблеме?
Спасибо!
Почему не просто
sumd = sum . map Char.digitToInt . show
sumd 0 = 0
sumd x = (x `mod` 10) + sumd (x `div` 10)
Затем запустите его:
ghci> sumd 2345
14
ОБНОВЛЕНИЕ 1:
Этот не генерирует thunks и использует аккумулятор:
sumd2 0 acc = acc
sumd2 x acc = sumd2 (x `div` 10) (acc + (x `mod` 10))
Тест:
ghci> sumd2 2345 0
14
ОБНОВЛЕНИЕ 2:
Частично примененная версия в pointfree style:
sumd2w = (flip sumd2) 0
Тест:
ghci> sumd2w 2345
14
Я использовал flip
здесь, потому что функция по какой-либо причине (вероятно, из-за конструкции GHC) не работала с аккумулятором в качестве первого параметра.
Это просто вариант @ony's, но как я его напишу:
import Data.List (unfoldr)
digits :: (Integral a) => a -> [a]
digits = unfoldr step . abs
where step n = if n==0 then Nothing else let (q,r)=n`divMod`10 in Just (r,q)
Это приведет к получению цифр от низкого до высокого, что, в то время как неестественно для чтения, обычно является тем, что вы хотите для математических задач, связанных с цифрами числа. (Project Euler кто-нибудь?) Также обратите внимание, что 0
создает []
, а отрицательные числа принимаются, но производят цифры абсолютного значения, (Мне не нужны частичные функции!)
Если, с другой стороны, мне нужны цифры числа, поскольку они обычно пишутся, тогда я бы использовал метод @newacct, поскольку проблема является одной из по существу орфографии, а не математикой:
import Data.Char (digitToInt)
writtenDigits :: (Integral a) => a -> [a]
writtenDigits = map (fromIntegral.digitToInt) . show . abs
Сравнить вывод:
> digits 123
[3,2,1]
> writtenDigits 123
[1,2,3]
> digits 12300
[0,0,3,2,1]
> writtenDigits 12300
[1,2,3,0,0]
> digits 0
[]
> writtenDigits 0
[0]
Выполняя Project Euler, я действительно обнаружил, что некоторые проблемы требуют одного, а некоторые требуют другого.
.
и стиле "без точек" Чтобы сделать это понятным для тех, кто не знаком с оператором Haskell .
и стилем "без точек", их можно было бы переписать как:
import Data.Char (digitToInt)
import Data.List (unfoldr)
digits :: (Integral a) => a -> [a]
digits i = unfoldr step (abs i)
where step n = if n==0 then Nothing else let (q,r)=n`divMod`10 in Just (r,q)
writtenDigits :: (Integral a) => a -> [a]
writtenDigits i = map (fromIntegral.digitToInt) (show (abs i))
Это точно такие же, как и выше. Вы должны узнать, что они одинаковы:
f . g
(\a -> f (g a))
И "point-free" означает, что они одинаковы:
foo a = bar a
foo = bar
Объединяя эти идеи, они одинаковы:
foo a = bar (baz a)
foo a = (bar . baz) a
foo = bar . baz
Laster является идиоматическим Haskell, так как как только вы привыкнете его читать, вы можете видеть, что он очень краток.
Подводя итог всем цифрам числа:
digitSum = sum . map (read . return) . show
show преобразует число в строку. map итерации по отдельным элементам строки (например, цифры), превращает их в строку (например, символ "1" становится строкой "1" ), и чтение возвращает их к целому. сумма окончательно вычисляет сумму.
Просто чтобы сделать пул решений больше:
miterate :: (a -> Maybe (a, b)) -> a -> [b]
miterate f = go . f where
go Nothing = []
go (Just (x, y)) = y : (go (f x))
sumd = sum . miterate f where
f 0 = Nothing
f x = Just (x `divMod` 10)
Ну, одна, ваша функция Haskell пропускает скобки, вам нужен fac (n - 1). (о, я вижу, вы это исправили)
Два, реальный ответ, что вы хотите, сначала сделайте список:
listdigits n = if n < 10 then [n] else (listdigits (n `div` 10)) ++ (listdigits (n `mod` 10))
Это должно просто составить список всех цифр (тип: Int → [Int]).
Затем мы просто делаем сумму, как в sum (listdigits n). И мы должны это сделать.
Естественно, вы можете обобщить приведенный выше пример для списка для множества разных областей, также вы можете легко перевести это и на продукты.
Хотя, возможно, это не так эффективно, как другие примеры, здесь есть другой способ приблизиться к нему:
import Data.Char
sumDigits :: Integer -> Int
sumDigits = foldr ((+) . digitToInt) 0 . show
Изменить: метод newacct очень похож, и мне он немного лучше: -)