Project Euler 5 в Python - Как я могу оптимизировать свое решение? - PullRequest
9 голосов
/ 06 ноября 2011

Я недавно работал над проблемами Project Euler в Python.Я довольно новичок в Python, и все еще несколько новичок в качестве программиста.

В любом случае, я столкнулся с проблемой скорости, кодирующей решение проблемы № 5.Проблема в том, что

"2520 - это наименьшее число, которое можно разделить на каждое из чисел от 1 до 10 без остатка. Наименьшее положительное число, которое равномерно делится на все числа от 1to 20? "

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

Написанный мною код успешно работает на примере 2520 и в диапазоне от 1 до 10, и его следует изменять напрямую для работы с вопросом.Однако, запустив его, я не получаю ответ.Предположительно, это очень большое число, а код недостаточно быстрый.Печать текущего проверяемого числа, кажется, поддерживает это, достигая нескольких миллионов без ответа.

Код в его текущей реализации выглядит следующим образом:

rangemax = 20
def div_check(n):
    for i in xrange(11,rangemax+1):
        if n % i == 0:
            continue
        else:
            return False
    return True

if __name__ == '__main__':
   num = 2
   while not div_check(num):
       print num
       num += 2
   print num

Я уже сделалпара изменений, которые, я думаю, должны помочь скорости.Во-первых, для того, чтобы число делилось на все числа от 1 до 20, оно должно быть четным, поскольку только четные числа делятся на 2. Следовательно, я могу увеличивать на 2 вместо 1. Кроме того, хотя я и не думал оЯ сам нашел, что кто-то указал, что число, делимое на 11 - 20, делится на 1 - 10. (Не проверял это число, но кажется разумным)

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

Заранее спасибо всем, кто может помочь.

Ответы [ 20 ]

18 голосов
/ 06 ноября 2011

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

Поскольку нам нужен сравнительно короткий список проверенных номеров, мы можем предварительно составить список номеров, а не повторно вызывать xrange() или range().

Кроме того, хотя бы просто поставить цифры [1, 2, 3, ..., 20] в список, мы можем немного подумать и вытащить числа:

Просто возьми 1. Каждое целое число делится поровну на 1.

Если мы оставляем 20 в, нет необходимости оставлять 2 в. Любое целое число, равномерно делимое на 20, равномерно делится на 2 (но обратное может быть неверным). Таким образом, мы оставляем 20 и вынимаем 2, 4 и 5. Оставляем 19, так как это простое число. Оставьте 18, но теперь мы можем убрать 3 и 6. Если вы повторите этот процесс, вы получите гораздо более короткий список чисел, чтобы попробовать.

Мы начинаем с 20, а номера шагов с 20, как предложил Майкл Миор. Мы используем выражение-генератор внутри all(), как подсказывает poke.

Вместо цикла while я использовал цикл for с xrange(); Я думаю, что это немного быстрее.

Результат:

check_list = [11, 13, 14, 16, 17, 18, 19, 20]

def find_solution(step):
    for num in xrange(step, 999999999, step):
        if all(num % n == 0 for n in check_list):
            return num
    return None

if __name__ == '__main__':
    solution = find_solution(20)
    if solution is None:
        print "No answer found"
    else:
        print "found an answer:", solution

На моем компьютере это находит ответ менее чем за девять секунд.

EDIT: И, если мы примем совет от Давида Заславского, мы поймем, что можем запустить цикл в 2520 и шаг в 2520. Если я это сделаю, то на моем компьютере я получу правильный ответ примерно за одну десятую секунды.

Я заставил find_solution() принять аргумент. Попробуйте позвонить find_solution(2520).

8 голосов
/ 06 ноября 2011

Мой первый ответ ускорил исходный расчет от вопроса.

Вот еще один ответ, который решает его по-другому: просто найдите все основные множители каждого числа, а затем умножьте их вместе, чтобы перейти прямо к ответу. Другими словами, это автоматизирует процесс, рекомендованный poke в комментарии.

Он заканчивается за доли секунды. Я не думаю, что есть более быстрый способ сделать это.

Я выполнил поиск в Google по запросу «найти простые факторы Python» и обнаружил следующее:

