Today, I was fooling around with a couple of programming puzzles. Faced with the task of testing a string to see whether or not it is a palindrome, I conceived of several ways t
Use the timeit module for speed-testing, the profile module for performance statistics, and the dis module for bytecode disassembly.
The script below demonstrates how the modules can be used.
One thing to notice from the output is how much the number of function calls can affect the overall performance (and, of course, the same thing goes for the number of bytecode instructions).
Hopefully, that (and a little more experimentation) should give you enough clues on how to improve the efficiency of your functions.
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)
Output:
##################################################
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
For timing small pieces of code like these functions, timeit can be very useful. Then you can find which one is faster yourself :)
Module timeit can be used. eg.:
import timeit
for method in 1, 2, 3:
print method, timeit.timeit('check_palin("victimmitciv", %i)' % method,
'from __main__ import check_palin', number=1000000)
You can't do a palindrome check faster than O(n) (n being the length of the input string). Any extra effort (stacks, reversing the string etc.) won't give you any perforce improvement, it only costs memory.