Возможные оптимизации для алгоритма Project Euler # 4

Найдите самый большой палиндром, составленный из двух трехзначных чисел.

Несмотря на то, что алгоритм достаточно быстр для решения данной проблемы, я хотел бы знать, пропустил ли я какие-либо очевидные оптимизации.

from __future__ import division
from math import sqrt

def createPalindrome(m):
    m = str(m) + str(m)[::-1]
    return int(m)

def problem4():
    for x in xrange(999,99,-1):
        a = createPalindrome(x)
        for i in xrange(999,int(sqrt(a)),-1):
            j = a/i
            if (j < 1000) and (j % 1 == 0):
                c = int(i * j)
                return c

person Nicolás Siplis    schedule 19.02.2015    source источник
comment
print my_number .... скорее всего будет работать намного быстрее ...   -  person Joran Beasley    schedule 19.02.2015


Ответы (3)


Идеи см. здесь

В C ++ я делаю так:

int euler004()
    {
    // A palindromic number reads the same both ways. The largest palindrome
    // made from the product of two 2-digit numbers is 9009 = 91  99.
    // Find the largest palindrome made from the product of two 3-digit numbers.
    const int N=3;
    const int N2=N<<1;
    int min,max,a,b,c,i,j,s[N2],aa=0,bb=0,cc=0;
    for (min=1,a=1;a<N;a++) min*=10; max=(min*10)-1;
    i=-1;
    for (a=max;a>=min;a--)
     for (b=a;b>=min;b--)
        {
        c=a*b; if (c<cc) continue;
        for (j=c,i=0;i<N2;i++) { s[i]=j%10; j/=10; }
        for (i=0,j=N2-1;i<j;i++,j--)
         if (s[i]!=s[j]) { i=-1; break; }
        if (i>=0) { aa=a; bb=b; cc=c; }
        }
    return cc; // cc is the output
    }
  • нет необходимости в sqrt ...
  • подвызов createPalindrome может замедлить работу из-за мусора кучи / стека
  • манипуляции со строками m = str(m) + str(m)[::-1] медленные
  • преобразование строки в int может быть быстрее, если вы сделаете это самостоятельно на массиве фиксированного размера
  • моя реализация занимает около 1,7 мс, но большая часть этого времени - это вывод и форматирование приложения (32-битное приложение AMD 3,2 ГГц на W7 x64) ...

[edit1] реализация вашей формулы

int euler004()
    {
    int i,c,cc,c0,a,b;
    for (cc=0,i=999,c0=1100*i;i>=100;i--,c0-=1100)
        {
        c=c0-(990*int(i/10))-(99*int(i/100));
        for(a=999;a>=300;a--)
         if (c%a==0)
            {
            b=c/a;
            if ((b>=100)&&(b<1000)) { cc=c; i=0; break; }
            }
        }
    return cc;
    }
  • это занимает ~ 0,4 мс

[edit2] дальнейшие оптимизации

//---------------------------------------------------------------------------
int euler004()
    {
    // A palindromic number reads the same both ways. The largest palindrome
    // made from the product of two 2-digit numbers is 9009 = 91  99.
    // Find the largest palindrome made from the product of two 3-digit numbers.
    int i0,i1,i2,c0,c1,c,cc=0,a,b,da;
    for (c0=  900009,i0=9;i0>=1;i0--,c0-=100001)    // first digit must be non zero so <1,9>
    for (c1=c0+90090,i1=9;i1>=0;i1--,c1-= 10010)    // all the rest <0,9>
    for (c =c1+ 9900,i2=9;i2>=0;i2--,c -=  1100)    // c is palindrome from 999999 to 100001
     for(a=999;a>=948;a-- )
      if (c%a==0)
        {
        // biggest palindrome is starting with 9
        // so smallest valid result is 900009
        // it is odd and sqrt(900009)=948 so test in range <948,999>
        b=c/a;
        if ((b>=100)&&(b<1000)) { cc=c; i0=0; i1=0; i2=0; break; }
        }
    return cc;
    }
//---------------------------------------------------------------------------
  • это слишком быстро для меня, чтобы правильно измерить время (необработанное время составляет около 0,037 мс)
  • удалены деления и умножения из генерации палиндрома
  • изменил диапазоны после некоторого числового анализа и размышлений в ожидании автобуса
  • первый цикл можно исключить (результат начинается с 9)
