Быстрый способ удалить несколько элементов из списка / очереди

это продолжение аналогичного вопрос который спросил лучший способ написать

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 понимания списка быстрее, чем для циклов.


Если вам нужно удалить элемент в O (1), Вы можете использовать HashMaps


С 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, потому что он на самом деле работает замедлившийся.