2009-09-27 2 views
2

Я делаю вопрос 12 эйлера проекта, где должен найти первый номер треугольника с 501 делителями. Поэтому я взломал это с помощью Haskell:Функция Haskell, занимающая много времени для обработки

divS n = [ x | x <- [1..(n)], n `rem` x == 0 ] 
tri n = (n* (n+1)) `div` 2 
divL n = length (divS (tri n)) 
answer = [ x | x <- [100..] , 501 == (divL x)] 

Первая функция находит делители числа.

Вторая функция вычисляет п-й номер треугольника

3-я функция находит длину списка, которые являются делителями треугольника числа

4-функция должна возвращать значение числа треугольника, который имеет 501 делителей.

Но пока это работает некоторое время, не возвращая результата. Является ли ответ очень большим или мне нужна какая-то серьезная оптимизация, чтобы сделать эту работу в реалистичное время?

ответ

4

Вы должны использовать свойства функции делителей: http://en.wikipedia.org/wiki/Divisor_function

Обратите внимание, что п и п + 1 всегда взаимно просты, так что вы можете получить д (п * (п + 1)/2) путем умножения ранее вычисленные значения.

+0

В общем, это хорошая эвристика в таких задачах, чтобы посмотреть на основные коэффициенты чисел, которые вы получаете - 501 = 3 * 167 –

-1

Некоторые советы по оптимизации:

  • проверки для делителей между 1 и SQRT (п). Я обещаю, что вы не найдете ничего выше этого предела (кроме самого номера).
  • не создавайте список делителей и подсчитывайте список, но считайте их напрямую.
+0

Но что я сделал, должен работать теоретически? –

+0

Он должен ... но кажется, что вы строите список чисел треугольника, имеющих> 500 делителей, и не просто ищет первый. Я не в Haskell, поэтому я мог бы ошибаться здесь :) – Zed

+2

Для каждого дивизора d, 1 <= d dave4420

2

Вероятно, скорее всего простое умножение числа, а затем использование факторизации для нахождения делителей, чем использование пробного деления со всеми числами < = sqrt (n).

Sieve of Eratosthenes - классический способ нахождения простых чисел, которые могут быть слегка изменены, чтобы найти количество делителей каждого натурального числа. Вместо того, чтобы просто маркировать каждый не-простой как «не простой», вы могли бы составить список всех простых чисел, делящих каждый номер. Затем вы можете использовать эти простые числа, чтобы вычислить полный набор делителей или просто их число, поскольку это все, что вам нужно.

Другим вариантом будет отметить не только кратность простых чисел, но и кратность всех натуральных чисел. Затем вы можете просто использовать счетчик, чтобы отслеживать количество делителей для каждого номера.

Вы также можете проверить The Genuine Sieve of Eratosthenes, что объясняет, почему пробное деление намного медленнее, чем реальное сито.

Последнее, вы должны внимательно изучить различные типы массивов в Haskell. Я думаю, что, возможно, проще использовать монаду ST для реализации решета, но может быть возможно достичь правильной сложности, используя accumArray, если вы можете убедиться, что ваша функция обновления является строгой. Мне никогда не удавалось заставить это работать, так что вы сами здесь.

+0

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

+0

@ Zed: Вы абсолютно правы. С головы до ног я не могу сказать, что асимптотически быстрее, но мое чувство кишки говорит мне, что мое решение будет быстрее, чем оригинальная версия Jonno_FTW. Я основываю это на количестве делителей, которые должны быть найдены, и тот факт, что добавление - это * путь * быстрее, чем деление. Кэш-оптимизация сита также может дать значительное ускорение посто нного коэффициента. Я однажды добился 12-кратного ускорения, просто повторно используя тот же небольшой блок ОЗУ. Оптимизация кэша Haskell может быть сложной. Решение rawicki выглядит, однако, намного лучше. –

+0

На самом деле, сито Эратосфена очень полезно, если вы объедините его с идеей rawickis. Я просто попробовал это на python, и для решения проблемы требуется всего лишь небольшая часть секунды. Глядя на эти две идеи, также поможет в решении других проблем проекта Эйлера. – Accipitridae

0

Если вы использовали C вместо Haskell, ваша функция все равно займет много времени.

Чтобы сделать это быстрее, вам нужно будет улучшить алгоритм, используя предложения из приведенных выше ответов.Я предлагаю изменить название и описание вопроса соответственно. После этого я удалю этот комментарий.

Если вы хотите, я могу портить проблему, поделившись своим решением.

Сейчас я дам вам свой код верхнего уровня:

main = 
    print . 
    head . filter ((> 500) . length . divisors) . 
    map (figureNum 3) $ [1..] 

алгоритмическая улучшение заключается в функции divisors. Вы можете улучшить его, используя предложение rawicki, но это уже занимает менее 100 мс.

Смежные вопросы