http://www.stealthcopter.com/blog/2009/11/python-factors-of-a-number/

С этого момента я нашел ссылку на factor.py (написанную Майком Хансеном) с некоторыми полезными функциями:

https://gist.github.com/weakish/986782#file-factor-py

Его функции делали не совсем то, что я хотел, поэтому я написал новый, но использовал его pull_prime_factors(), чтобы выполнить тяжелую работу. Результатом было find_prime_factors(), которое возвращает список кортежей: простое число и число. Например, find_prime_factors(400) возвращает [(2,4), (5,2)], потому что главные факторы 400: (2 * 2 * 2 * 2) * (5 * 5)

Затем я использую простой defaultdict(), чтобы отследить, сколько мы видели до сих пор каждого простого фактора.

Наконец, цикл умножает все вместе.

from collections import defaultdict
from factor import pull_off_factors

pf = defaultdict(int)

_primes = [2,3,5,7,11,13,17,19,23,29]
def find_prime_factors(n):
    lst = []
    for p in _primes:
        n = pull_off_factors(n, p, lst)
    return lst

def find_solution(low, high):
    for num in xrange(low, high+1):
        lst = find_prime_factors(num)
        for n, count in lst:
            pf[n] = max(pf[n], count)

    print "prime factors:", pf
    solution = 1
    for n, count in pf.items():
        solution *= n**count

    return solution

if __name__ == '__main__':
    solution = find_solution(1, 20)
    print "answer:", solution

РЕДАКТИРОВАТЬ: Ого, я просто взглянул на @ J.F. Ответ Себастьяна на связанный вопрос. Его ответ, по сути, аналогичен приведенному выше коду, но гораздо проще и элегантнее. И это на самом деле быстрее, чем приведенный выше код.

Наименьшее общее кратное для 3 или более чисел

Я оставлю вышеупомянутое, потому что я думаю, что функции могут иметь другое использование в Project Euler. Но вот решение Дж. Ф. Себастьяна:

def gcd(a, b):
    """Return greatest common divisor using Euclid's Algorithm."""
    while b:
        a, b = b, a % b
    return a

def lcm(a, b):
    """Return lowest common multiple."""
    return a * b // gcd(a, b)

def lcmm(*args):
    """Return lcm of args."""   
    return reduce(lcm, args)

def lcm_seq(seq):
    """Return lcm of sequence."""
    return reduce(lcm, seq)

solution = lcm_seq(xrange(1,21))
print "lcm_seq():", solution

Я добавил lcm_seq(), но вы также можете позвонить:

lcmm(*range(1, 21))
6 голосов
/ 06 ноября 2011

Поскольку ваш ответ должен делиться на 20, вы можете начать с 20 и увеличить на 20 вместо двух. В общем, вы можете начать с rangemax и увеличить на rangemax. Это уменьшает количество вызовов div_check на порядок.

1 голос
/ 15 июля 2012

Два различных типа решений были размещены здесь. Один тип использует gcd вычисления; другой использует первичную факторизацию. Я предложу третий тип, который основан на методе первичной факторизации, но, вероятно, будет намного быстрее, чем сама первичная факторизация. Он опирается на несколько простых наблюдений о простых степенях - простых числах, возведенных в некоторую целую степень Короче говоря, оказывается, что наименьшее общее кратное всех чисел ниже некоторого числа n равно произведению всех максимальных простых степеней ниже n.

Чтобы доказать это, мы начнем с размышления о свойствах, которыми должен обладать x, наименьший общий множитель из всех чисел ниже n, и выразим их в терминах простых степеней.

  1. x должно быть кратно всем простым силам ниже n. Это очевидно; скажем n = 20. 2, 2 * 2, 2 * 2 * 2 и 2 * 2 * 2 * 2 все ниже 20, поэтому все они должны делиться x. Аналогично, 3 и 3 * 3 оба ниже n, и поэтому оба должны делиться x.

  2. Если какое-то число a кратно основной степени p ** e, а p ** e - максимальная степень p ниже n, то a также кратно всем меньшие простые степени p. Это также довольно очевидно; если a == p * p * p, то a == (p * p) * p.

  3. По теореме об уникальной факторизации любое число m может быть выражено в виде кратного числа простых степеней, меньшего m. Если m меньше n, тогда m может быть выражено как кратность простых степеней, меньших n.

