2012-01-07 2 views
3

Сегодня я обманывал пару паролей программирования. Столкнувшись с задачей тестирования строки, чтобы увидеть, является ли она палиндром, я задумал несколько способов сделать это. Основы этих трех методов изображены ниже (наиболее аккуратный и тестовый код опущен).Производительность различных методов тестирования для палиндрома [Python]

def check_palin(victim, method): 
if method is 1: # check progressively inner chars 
    x = 0 
    while x < (len(victim)/2): # len/2 is num of iter needed for guarantee 
     if victim[x+0] is victim[-(1+x)]: # on pass n, compare nth letter 
              # and nth to last letter 
      x += 1 # then increment the n counter 
     else: 
      return False 
    return True 
elif method is 2: # check first and last chars repeatedly 
    tmp = [] 
    for i in victim: 
     tmp.append(i) # convert string into list 
    while len(tmp) > 1: # if 1 or 0 char left, palin is guaranteed 
     if tmp[0] is tmp[-1]: # if the first and last characters are the same letter 
      tmp.pop(0) # remove them both 
      tmp.pop(-1) 
     else: 
      return False 
    return True 
elif method is 3: # reverse string and compare to original 
    tmp = "" 
    for i in victim: # for every letter 
     tmp = i + tmp # cat it to the beginning, not append   
    return tmp == victim 
else: 
    return -1 

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

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

Способ 2 также использует индексацию символов в строке. Первый и последний символы сравниваются, затем отбрасываются, и эти шаги повторяются до тех пор, пока палиндром не будет гарантирован (или опровергнут).

Затраты будут аналогичны методу 1 с некоторыми отличиями: добавление преобразования из str -> list, выскакивающие элементы из списка и минус переменная счетчика.

Способ 3 отменяет данную строку, а затем сравнивает ее с оригиналом. Существуют различные способы изменения строки (list.__reversed__() и т. Д.), Но я показал только одну такую ​​возможность: преобразование строки в список и последующее объединение каждого элемента этого списка в НАЧАЛО новой строки.

С помощью различных методов для реверсирования строки могут быть разные операции и, следовательно, затраты. Для моего выбранного метода здесь мы имеем стоимость нарезки каждого элемента из списка и объединения его с переменной str.

Мои вопросы:

Какой из этих методов были бы быстрее выполнения и почему? Кроме того, есть ли способ повысить эффективность этих методов? (По касательной, как вы проверяете скорость выполнения модулей в Python?)

+6

Там очень простой способ узнать, какой метод является самым быстрым: ** измерить их скорость **. –

+1

Oneliner для метода 3: ispalin = lambda s: s == s [:: - 1] –

+2

Не используйте 'is' для сравнения ни с чем, кроме одиночных, таких как' True' или 'False' - это может вести себя странно. Реализация Python не требуется, чтобы каждая строка с одним символом была одиночной, поэтому сравнение может завершиться неудачей. Даже с CPython '2048 равно 2047 + 1' является False, так как' 'ab 'is' abc '[0: 2]', а другой интерпретатор может даже сделать '' a' is 'a'' False. – Tanriol

ответ

2

Используйте timeit модуль для скоростно-тестирования, profile модуля для статистики производительности, а dis модуля для байткода разборки.

В приведенном ниже сценарии показано, как использовать модули.

Одна вещь, чтобы заметить, что количество вызовов функций может влиять на общую производительность (и, конечно же, это то же самое касается количества инструкций байткода).

Надеюсь, что (и немного больше экспериментов) должно дать вам достаточно информации о том, как повысить эффективность ваших функций.

from timeit import timeit 
from cProfile import runctx 
from dis import dis 

def analyse(*args): 
    victim = 'detartrated' 
    number = 1000 
    for func in args: 
     print('\n%s\n' % ('#' * 50)) 
     name = func.__name__ 
     print('test: %s(%r): %r' % (name, victim, func(victim))) 
     code = '%s(%r)' % (name, victim) 
     duration = timeit(
      code, 'from __main__ import %s' % name, number=number) 
     usec = 1000000 * duration/number 
     print('time: %s: %.2f usec/pass\n' % (code, usec)) 
     runctx(code, globals(), locals()) 
     dis(func) 

