Подтвердить что ты не робот

Манипуляция списком в Python с pop()

Короче говоря, мне нужно удалить несколько элементов из списка в соответствии с их индексами. Однако я не могу использовать pop, потому что он сдвигает индексы (без какой-либо неуклюжей системы компенсации). Есть ли способ одновременно удалить несколько элементов?

У меня есть алгоритм, который проходит через список, и если условия правильны, этот элемент удаляется с помощью метода pop. Проблема возникает, когда все это делается в цикле. После того, как поп сделан, список сокращается на единицу, вытесняя все значения на единицу. Таким образом, цикл выходит за пределы диапазона. Можно ли одновременно удалить несколько элементов или другое решение?

Пример моей проблемы:

L = ['a', 'b', 'c', 'd']

for i in range(len(L)):
    print L
    if L[i] == 'a' or L[i] == 'c':
        L.pop(i)
4b9b3361

Ответ 1

Являются ли ваши списки большими? Если да, используйте ifilter из itertools, чтобы отфильтровать элементы, которые вам не нужны лениво (без начальной стоимости).

Списки не такие большие? Просто используйте понимание списка:

 newlist = [x for x in oldlist if x not in ['a', 'c'] ]

Это создаст новую копию списка. Обычно это не проблема эффективности, если вы действительно не заботитесь о потреблении памяти.

Как удобная среда синтаксиса и лень (= эффективность для больших списков), вы можете построить генератор, а не список, используя ( ) вместо [ ]:

interestingelts = (x for x in oldlist if x not in ['a', 'c'])

После этого вы можете перебирать interestingelts, но вы не можете индексировать его:

 for y in interestingelts:    # ok
    print y

 print interestingelts[0]     # not ok: generator allows sequential access only

Ответ 2

Вы хотите понять список:

L = [c for c in L if c not in ['a', 'c']]

Или, если вы действительно не хотите создавать копию, вернитесь назад:

for i in reversed(range(len(L))):
    if L[i] in ['a', 'c']:
        L.pop(i)    # del L[i] is more efficient

Благодаря ncoghlan для reversed() и phooji для del L[i] предложений. (Я решил оставить его как L.pop(i), так как это вопрос был изначально сформулирован.)

Кроме того, как J.S. Себастьян правильно указывает, что движение назад - это пространство, но время неэффективно; в большинстве случаев лучше всего понимать список или генератор (L = (...) вместо L = [...]).

Edit:

Хорошо, так как люди, кажется, хотят что-то менее смехотворно медленное, чем обратный метод выше (я не могу себе представить, почему...:) здесь хранится порядок, фильтр на месте, который должен отличаться по скорости от списка понимание только константой. (Это похоже на то, что я сделал бы, если бы я хотел отфильтровать строку в c.)

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:]
print L
# output: ['b', 'd']

Ответ 3

Резюме

  • использовать список списков (или genexpr) для удаления нескольких элементов из списка
  • Если ваш ввод является большой байтовой строкой, используйте str.translate() для удаления символов
  • удаление одного элемента за раз del L[i] выполняется медленно для больших списков

Если элементы байтов, как в вашем примере, вы можете использовать str.translate():

def remove_bytes(bytestr, delbytes):
    """
    >>> remove_bytes(b'abcd', b'ac') == b'bd'
    True
    """
    return bytestr.translate(None, delbytes)

В общем случае несколько элементов можно удалить с помощью нарезки:

def remove_inplace_without_order(L, delitems):
    """Remove all items from `L` that are in `delitems` (not preserving order).

    >>> L = list(range(4)); remove_inplace_without_order(L, [0,2]); L
    [3, 1]
    """
    idel = len(L) # items idel.. to be removed
    for i in reversed(range(len(L))):
        if L[i] in delitems:
            idel -= 1
            L[i] = L[idel] # save `idel`-th item
    del L[idel:] # remove items all at once
    #NOTE: the function returns `None` (it means it modifies `L` inplace)

Как @phooji и @senderle уже упомянутое понимание списка (или выражение генератора) предпочтительнее в вашем случай:

def remove_listcomp(L, delitems):
    return [x for x in L if x not in delitems]

Здесь сравнение производительности для L=list("abcd"*10**5); delitems="ac":

| function                     | time, msec |  ratio |
|------------------------------+------------+--------|
| list                         |       4.42 |    0.9 |
| remove_bytes                 |       4.88 |    1.0 |
| remove                       |       27.3 |    5.6 |
| remove_listcomp              |       36.8 |    7.5 |
| remove_inplace_without_order |       71.2 |   14.6 |
| remove_inplace_senderle2     |       83.8 |   17.2 |
| remove_inplace_senderle      |      15000 | 3073.8 |
#+TBLFM: $3=$2/@3$2;%.1f

Где

try:
    from itertools import ifilterfalse as filterfalse
except ImportError:
    from itertools import filterfalse # py3k

def remove(L, delitems):
    return filterfalse(delitems.__contains__, L)

def remove_inplace_senderle(L, delitems):
    for i in reversed(range(len(L))):
        if L[i] in delitems:
            del L[i]

def remove_inplace_senderle2(L, delitems):
    write_i = 0
    for read_i in range(len(L)):
        L[write_i] = L[read_i]
        if L[read_i] not in delitems:
             write_i += 1
    del L[write_i:]

remove_inplace_senderle() медленный из-за использования алгоритма O(N**2). Каждый del L[i] может привести к тому, что все элементы вправо будут перемещены влево, чтобы закрыть пробел.

Столбец времени в приведенной выше таблице включает время, необходимое для создания нового входного списка (первая строка) из-за того, что некоторые алгоритмы изменяют входной сигнал inplace.

Здесь тайминги для одного и того же ввода, но без создания нового списка на каждой итерации:

 | function        | time, msec | ratio |
 |-----------------+------------+-------|
 | remove_bytes    |      0.391 |     1 |
 | remove          |       24.3 |    62 |
 | remove_listcomp |       33.4 |    85 |
 #+TBLFM: $3=$2/@2$2;%d

В таблице показано, что itertools.ifilterfalse() не обеспечивает значительного улучшения по сравнению с listcomp.

В целом, это не стоит или даже вредно думать о производительности для таких задач, если профайлер не доказал, что этот код является узким местом, и это важно для вашей программы. Но было бы полезно знать альтернативные подходы, которые могли бы обеспечить более чем на порядок улучшение скорости.