Взятые вместе, два последних наблюдения показывают, что любое число x, кратное всем максимальным простым степеням ниже n, должно быть общим кратным всем числам ниже n. Согласно (2), если x кратно всем максимальным простым степеням ниже n, оно также кратно всем простым степеням ниже n. Таким образом, согласно (3), это также , кратное всем другим числам ниже n, поскольку все они могут быть выражены как кратные простых степеней ниже n.

Наконец, учитывая (1), мы можем доказать, что x также является наименьшим общим кратным всех чисел ниже n, поскольку любое число, меньшее x, не может быть кратным всех максимальных простых степеней ниже n, и поэтому не может удовлетворить (1).

Результатом всего этого является то, что нам не нужно ничего разлагать. Мы можем просто генерировать простые числа меньше чем n!

Учитывая хорошо оптимизированное сито эратосфена , можно сделать это очень быстро за n ниже миллиона. Тогда все, что вам нужно сделать, это найти максимальную простую мощность ниже n для каждого простого и умножить их вместе.

prime_powers = [get_max_prime_power(p, n) for p in sieve(n)]
result = reduce(operator.mul, prime_powers)

Я оставлю запись get_max_prime_power в качестве упражнения. Быстрая версия в сочетании с вышеупомянутым может генерировать lcm всех чисел ниже 200000 за 3 секунды на моей машине.

В результате получается 86871-значное число!

1 голос
/ 13 октября 2014

Это решение работало довольно быстро для меня (импорт numpy).

t0 = time.time()
import numpy

ints = numpy.array(range(1,21))
primes = [2,3,5,7,11,13,17,19] # under 20
facts = []
for p in primes:
    counter = 0
    nums = ints
    while any(nums % p == 0):
        nums = nums / float(p)
        counter += 1
    facts.append(counter)

facts = numpy.array(facts)
mults = primes**facts
ans = 1
for m in mults:
    ans = m * ans

t1 =time.time()
perf = t1 - t0
print "Problem 5\nAnswer:",ans, "runtime:", perf, "seconds"

"""Problem 5
Answer: 232792560 runtime: 0.00505399703979 seconds"""
1 голос
/ 06 ноября 2011

Понимание списка быстрее, чем для циклов.

Сделайте что-то подобное, чтобы проверить число:

def get_divs(n):
    divs = [x for x in range(1,20) if n % x == 0]
    return divs

Затем вы можете проверить длину массива div, чтобы увидеть,цифры присутствуют.

1 голос
/ 06 августа 2015

Я написал решение для euler5, которое:

  • Это на несколько порядков быстрее, чем большинство решений здесь, когда n = 20 (хотя не все респонденты сообщают о своем времени), потому что он не использует импорт (кроме измерения времени для этого ответа) и только базовые структуры данных в python.
  • Масштабируется намного лучше, чем большинство других решений. Он даст ответ для n = 20 через 6e-05 секунд или для n = 100 в 1 миллисекунде, быстрее, чем большинство ответов для n = 20, перечисленных здесь.

    import time
    a=time.clock() # set timer
    
    j=1
    factorlist=[]
    mydict={}
    # change second number to desired number +1 if the question were changed.
    for i in range(2,21,1):
        numberfactors=[]
        num=i
        j=2
    # build a list of the prime factors
        for j in range(j,num+1,1):
            counter=0
            if i%j==0:
                while i%j==0:
                    counter+=1
                    numberfactors.append(j)
                    i=i/j
    # add a list of factors to a dictionary, with each prime factor as a key
                    if j not in mydict:
                        mydict[j] = counter
    # now, if a factor is already present n times, including n times the factor
    # won't increase the LCM. So replace the dictionary key with the max number of
    # unique factors if and only if the number of times it appears is greater than
    # the number of times it has already appeared.
    # for example, the prime factors of 8 are 2,2, and 2. This would be replaced 
    # in the dictionary once 16 were found (prime factors 2,2,2, and 2).
                elif mydict[j] < counter:
                    mydict[j]=counter
    
    total=1
    for key, value in mydict.iteritems():
        key=int(key)
        value=int(value)
        total=total*(key**value)
    
    b=time.clock()
    elapsed_time=b-a
    print total, "calculated in", elapsed_time, "seconds"
    

    возвращается:

    232792560 calculated in 6e-05 seconds
    
    # does not rely on heuristics unknown to all users, for instance the idea that 
    # we only need to include numbers above 10, etc.
    
    
    # For all numbers evenly divisible by 1 through 100:
    69720375229712477164533808935312303556800 calculated in 0.001335 seconds
    