def check_palin1(victim): 
    """ check progressively inner chars """ 
    x = 0 
    # len/2 is num of iter needed for guarantee 
    while x < (len(victim)/2): 
     # on pass n, compare nth letter and nth to last letter 
     if victim[x+0] is victim[-(1+x)]: 
      # then increment the n counter 
      x += 1 
     else: 
      return False 
    return True 

def check_palin2(victim): 
    """ check first and last chars repeatedly """ 
    tmp = [] 
    for i in victim: 
     # convert string into list 
     tmp.append(i) 
    # if 1 or 0 char left, palin is guaranteed 
    while len(tmp) > 1: 
     # if the first and last characters are the same letter 
     if tmp[0] is tmp[-1]: 
      # remove them both 
      tmp.pop(0) 
      tmp.pop(-1) 
     else: 
      return False 
    return True 

def check_palin3(victim): 
    """ reverse string and compare to original using a loop """ 
    tmp = "" 
    # for every letter 
    for i in victim: 
     # cat it to the beginning, not append 
     tmp = i + tmp 
    return tmp == victim 

def check_palin4(victim): 
    """ reverse string and compare to original using slice syntax """ 
    return victim == victim[::-1] 

analyse(check_palin1, check_palin2, check_palin3, check_palin4) 

Выход:

################################################## 

test: check_palin1('detartrated'): True 
time: check_palin1('detartrated'): 3.80 usec/pass 

     9 function calls in 0.000 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 0.000 0.000 <string>:1(<module>) 
     1 0.000 0.000 0.000 0.000 test.py:20(check_palin1) 
     6 0.000 0.000 0.000 0.000 {len} 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 


22   0 LOAD_CONST    1 (0) 
       3 STORE_FAST    1 (x) 

24   6 SETUP_LOOP    72 (to 81) 
     >> 9 LOAD_FAST    1 (x) 
      12 LOAD_GLOBAL    0 (len) 
      15 LOAD_FAST    0 (victim) 
      18 CALL_FUNCTION   1 
      21 LOAD_CONST    2 (2) 
      24 BINARY_DIVIDE  
      25 COMPARE_OP    0 (<) 
      28 POP_JUMP_IF_FALSE  80 

26   31 LOAD_FAST    0 (victim) 
      34 LOAD_FAST    1 (x) 
      37 LOAD_CONST    1 (0) 
      40 BINARY_ADD   
      41 BINARY_SUBSCR  
      42 LOAD_FAST    0 (victim) 
      45 LOAD_CONST    3 (1) 
      48 LOAD_FAST    1 (x) 
      51 BINARY_ADD   
      52 UNARY_NEGATIVE  
      53 BINARY_SUBSCR  
      54 COMPARE_OP    8 (is) 
      57 POP_JUMP_IF_FALSE  73 

28   60 LOAD_FAST    1 (x) 
      63 LOAD_CONST    3 (1) 
      66 INPLACE_ADD   
      67 STORE_FAST    1 (x) 
      70 JUMP_ABSOLUTE   9 

30  >> 73 LOAD_GLOBAL    1 (False) 
      76 RETURN_VALUE   
      77 JUMP_ABSOLUTE   9 
     >> 80 POP_BLOCK   

31  >> 81 LOAD_GLOBAL    2 (True) 
      84 RETURN_VALUE   

################################################## 

test: check_palin2('detartrated'): True 
time: check_palin2('detartrated'): 10.57 usec/pass 

     30 function calls in 0.000 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 0.000 0.000 <string>:1(<module>) 
     1 0.000 0.000 0.000 0.000 test.py:33(check_palin2) 
     6 0.000 0.000 0.000 0.000 {len} 
     11 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects} 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 
     10 0.000 0.000 0.000 0.000 {method 'pop' of 'list' objects} 


35   0 BUILD_LIST    0 
       3 STORE_FAST    1 (tmp) 

36   6 SETUP_LOOP    27 (to 36) 
       9 LOAD_FAST    0 (victim) 
      12 GET_ITER    
     >> 13 FOR_ITER    19 (to 35) 
      16 STORE_FAST    2 (i) 

