OK, I have measured the solution. The reversed solutions are about the same. The forward while
loop is about 4 times slower. BUT! The Patrik's dirty solution is about 80 times faster for the list of 100,000 random integers [bug in Patrik2 corrected]:
import timeit
import random
def solution_ninjagecko1(lst):
for i in xrange(len(lst)-1, -1, -1):
if lst[i] % 2 != 0: # simulation of the choice
del lst[i]
return lst
def solution_jdi(lst):
L = len(lst) - 1
for i, v in enumerate(reversed(lst)):
if v % 2 != 0:
lst.pop(L-i) # L-1 is the forward index
return lst
def solution_Patrik(lst):
for i, v in enumerate(lst):
if v % 2 != 0: # simulation of the choice
lst[i] = None
return [v for v in lst if v is not None]
def solution_Patrik2(lst):
##buggy lst = [v for v in lst if v % 2 != 0]
##buggy return [v for v in lst if v is not None]
# ... corrected to
return [v for v in lst if v % 2 != 0]
def solution_pepr(lst):
i = 0 # indexing the processed item
n = 0 # enumerating the original position
while i < len(lst):
if lst[i] % 2 != 0: # simulation of the choice
del lst[i] # i unchanged if item deleted
else:
i += 1 # i moved to the next
n += 1
return lst
def solution_pepr_reversed(lst):
i = len(lst) - 1 # indexing the processed item backwards
while i > 0:
if lst[i] % 2 != 0: # simulation of the choice
del lst[i] # i unchanged if item deleted
i -= 1 # i moved to the previous
return lst
def solution_steveha(lst):
def should_keep(x):
return x % 2 == 0
return filter(should_keep, lst)
orig_lst = range(30)
print 'range() generated list of the length', len(orig_lst)
print orig_lst[:20] + ['...'] # to have some fun :)
lst = orig_lst[:] # copy of the list
print solution_ninjagecko1(lst)
lst = orig_lst[:] # copy of the list
print solution_jdi(lst)
lst = orig_lst[:] # copy of the list
print solution_Patrik(lst)
lst = orig_lst[:] # copy of the list
print solution_pepr(lst)
orig_lst = [random.randint(1, 1000000) for n in xrange(100000)]
print '\nrandom list of the length', len(orig_lst)
print orig_lst[:20] + ['...'] # to have some fun :)
lst = orig_lst[:] # copy of the list
t = timeit.timeit('solution_ninjagecko1(lst)',
'from __main__ import solution_ninjagecko1, lst',
number=1)
print 'solution_ninjagecko1: ', t
lst = orig_lst[:] # copy of the list
t = timeit.timeit('solution_jdi(lst)',
'from __main__ import solution_jdi, lst',
number=1)
print 'solution_jdi: ', t
lst = orig_lst[:] # copy of the list
t = timeit.timeit('solution_Patrik(lst)',
'from __main__ import solution_Patrik, lst',
number=1)
print 'solution_Patrik: ', t
lst = orig_lst[:] # copy of the list
t = timeit.timeit('solution_Patrik2(lst)',
'from __main__ import solution_Patrik2, lst',
number=1)
print 'solution_Patrik2: ', t
lst = orig_lst[:] # copy of the list
t = timeit.timeit('solution_pepr_reversed(lst)',
'from __main__ import solution_pepr_reversed, lst',
number=1)
print 'solution_pepr_reversed: ', t
lst = orig_lst[:] # copy of the list
t = timeit.timeit('solution_pepr(lst)',
'from __main__ import solution_pepr, lst',
number=1)
print 'solution_pepr: ', t
lst = orig_lst[:] # copy of the list
t = timeit.timeit('solution_steveha(lst)',
'from __main__ import solution_steveha, lst',
number=1)
print 'solution_steveha: ', t
It prints on my console:
c:\tmp\_Python\Patrick\so10305762>python a.py
range() generated list of the length 30
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, '...']
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
random list of the length 100000
[915411, 954538, 794388, 847204, 846603, 454132, 866165, 640004, 930488, 609138,
333405, 986073, 318301, 728151, 996047, 117633, 455353, 581737, 55350, 485030,
'...']
solution_ninjagecko1: 2.41921752625
solution_jdi: 2.45477176569
solution_Patrik: 0.0468565138865
solution_Patrik2: 0.024270403082
solution_pepr_reversed: 2.43338888043
solution_pepr: 9.11879694207
So, I tried with longer list. Using only twice as long one makes a big difference (on my old computer). Patrik's dirty solution behaves very nicely. It is about 200 times faster than the reversed solutions:
random list of the length 200000
[384592, 170167, 598270, 832363, 123557, 81804, 319315, 445945, 178732, 726600,
516835, 392267, 552608, 40807, 349215, 208111, 880032, 520614, 384119, 350090,
'...']
solution_ninjagecko1: 17.362140719
solution_jdi: 17.86837545
solution_Patrik: 0.0957998851809
solution_Patrik2: 0.0500024444448
solution_pepr_reversed: 17.5078452708
solution_pepr: 52.175648581
[Added after ninjagecko's comments]
The corrected Patrik2 solution is about twice faster than 2-stage Patrick solution.
To simulate not so frequent deletion of the elements, the test like if v % 2 != 0:
was changed to if v % 100 == 0:
. Then about 1 % of items should be deleted. It is obvious that it takes less time. For 500,000 random integers in the list, the results are the following:
random list of the length 500000
[403512, 138135, 552313, 427971, 42358, 500926, 686944, 304889, 916659, 112636,
791585, 461948, 82622, 522768, 485408, 774048, 447505, 830220, 791421, 580706,
'...']
solution_ninjagecko1: 6.79284210703
solution_jdi: 6.84066913532
solution_Patrik: 0.241242951269
solution_Patrik2: 0.162481823807
solution_pepr_reversed: 6.92106007886
solution_pepr: 7.12900522273
Patrick's solution is stil about 30 times faster.
[Added 2012/04/25]
Another solution that works in-place, that loops forward, and that is as fast as Patrick's solution. It does not move all the tail when the element is deleted. Instead, it moves the wanted elements to their final position and then cuts the unused tail of the list.
def solution_pepr2(lst):
i = 0
for v in lst:
lst[i] = v # moving the element (sometimes unneccessary)
if v % 100 != 0: # simulation of the choice
i += 1 # here will be the next one
lst[i:] = [] # cutting the tail of the length of the skipped
return lst
# The following one only adds the enumerate to simulate the situation when
# it is needed -- i.e. slightly slower but with the same complexity.
def solution_pepr2enum(lst):
i = 0
for n, v in enumerate(lst):
lst[i] = v # moving the element (sometimes unneccessary)
if v % 100 != 0: # simulation of the choice
i += 1 # here will be the next one
lst[i:] = [] # cutting the tail of the length of the skipped
return lst
Compared with the above solutions for v % 100 != 0
:
random list of the length 500000
[533094, 600755, 58260, 295962, 347612, 851487, 523927, 665648, 537403, 238660,
781030, 940052, 878919, 565870, 717745, 408465, 410781, 560173, 51010, 730322,
'...']
solution_ninjagecko1: 1.38956896051
solution_jdi: 1.42314502685
solution_Patrik: 0.135545530079
solution_Patrik2: 0.0926935780151
solution_pepr_reversed: 1.43573239178
solution_steveha: 0.122824246805
solution_pepr2: 0.0938177241656
solution_pepr2enum: 0.11096263294