1 голос
/ 29 августа 2014

Разбейте число как простую факторизацию.

Все простые числа меньше 20:

2,3,5,7,11,13,17,19

Таким образом, минимальное число, которое можно разделить на эти числа:

2*3*5*7*11*13*17*19

Композиты:

4,6,8,9,10,12,14,15,16,18,20 = 2^2, 2*3, 2^3, 3^2, 2*5, 2^2*3, 2*7, 3*5, 2*3^2, 2^2*5

Начиная слева, чтобы увидеть, какие факторы необходимы:

  • 2^3 для сборки 4, 8, и 16
  • 3 для сборки 9
  • Первичная факторизация: 2^4 * 3^2 * 5 * 7 * 11 * 13 * 17 * 19 = 232,792,560
1 голос
/ 24 декабря 2013

Я получил решение за 0,066 миллисекунды (только 74 вращения через цикл), используя следующую процедуру:

Начните с наименьшего кратного для 1, что = 1. Затем найдите наименьшее кратное для next_number_up.Сделайте это, добавив к нему предыдущее наименьшее кратное (smalllest_multiple = smalllest_multiple + prev_prod) до next_number_up% smalllest_multiple == 0. В этот момент smalllest_multiple является правильным наименьшим кратным для next_number_up.Затем увеличивайте значение next_number_up и повторяйте до тех пор, пока не достигнете желаемого smalllest_multiple (в данном случае 20 раз).Я считаю, что это находит решение примерно за n * log (n) времени (хотя, учитывая то, как числа, кажется, работают, кажется, что оно завершается гораздо быстрее, чем обычно).

Например:

1 является наименьшим кратным для 1

Найти наименьшее кратное для 2

Проверьте, не является ли предыдущее наименьшее кратное произведение 1/2 = .5, поэтому

нет предыдущего наименьшего кратного+ предыдущее наименьшее кратное == 2.

Проверить, делится ли 2 на 2 - да, поэтому 2 наименьшее кратное для 2

Найти наименьшее кратное для 3

Проверитьесли предыдущее наименьшее кратное число работает 2/3 = .667, то нет

предыдущее наименьшее кратное + предыдущее наименьшее кратное == 4

Проверьте, делится ли 4 на 3 - нет

4 + предыдущее наименьшее кратное == 6

Проверьте, делится ли 6 на 3 - да, поэтому 6 является наименьшим кратным для 3

Найдите наименьшее кратное для 4

Проверьте, не является ли предыдущая наименьшая множественная работа 6/4 = 1,5, поэтому не

предыдущая наименьшая9-й кратный + предыдущий наименьший кратный == 12

Проверьте, делится ли 12 на 4 - да, поэтому 12 является наименьшим кратным для 4

, повторяющихся до 20 ..

Ниже приведен код в ruby, реализующий этот подход:

def smallestMultiple(top)
    prod = 1
    counter = 0
    top.times do
        counter += 1
        prevprod = prod
        while prod % counter != 0
            prod = prod + prevprod
        end
    end
    return prod
end
0 голосов
/ 26 февраля 2018

Вот наблюдение по этой проблеме.В конечном счете, чтобы найти решение, требуется 48 итераций.

Любое число, которое делится на все числа из 1..20, должно делиться на произведение простых чисел в этом диапазоне, а именно 2, 3,5, 7, 11, 13, 17 и 19. Он не может быть меньше, чем произведение этих простых чисел, поэтому давайте используем это число, 232 792 560, в качестве приращения, а не 20, или 2 520, или какое-то другое число.

Как оказалось, 48 * 232 792 560 делится на на все числа 1..20.Между прочим, произведение всех не простых чисел между 1..20 - 66. Я не совсем понял связь между 48 и 66 в этом контексте.

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