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

Это продолжение аналогичного question, в котором прозвучал лучший способ написать

for item in somelist:
    if determine(item):
         code_to_remove_item

и, похоже, консенсус был о чем-то вроде

somelist[:] = [x for x in somelist if not determine(x)]

Однако, я думаю, что если вы удаляете только несколько элементов, большинство элементов копируются в один и тот же объект, и, возможно, это медленно. В ответе к другому связанному question кто-то предлагает:

for item in reversed(somelist):
    if determine(item):
        somelist.remove(item)

Однако здесь list.remove будет искать элемент, который является O (N) в длине списка. Может быть, мы ограничены тем, что список представлен как массив, а не связанный список, поэтому для удаления элементов необходимо будет переместить все после него. Тем не менее, предлагается здесь, что collection.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

Ответ 1

Понимание списка - это асимптотически оптимальное решение:

somelist = [x for x in somelist if not determine(x)]

Это делает только один проход по списку, поэтому работает в O (n) времени. Так как вам нужно вызвать define() для каждого объекта, любой алгоритм потребует не менее O (n) операций. Понимание списка должно выполнять некоторое копирование, но оно копирует только ссылки на объекты, которые не копируют сами объекты.

Удаление элементов из списка в Python - это O (n), поэтому все, что должно быть с удалением, pop или del внутри цикла, будет O (n ** 2).

Кроме того, в списке списков CPython быстрее, чем для циклов.

Ответ 2

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

Ответ 3

Так как list.remove эквивалентно del list[list.index(x)], вы можете сделать:

for idx, item in enumerate(somelist):
    if determine(item):
        del somelist[idx]

Но: вы не должны изменять список во время итерации по нему. Это вас укусит рано или поздно. Сначала используйте filter или ознакомьтесь со списком и оптимизируйте его позже.

Ответ 4

Deque оптимизирован для удаления головы и хвоста, а не для произвольного удаления в середине. Само удаление происходит быстро, но вам все равно придется перемещать список в точку удаления. Если вы выполняете итерацию по всей длине, то единственная разница между фильтрацией 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:]

Ответ 5

Я принял удар. Мое решение медленнее, но требует меньшего объема памяти (т.е. Не создает новый массив). В некоторых случаях это может быть даже быстрее!

Этот код был отредактирован с момента его первого размещения

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