person Spektre    schedule 19.02.2015
comment
Я никогда не использовал C ++, поэтому могу ошибаться здесь, но разве вы не проверяете несколько чисел, чтобы узнать, палиндромы они или нет? Я использовал m = str(m) + str(m)[::-1], чтобы создать палиндром из 3-значного числа, чтобы избежать необходимости проверять несколько непалиндромов. Однако, поскольку вы заявили, что манипуляции со строками происходят медленно, я решил еще немного покопаться в палиндромах и нашел эту ссылку: math.stackexchange.com/questions/97752/ Итак, трехзначное число становится шестизначным палиндромом по формуле: 1100 × n − 990 × ⌊n / 10⌋ − 99 × ⌊N / 100⌋ - person Nicolás Siplis; 19.02.2015
comment
@ NicolásSiplis 1. Да, я проверяю все скалярные произведения, потому что быстрее решить, является ли число палиндромом, а затем проверить, делится ли оно на какое-то трехзначное число. 2. Вы можете использовать строку, но не создавайте ее динамически для каждой цифры, вместо этого создайте статический массив и установите цифры без перераспределения. 3. если вы хотите использовать эту формулу, то вы все еще сталкиваетесь с проблемой поиска делителя, вы можете ускорить его чем-то вроде сита из Erasthostenes, но для этого потребуется память и время инициализации, которое только для 6-значных чисел будет, вероятно, медленнее, чем у меня код. - person Spektre; 20.02.2015
comment
@ NicolásSiplis, эта ваша формула сортирует палиндромы, поэтому, если она правильно закодирована, она быстрее (так что я ошибался в предыдущем пункте 3 комментария) добавил код C ++ к моему ответу. его можно было бы дополнительно оптимизировать, но я не вижу в этом смысла (0,4 мс, из которых около 0,3 мс накладные расходы на формирование вывода приложения) - person Spektre; 20.02.2015
comment
@ NicolásSiplis пришла в голову идея, пока ждала автобус ... после кодирования результат в 10 раз быстрее см. Edit2 - person Spektre; 20.02.2015

Кажется, что самое большое замедление в моем коде - это преобразование целого числа в строку, добавление ее обратного и преобразование результата обратно в целое число.

Я поискал дополнительную информацию о палиндромах и наткнулся на эту формулу, которая позволяет мне преобразовать 3-значное число «n» в 6-значное палиндром «p» (может быть адаптировано для других цифр, но меня это не беспокоит. ).

p = 1100*n−990*⌊n/10⌋−99*⌊n/100⌋

Мой исходный код выполняется примерно за 0,75 мс, а новый занимает практически такое же количество времени (не говоря уже о том, что формулу пришлось бы адаптировать в зависимости от количества цифр «n»), поэтому я предполагаю, что их было не так много осталось выполнить оптимизацию.

person Nicolás Siplis    schedule 19.02.2015

Я написал это некоторое время назад, когда только начинал изучать Python, но вот он:

for i in range (999, 800, -1):

for j in range (999,800, -1):

    number = i*j
    str_number = str(number)

rev_str_number = str_number[::-1]

if str_number == rev_str_number:

    print("%s a palendrome") % number

Я не проверил все числа, которые вы сделали, но все же получил правильный ответ. Что я действительно узнал в этом упражнении, так это «::» и то, как это работает. Вы можете проверить это здесь.

Удачи с Эйлером!

person Chef1075    schedule 19.02.2015
comment
вы пробовали for j in range(i, 800, -1)? В противном случае вы проверяете одно и то же значение дважды ... - person ssm; 19.02.2015
comment
Я написал это так давно, я не уверен, что я пробовал, а что нет. Я просто вытащил старый файл и выложил его. - person Chef1075; 19.02.2015
comment
Проблема с этим решением в том, что вы проверяете несколько чисел, которые могут быть или не быть палиндромами. Я решил просто создать палиндром из заданного числа (999 возвращает 999999, 998 возвращает 998899 и т. Д.), А затем проверить, делится ли оно без остатка на два трехзначных числа. - person Nicolás Siplis; 19.02.2015
comment
Согласованный. Это неэффективно. Это был просто способ изучить синтаксис Python. - person Chef1075; 19.02.2015