38   19 LOAD_FAST    1 (tmp) 
      22 LOAD_ATTR    0 (append) 
      25 LOAD_FAST    2 (i) 
      28 CALL_FUNCTION   1 
      31 POP_TOP    
      32 JUMP_ABSOLUTE   13 
     >> 35 POP_BLOCK   

40  >> 36 SETUP_LOOP    75 (to 114) 
     >> 39 LOAD_GLOBAL    1 (len) 
      42 LOAD_FAST    1 (tmp) 
      45 CALL_FUNCTION   1 
      48 LOAD_CONST    1 (1) 
      51 COMPARE_OP    4 (>) 
      54 POP_JUMP_IF_FALSE  113 

42   57 LOAD_FAST    1 (tmp) 
      60 LOAD_CONST    2 (0) 
      63 BINARY_SUBSCR  
      64 LOAD_FAST    1 (tmp) 
      67 LOAD_CONST    3 (-1) 
      70 BINARY_SUBSCR  
      71 COMPARE_OP    8 (is) 
      74 POP_JUMP_IF_FALSE  106 

44   77 LOAD_FAST    1 (tmp) 
      80 LOAD_ATTR    2 (pop) 
      83 LOAD_CONST    2 (0) 
      86 CALL_FUNCTION   1 
      89 POP_TOP    

45   90 LOAD_FAST    1 (tmp) 
      93 LOAD_ATTR    2 (pop) 
      96 LOAD_CONST    3 (-1) 
      99 CALL_FUNCTION   1 
      102 POP_TOP    
      103 JUMP_ABSOLUTE   39 

47  >> 106 LOAD_GLOBAL    3 (False) 
      109 RETURN_VALUE   
      110 JUMP_ABSOLUTE   39 
     >> 113 POP_BLOCK   

48  >> 114 LOAD_GLOBAL    4 (True) 
      117 RETURN_VALUE   

################################################## 

test: check_palin3('detartrated'): True 
time: check_palin3('detartrated'): 2.77 usec/pass 

     3 function calls in 0.000 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 0.000 0.000 <string>:1(<module>) 
     1 0.000 0.000 0.000 0.000 test.py:50(check_palin3) 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 


52   0 LOAD_CONST    1 ('') 
       3 STORE_FAST    1 (tmp) 

54   6 SETUP_LOOP    24 (to 33) 
       9 LOAD_FAST    0 (victim) 
      12 GET_ITER    
     >> 13 FOR_ITER    16 (to 32) 
      16 STORE_FAST    2 (i) 

56   19 LOAD_FAST    2 (i) 
      22 LOAD_FAST    1 (tmp) 
      25 BINARY_ADD   
      26 STORE_FAST    1 (tmp) 
      29 JUMP_ABSOLUTE   13 
     >> 32 POP_BLOCK   

57  >> 33 LOAD_FAST    1 (tmp) 
      36 LOAD_FAST    0 (victim) 
      39 COMPARE_OP    2 (==) 
      42 RETURN_VALUE   

################################################## 

test: check_palin4('detartrated'): True 
time: check_palin4('detartrated'): 0.65 usec/pass 

     3 function calls in 0.000 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 0.000 0.000 <string>:1(<module>) 
     1 0.000 0.000 0.000 0.000 test.py:59(check_palin4) 
     1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 


61   0 LOAD_FAST    0 (victim) 
       3 LOAD_FAST    0 (victim) 
       6 LOAD_CONST    1 (None) 
       9 LOAD_CONST    1 (None) 
      12 LOAD_CONST    2 (-1) 
      15 BUILD_SLICE    3 
      18 BINARY_SUBSCR  
      19 COMPARE_OP    2 (==) 
      22 RETURN_VALUE   
2

Вы не можете делать проверку палиндрома быстрее, чем O (n) (n - длина входной строки). Любое дополнительное усилие (стеки, реверсирование строки и т. Д.) Не даст вам какого-либо улучшения, это только стоит памяти.

0

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

1

Возможно использование модуля timeit. например .:

import timeit 

for method in 1, 2, 3: 
    print method, timeit.timeit('check_palin("victimmitciv", %i)' % method, 
         'from __main__ import check_palin', number=1000000) 
Смежные вопросы