Быстрый способ удалить несколько элементов из списка / очереди
это продолжение аналогичного вопрос который спросил лучший способ написать
for item in somelist:
if determine(item):
code_to_remove_item
и похоже, что консенсус был на что-то вроде
somelist[:] = [x for x in somelist if not determine(x)]
однако, я думаю, что если вы удаляете только несколько элементов, большинство элементов копируются в тот же объект, и, возможно, это медленно. В ответ другому вопрос, кто-то предлагает:
for item in reversed(somelist):
if determine(item):
somelist.remove(item)
здесь list.remove
будет найдите элемент, который равен O (N) по длине списка. Возможно, мы ограничены тем, что список представлен как массив, а не связанный список, поэтому для удаления элементов нужно будет переместить все после него. Однако, он предложил здесь что коллекции.dequeue представлен в виде двусвязного списка. Затем можно удалить в O (1) во время итерации. Как бы мы этого добились?
обновление: Я сделал некоторое время тестирования, как ну, со следующим кодом:
import timeit
setup = """
import random
random.seed(1)
b = [(random.random(),random.random()) for i in xrange(1000)]
c = []
def tokeep(x):
return (x[1]>.45) and (x[1]<.5)
"""
listcomp = """
c[:] = [x for x in b if tokeep(x)]
"""
filt = """
c = filter(tokeep, b)
"""
print "list comp = ", timeit.timeit(listcomp,setup, number = 10000)
print "filtering = ", timeit.timeit(filt,setup, number = 10000)
и у:
list comp = 4.01255393028
filtering = 3.59962391853
5 ответов
понимание списка является асимптотически оптимальным решением:
somelist = [x for x in somelist if not determine(x)]
Он делает только один проход над списком, поэтому работает в O (n) времени. Поскольку вам нужно вызвать determine () для каждого объекта, любой алгоритм потребует не менее O (n) операций. Понимание списка должно делать некоторое копирование, но это только копирование ссылок на объекты, не копирующие сами объекты.
удаление элементов из списка в Python-это O (n), поэтому все с удалением, pop, или del внутри цикла будет O (n**2).
кроме того, в CPython понимания списка быстрее, чем для циклов.
С list.remove
эквивалентно del list[list.index(x)]
, вы могли бы сделать:
for idx, item in enumerate(somelist):
if determine(item):
del somelist[idx]
но: вы должны не измените список, повторяя его. Рано или поздно он тебя укусит. Использовать filter
или сначала перечислите понимание и оптимизируйте позже.
деке оптимизировано для удаления головы и кабеля, не для произвольного удаления в середине. Само удаление выполняется быстро, но вам все равно придется пройти по списку до точки удаления. Если вы повторяете всю длину, то единственное различие между фильтрацией deque и фильтрацией списка (используя filter
или понимание) - это накладные расходы на копирование, которое в худшем случае является постоянным кратным; это все еще операция O(n). Кроме того, обратите внимание, что объекты в списке не являются скопированы только ссылки на них. Так что накладных расходов не так уж много.
возможно, вы могли бы избежать копирования таким образом, но у меня нет особых причин полагать, что это быстрее, чем простое понимание списка-это, вероятно, не:
write_i = 0
for read_i in range(len(L)):
L[write_i] = L[read_i]
if L[read_i] not in ['a', 'c']:
write_i += 1
del L[write_i:]
Я сделал удар на этом. Мое решение медленнее, но требует меньше накладных расходов памяти (т. е. не создает новый массив). В некоторых обстоятельствах это может быть даже быстрее!
этот код был отредактирован с момента его первой публикации
у меня были проблемы с timeit, я мог бы сделать это неправильно.
import timeit
setup = """
import random
random.seed(1)
global b
setup_b = [(random.random(), random.random()) for i in xrange(1000)]
c = []
def tokeep(x):
return (x[1]>.45) and (x[1]<.5)
# define and call to turn into psyco bytecode (if using psyco)
b = setup_b[:]
def listcomp():
c[:] = [x for x in b if tokeep(x)]
listcomp()
b = setup_b[:]
def filt():
c = filter(tokeep, b)
filt()
b = setup_b[:]
def forfilt():
marked = (i for i, x in enumerate(b) if tokeep(x))
shift = 0
for n in marked:
del b[n - shift]
shift += 1
forfilt()
b = setup_b[:]
def forfiltCheating():
marked = (i for i, x in enumerate(b) if (x[1] > .45) and (x[1] < .5))
shift = 0
for n in marked:
del b[n - shift]
shift += 1
forfiltCheating()
"""
listcomp = """
b = setup_b[:]
listcomp()
"""
filt = """
b = setup_b[:]
filt()
"""
forfilt = """
b = setup_b[:]
forfilt()
"""
forfiltCheating = '''
b = setup_b[:]
forfiltCheating()
'''
psycosetup = '''
import psyco
psyco.full()
'''
print "list comp = ", timeit.timeit(listcomp, setup, number = 10000)
print "filtering = ", timeit.timeit(filt, setup, number = 10000)
print 'forfilter = ', timeit.timeit(forfilt, setup, number = 10000)
print 'forfiltCheating = ', timeit.timeit(forfiltCheating, setup, number = 10000)
print '\nnow with psyco \n'
print "list comp = ", timeit.timeit(listcomp, psycosetup + setup, number = 10000)
print "filtering = ", timeit.timeit(filt, psycosetup + setup, number = 10000)
print 'forfilter = ', timeit.timeit(forfilt, psycosetup + setup, number = 10000)
print 'forfiltCheating = ', timeit.timeit(forfiltCheating, psycosetup + setup, number = 10000)
и вот результаты
list comp = 6.56407690048
filtering = 5.64738512039
forfilter = 7.31555104256
forfiltCheating = 4.8994679451
now with psyco
list comp = 8.0485959053
filtering = 7.79016900063
forfilter = 9.00477004051
forfiltCheating = 4.90830993652
Я, должно быть, что-то не так с psyco, потому что он на самом деле работает замедлившийся.