Обратите внимание, что в исходном вопросе были заданы все факторы, а не только простое факторы. Поскольку основных факторов намного меньше, их, вероятно, можно найти быстрее. Возможно, это то, что хотел OQ. Возможно нет. Но давайте решим исходную проблему и вернем «веселье» обратно в «функционал»!
Некоторые наблюдения:
Эти две функции не выдают одинаковый вывод - если x является идеальным квадратом, вторая функция дважды включает квадратный корень.
Первая функция перечисляет проверяет количество потенциальных факторов, пропорциональных размеру x; вторая функция проверяет только пропорционально квадратному корню из x
, затем останавливается (с ошибкой, отмеченной выше).
Первая функция (factors
) выделяет список всех целых чисел от 2 до n div 2
, где вторая функция никогда не выделяет список, а вместо этого посещает меньше целых чисел по одному за раз в параметре. Я запустил оптимизатор с помощью -O
и посмотрел на вывод с помощью -ddump-simpl
, а GHC просто не достаточно умен, чтобы оптимизировать эти распределения.
factorcalc
является хвост-рекурсивным, что означает, что он компилируется в замкнутый цикл машинного кода; filter
нет и нет.
Некоторые эксперименты показывают, что квадратный корень - убийца :
Вот пример функции, которая выдает коэффициенты x от z до 2:
factors_from x 1 = []
factors_from x z
| x `mod` z == 0 = z : factors_from x (z-1)
| otherwise = factors_from x (z-1)
factors'' x = factors_from x (x `div` 2)
Это немного быстрее, потому что не выделяет, но все равно не является хвостовой рекурсией.
Вот хвосто-рекурсивная версия, более верная оригиналу:
factors_from' x 1 l = l
factors_from' x z l
| x `mod` z == 0 = factors_from' x (z-1) (z:l)
| otherwise = factors_from' x (z-1) l
factors''' x = factors_from x (x `div` 2)
Это все еще медленнее, чем factorcalc
, поскольку оно перечисляет все целые числа от 2 до x div 2
, тогда как factorcalc
останавливается на квадратном корне.
Вооружившись этим знанием, теперь мы можем создать более функциональную версию factorcalc
, которая копирует и скорость, и ошибку:
factors'''' x = sort $ uncurry (++) $ unzip $ takeWhile (uncurry (<=)) $
[ (z, x `div` z) | z <- [2..x], x `mod` z == 0 ]
Я точно не рассчитал время, но, учитывая, что 100 миллионов в качестве входных данных, он и factorcalc
мгновенно завершаются, тогда как все остальные занимают несколько секунд.
Как и почему эта функция работает, оставлено читателю в качестве упражнения: -)
ADDENDUM : ОК, чтобы уменьшить кровотечение из глазного яблока, вот немного более разумная версия (и без ошибки):
saneFactors x = sort $ concat $ takeWhile small $
[ pair z | z <- [2..], x `mod` z == 0 ]
where pair z = if z * z == x then [z] else [z, x `div` z]
small [z, z'] = z < z'
small [z] = True