Составление списка делителей в Haskell

Я делаю задачу 21 в eulerproject. Одна часть требует найти список правильных делителей числа. то есть там, где есть остаток n и некоторое число меньше, чем n. Поэтому я сделал этот Haskell, но GHCI рассердился на меня.

divisors n =[ n | n <- [1..(n-1)], n `rem` [1..(n-1)] ==0 ]

Проблема в том, что я не знаю, как это сделать:

n `rem` [1..(n-1)]

так что он возвращает число меньше n, которое равномерно делит на n.

Ответ 1

Вам просто нужна отдельная переменная.

Prelude> let divisors n = [x | x <- [1..(n-1)], n `rem` x == 0]
Prelude> divisors 20
[1,2,4,5,10]
Prelude> divisors 30
[1,2,3,5,6,10,15]

Теперь, если вы хотите сделать его более эффективным, мы уже знаем, что делитель не будет больше половины n, и мы знаем, что 1 является делителем всего. И, давайте продолжим и сделаем немного больше Haskell-y для загрузки, избегая понимания списка:

Prelude> let divisors n = 1 : filter ((==0) . rem n) [2 .. n `div` 2]
Prelude> divisors 20
[1,2,4,5,10]
Prelude> divisors 30
[1,2,3,5,6,10,15]
Prelude> divisors 31
[1]

Ответ 2

Если порядок списка делителей не важен, вы можете сделать это значительно более эффективным, только проверяя делители в диапазоне [2..sqrt n].

Что-то вроде этого (возможно, вы могли бы сделать некоторые локальные оптимизации, если бы подумали об этом больше):

divisors' n = (1:) $ nub $ concat [ [x, div n x] | x <- [2..limit], rem n x == 0 ]
     where limit = (floor.sqrt.fromIntegral) n

Где divisors - предыдущая реализация, а divisors - новая:

*Main> (sum.divisors) 10000000
14902280
(4.24 secs, 521136312 bytes)

*Main> (sum.divisors') 10000000
14902280
(0.02 secs, 1625620 bytes)

Примечание: Мы использовали nub для удаления любых повторений, когда на самом деле единственным возможным повторением было бы ограничение, если n - квадратное число. Вы можете сделать его несколько более эффективным, если обработать этот случай лучше, но я считаю, что этот способ более читабельен (если время работы не критично).