Почему мой код не работает быстрее после того, как я его отредактировал?

Я новичок в информатике и программировании. Я посещаю бесплатный онлайн-курс по программированию, и одним из заданий было написать программу, которая будет генерировать 1000-е простое число.

К вашему сведению, это в Python 2.5.4

Вот исходный код, который я скопировал (и немного отредактировал) из другой темы на этом форуме:

def isprime(n):

    if n<2:
        return False
    else:
        for i in range(2,(n/2+1)):
            if n%i==0:
                return False
        else:
            return True

def nthprime(n):
    x=[]
    j=2
    while len(x)<n:
        if (isprime(j)) == True:
            x.append(j)
        j =j+1
    return(x[n-1])

print nthprime(1000)

Однако я подумал, что смогу сделать программу быстрее, переписав функцию isprime(n) следующим образом:

def isprime(n):# First the primality test
    i=1
    if n<2:
        return False
    if (n!=2 and (n/2*2==n)):
        return False
    if n==3:
        return True
    if n==5:
        return True
    else:
        while i <= (n/2+1):
            i+=2
            if n%i==0:
                return False
        else:
              return True  

Таким образом, когда он только проверяет, делится ли n на нечетные целые числа (к этому моменту кода мы уже знаем, что n нечетно и, следовательно, не делится ни на какие четные целые числа).

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

Кроме того, есть ли способ переписать второй блок кода, чтобы избавиться от:

if n==3:
    return True
if n==5:
    return True 

Единственная причина, по которой я включил это, заключается в том, что я понял, что мой алгоритм генерирует «ложь» для 3 и 5, даже если они простые.


person Matt    schedule 28.06.2017    source источник
comment
Почему вы изучаете версию Python, которой девять лет? Вы должны посмотреть на python 3.6   -  person Wombatz    schedule 28.06.2017
comment
Подсказка: если вы ищете пары множителей, один множитель всегда будет ‹= квадратному корню из произведения.   -  person Klaus D.    schedule 28.06.2017
comment
Вы должны рассчитать свой код. Ваша новая версия на 42% быстрее, измерено в моей системе.   -  person Mark Tolonen    schedule 28.06.2017
comment
n/2*2==n — плохой (и неправильный в Python 3) способ проверки четных чисел.   -  person treecoder    schedule 28.06.2017
comment
Кстати, причина, по которой 3 и 5 были false, заключалась в том, что вы выполняете i += 2 перед проверкой, является ли оно простым, поэтому в итоге вы тестируете 3%3 и 5%5. Сохранив структуру старого кода, вы могли бы сохранить for и шаг за шагом два: for i in range(3,(n/2+1), 2) или for i in range(3,(math.sqrt(n)+1), 2) для лучшего верхнего предела.   -  person Ken Y-N    schedule 28.06.2017
comment
Спасибо. Я изучаю версию, которой 9 лет, потому что я провожу бесплатный онлайн-курс на ocw.mit.edu (MIT Open Courseware), выпущенный осенью 2008 года.   -  person Matt    schedule 28.06.2017
comment
Марк Толонен, какую систему вы используете для определения скорости программы?   -  person Matt    schedule 28.06.2017


Ответы (1)


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

Первое, что я бы порекомендовал, — это мгновенное исключение четных чисел:

def isprime(n):
    if n < 2 or n % 2 == 0:
        return False
    # ...

Еще одно важное место, которое вы проверяете гораздо больше, чем вам нужно, — это проверка факторов, к которой вы возвращаетесь, когда оптимизация терпит неудачу. Вам не нужно проходить весь путь до n / 2; Самый большой фактор, о котором вам нужно беспокоиться, это sqrt(n) (как только вы прошли корень n, вы начинаете проверять факторы, пары которых вы уже проверили, например, если вы проверяете n = 6, как только вы проверяете 2, вы уже проверили 3 ). Вот соответствующая оптимизация:

def isprime(n):
    # ...
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False

В общем, вот новый isprime (EDIT: с советами из комментариев):

def isprime(n):
    if n == 2: return True
    if n % 2 == 0: return False

    for i in xrange(3, int(n ** 0.5) + 1, 2):
        if n % i == 0: return False

    return True

При вычислении nthprime(5000) эти 2 оптимизации заняли мое время с 8,497 секунд до 0,108 секунд.

РЕДАКТИРОВАТЬ: демонстрация

person wbadart    schedule 28.06.2017
comment
int(n ** .5) + 1, на самом деле, при использовании с range (который должен быть xrange в Python 2.x). По крайней мере, проверьте, что 1000-е простое число равно 7919. - person Mark Tolonen; 28.06.2017
comment
@treecoder Мне любопытно, что не так с math.sqrt()? - person Ken Y-N; 28.06.2017
comment
Вы не используете столько преимуществ, исключая даже значения n, сколько могли бы. Вы также можете пропустить проверку четных факторов. Я бы сделал if n == 2: return True, if n % 2 == 0: return False, for i in xrange(3, int(n ** 0.5) + 1, 2): .... - person Blckknght; 28.06.2017
comment
@KenY-N math.sqrt(10000) в моей системе заняло 286 нс. 10000**.5 заняло 23,6 нс. (проверено с помощью модуля timeit). - person Mark Tolonen; 28.06.2017
comment
Ничего плохого в math.sqrt как таковом нет, но когда мы можем использовать оператор для чего-то, использование функции не кажется идиоматическим. С add(1,1) все в порядке, но 1+1 чувствует себя лучше. Хотя можно сказать, что math.sqrt более читабелен для тех, кто не знаком с **. - person treecoder; 28.06.2017
comment
И профилирование может пойти в пользу **, как указывает Марк Толонен. - person treecoder; 28.06.2017
comment
Просто любопытно, в чем разница между range и xrange? Это просто разница между версиями Python? Я на Python 2.5.4, и диапазон работает нормально. xrange становится фиолетовым, когда я его набираю, но я не уверен, что он делает. - person Matt; 28.06.2017
comment
xrange дает объект диапазона вместо списка. Это означает, что сгенерированные числа загружаются в память только по мере необходимости (т.е. с каждой последующей итерацией), а не все сразу в список. В Python 3 это стало поведением по умолчанию встроенной функции range (вы все еще можете добиться старого поведения с помощью list(range(n))). - person wbadart; 28.06.2017