LINUX.ORG.RU

[Haskell] sumDivisors

 


0

2

Нужна функия суммы делителей натур. числа. Вместо обычного

sumDivisors n = sum [ k | k <- [1..(n `div` 2)], n `mod` k == 0 ]
лучше проверять делители не до n/2, а до sqrt(n/2), причем если нашли делитель k, то n/k тоже будет делителем. Таким способом исчерпываются все делители. Но надо следить, чтобы один делитель не учитывался дважды. На си это выглядит так:
uint sumDivisors(uint n)
{
    uint m = (uint)sqrt(n);
    uint k = 2, sum = 1, tmp;

    for (; k <= m; ++k)
        if (!(n % k)) {
            sum += k;
            if (k != (tmp = n/k))
                sum += tmp;
        }

    return sum;
}

(Замечю, что работает это значительно быстрее перебора всех делителей до n/2, особенно на больших n.) На хаскеле я новичок и написал так:
sumDivisors n = 1 + sum' [ k + m | k <- [2..sq],
                                   n `mod` k == 0,
                                   let t = n `div` k
                                       m = if t /= k then t else 0 ] where
    sq = floor . sqrt . fromIntegral $ n
Можно это как-нибудь попроще, покрасивее записать (не в ущерб скорости)?



Последнее исправление: toady2 (всего исправлений: 2)

Но надо следить, чтобы один делитель не учитывался дважды.

Ну, два делителя равны только в одном случае, почему бы его не рассмотреть отдельно? А в основном цикле написать:

sum' [ k + n`div`k | k <- [2..sq-1], n`mod`k == 0 ]

Единственное, что меня смущает — это вычисление sq через плавающую точку.

balodja ★★★
()
Ответ на: комментарий от balodja

Ну, два делителя равны только в одном случае, почему бы его не рассмотреть отдельно? А в основном цикле написать

Ага, спасибо.

Единственное, что меня смущает — это вычисление sq через плавающую точку.

А как иначе? Ньютоном? А стандартный sqrt не им считает?

toady2
() автор топика
Ответ на: комментарий от toady2

OFFTOP. Не подскажите какую-нибудь статью, где бы описывались основные техники оптимизации в Haskell (foldl', хвостовая рекурсия, toList . fromList, и т. д.)?

toady2
() автор топика

Всё нормально на первый взгляд. На второй не смотрел. Имхо нет смысла переписывать.

eliriand ★★
()
Ответ на: комментарий от dizza

не-не-не, не надо. Мне просто интересно, что так тормозит Хаскель? Я его компилил как ghc --make -O2 23.hs

toady2
() автор топика
Ответ на: комментарий от toady2

Оптимизирующий суперкомпилятор для хаскеля. Не суть. Там на первых слайдах очень внятно объясняют почему Хаскель не догоняет C.

KblCb ★★★★★
()
Ответ на: комментарий от KblCb

Сам факт, что это вообще нужно объяснять, как-то греет душу.

Miguel ★★★★★
()
Ответ на: комментарий от KblCb

А можно уточнить? Я верно понимаю, что

sum' [ k + n`div`k | k <- [2..sq], n`mod`k == 0 ]
Сначала создаёт список, а потом отправляет его функции sum? Или же они работают «параллельно», т. е. один элемент создался в списке — он отправляется к sum, затем создался второй элемент — и он отдался sum и т. д.?

И ищё вопросик: есть ли разница между компилированием ghc --make и запуском проги из интерпретатора ghci? Может когда ghc --make, компилятор более основательно подходит к оптимизации? Может хаскель сам додумается заменить «ленивый» sum на foldl' (+) 0, который сразу будет сворачивать сумму?

toady2
() автор топика
Ответ на: комментарий от toady2

Если нужна скорость надо собирать GHC с оптимизацией (флаг -O2) интерпретатор - тормоз.

anonymous
()

А если заменить списки на Array (я пока ещё не разбирался с этим, но Hoogle нашёл Data.Array), то это увеличит производительность?

toady2
() автор топика
Ответ на: комментарий от toady2

Сначала создаёт список, а потом отправляет его функции sum? Или же они работают «параллельно», т. е. один элемент создался в списке — он отправляется к sum, затем создался второй элемент — и он отдался sum и т. д.?

Haskell же ленивый :) И ему навряд ли захочется создавать вначале список.

AlexVR ★★★★★
()
Ответ на: комментарий от AlexVR

Спасибо. То есть «параллелится»?..

И ещё последний вопросик. Вот предположим у нас есть какая-то медленная функция f. Мы посчитали один раз её значение f(x) в точке x. Затем нам снова понадобилось f(x). Хаскель будет пересчитывать или он где-тол хранит результаты прошлых вычислений? Если да, то и в случае скомпилированной программы (ghc --make) то же будет?

toady2
() автор топика
Ответ на: комментарий от toady2

Это называется мемоизация и достигается специальными приёмами. См. http://www.haskell.org/haskellwiki/Memoization

GHCi чаще всего мемоизирует результаты вычислений, но компилированная программа просто так делать этого не будет

unC0Rr ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.