Удаление нескольких элементов из списка


160

Можно ли удалить несколько элементов из списка одновременно? Если я захочу удалить элементы с индексами 0 и 2 и попробовать что-то вроде del somelist[0], а затем del somelist[2]второе утверждение фактически удалит somelist[3].

Я полагаю, что я всегда мог сначала удалить элементы с более высоким номером, но я надеюсь, что есть лучший способ.

Ответы:


110

Вы можете использовать enumerateи удалить значения, индекс которых соответствует индексам, которые вы хотите удалить:

indices = 0, 2
somelist = [i for j, i in enumerate(somelist) if j not in indices]

2
Почти, только если вы удалите весь список. это будет len (индексы) * len (somelist). Это также создает копию, которая может или не может быть желательной
Ричард Левассер

если вы проверяете значение в списке, это так. Оператор «in» работает со значениями списка, тогда как он работает с ключами dict. Если я ошибаюсь, пожалуйста, укажите мне на бодрость духа / ссылку
Ричард Левассер

5
причина, по которой я выбрал кортеж для индексов, была только простота записи. было бы идеальной работой для set () давать O (n)
SilentGhost

18
Это вовсе не удаление элементов из somelist, а создание нового списка. Если что-то содержит ссылку на исходный список, в нем все равно будут все элементы.
Том Будущий

2
@SilentGhost Не нужно делать перечисление. Как насчет этого: somelist = [ lst[i] for i in xrange(len(lst)) if i not in set(indices) ]?
ToolmakerSteve

183

По какой-то причине мне не нравится ни один из ответов здесь. Да, они работают, но, строго говоря, большинство из них не удаляют элементы в списке, не так ли? (Но сделав копию, а затем заменив оригинал отредактированной копией).

Почему бы просто не удалить сначала более высокий индекс?

Для этого есть причина? Я бы просто сделал:

for i in sorted(indices, reverse=True):
    del somelist[i]

Если вы действительно не хотите удалять элементы задом наперед, тогда, я думаю, вам следует просто деинкрементировать значения индексов, которые больше, чем последний удаленный индекс (на самом деле нельзя использовать тот же индекс, поскольку у вас другой список), или использовать копия списка (которая не будет «удалять», а заменять оригинал отредактированной копией).

Я что-то здесь упускаю, есть ли причина НЕ удалять в обратном порядке?


1
Я не знаю, почему это не было выбрано в качестве принятого ответа! Спасибо за это.
swathis

4
Есть две причины. (a) Для списка сложность по времени будет в среднем выше, чем метод «сделать копию» (с использованием набора индексов) (с учетом случайных индексов), поскольку некоторые элементы необходимо сдвигать вперед несколько раз. (б) По крайней мере для меня это трудно читать, потому что есть функция сортировки, которая не соответствует какой-либо реальной логике программы и существует исключительно по техническим причинам. Хотя к настоящему времени я уже полностью понимаю логику, я все еще чувствую, что это будет трудно читать.
нетленная ночь

1
@ ImperishableNight, не могли бы вы уточнить (а)? Я не понимаю, "некоторые элементы должны быть сдвинуты". Для (б) вы можете просто определить функцию, если вам нужна ясность чтения.
tglaria

109

Если вы удаляете несколько несмежных элементов, то то, что вы описываете, является наилучшим способом (и да, обязательно начните с самого высокого индекса).

Если ваши элементы находятся рядом, вы можете использовать синтаксис назначения слайса:

a[2:10] = []

95
Вы также можете сказать del a[2:10]с тем же эффектом.
STH

8
@sth Интересно, что del немного быстрее, чем присваивание.
thefourtheye

24

Вы можете использовать numpy.deleteследующим образом:

import numpy as np
a = ['a', 'l', 3.14, 42, 'u']
I = [0, 2]
np.delete(a, I).tolist()
# Returns: ['l', '42', 'u']

Если вы не против получить numpyмассив в конце, вы можете опустить .tolist(). Вы также должны увидеть довольно значительные улучшения скорости, что делает это решение более масштабируемым. Я не тестировал его, но numpyоперации - это скомпилированный код, написанный на C или Fortran.


1
общее решение, когда элементы не являются последовательными +1
noɥʇʎԀʎzɐɹƆ

1
Здесь вопрос, как насчет удаления ['a', 42].
evanhutomo

Огромные бонусные баллы за это решение, по сравнению с другими, за скорость. Что я могу сказать, так это то, что для очень большого набора данных мне потребовалось несколько минут, чтобы достичь чего-то, что заняло всего несколько секунд с хорошей репутацией.
Легель

18

Как специализация ответа Грега, вы даже можете использовать расширенный синтаксис срезов. например. Если вы хотите удалить пункты 0 и 2:

>>> a= [0, 1, 2, 3, 4]
>>> del a[0:3:2]
>>> a
[1, 3, 4]

Конечно, это не распространяется на произвольный выбор, но он может определенно работать для удаления любых двух элементов.


16

Как функция:

def multi_delete(list_, *args):
    indexes = sorted(list(args), reverse=True)
    for index in indexes:
        del list_[index]
    return list_

Работает за время n log (n) , что должно сделать его самым быстрым и правильным решением.


1
Версия с args.sort (). Reverse () определенно лучше. Также бывает работать с диктофонами вместо того, чтобы бросать или, что еще хуже, беззвучно портить.

sort () не определен для кортежа, вам нужно сначала преобразовать в список. sort () возвращает None, поэтому вы не можете использовать reverse () для него.
SilentGhost

@ Р. Пэйт: я удалил первую версию по этой причине. Спасибо. @ SilentGhost: исправлено.
Nikhil Chelliah

@Nikhil: нет, вы не сделали;) args = list (args) args.sort () args.reverse (), но лучшим вариантом будет: args = sorted (args, reverse = True)
SilentGhost

2
n log n? В самом деле? Я не думаю, что del list[index]O (1).
user202729

12

Итак, вы по сути хотите удалить несколько элементов за один проход? В этом случае позиция следующего удаляемого элемента будет смещена на сколько ранее было удалено.

Наша цель - удалить все гласные, которые предварительно рассчитаны как индексы 1, 4 и 7. Обратите внимание, что важно, чтобы индексы to_delete находились в порядке возрастания, иначе он не будет работать.

to_delete = [1, 4, 7]
target = list("hello world")
for offset, index in enumerate(to_delete):
  index -= offset
  del target[index]

Было бы сложнее, если бы вы хотели удалить элементы в любом порядке. ИМО, сортировка to_deleteможет быть проще, чем выяснить, когда вы должны или не должны вычитать из index.


8

Я абсолютный новичок в Python, и мое программирование на данный момент грубое и грязное, если не сказать больше, но мое решение заключалось в использовании комбинации основных команд, которые я выучил в ранних уроках:

some_list = [1,2,3,4,5,6,7,8,10]
rem = [0,5,7]

for i in rem:
    some_list[i] = '!' # mark for deletion

for i in range(0, some_list.count('!')):
    some_list.remove('!') # remove
print some_list

Очевидно, что из-за необходимости выбора символа «метка для удаления» это имеет свои ограничения.

Что касается производительности по мере масштабирования списка, я уверен, что мое решение не оптимально. Тем не менее, это просто, что, я надеюсь, понравится другим новичкам, и будет работать в простых случаях, когда some_listимеет широко известный формат, например, всегда числовой ...


2
Вместо того, чтобы использовать '!' в качестве вашего специального персонажа используйте None. Это сохраняет действительность каждого персонажа и освобождает ваши возможности
portforwardpodcast

5

Вот альтернатива, которая не использует enumerate () для создания кортежей (как в первоначальном ответе SilentGhost).

Это кажется более читабельным для меня. (Возможно, я бы почувствовал себя по-другому, если бы использовал привычку перечисления.) ПРЕДУПРЕЖДЕНИЕ. Я не проверял производительность двух подходов.

# Returns a new list. "lst" is not modified.
def delete_by_indices(lst, indices):
    indices_as_set = set(indices)
    return [ lst[i] for i in xrange(len(lst)) if i not in indices_as_set ]

ПРИМЕЧАНИЕ. Синтаксис Python 2.7. Для Python 3 xrange=> range.

Использование:

lst = [ 11*x for x in xrange(10) ]
somelist = delete_by_indices( lst, [0, 4, 5])

somelist:

[11, 22, 33, 66, 77, 88, 99]

--- БОНУС ---

Удалить несколько значений из списка. То есть у нас есть значения, которые мы хотим удалить:

# Returns a new list. "lst" is not modified.
def delete__by_values(lst, values):
    values_as_set = set(values)
    return [ x for x in lst if x not in values_as_set ]

Использование:

somelist = delete__by_values( lst, [0, 44, 55] )

somelist:

[11, 22, 33, 66, 77, 88, 99]

Это тот же ответ, что и раньше, но на этот раз мы предоставили ЗНАЧЕНИЯ для удаления [0, 44, 55].


Я решил, что @ SilentGhost's было трудно читать только из-за неописательных имен переменных, используемых для результата перечисления. Кроме того, паренсы облегчили бы чтение. Так вот как я бы словом своего решения (с «набором» добавлен, для выполнения): [ value for (i, value) in enumerate(lst) if i not in set(indices) ]. Но я оставлю здесь свой ответ, потому что я также показываю, как удалять по значениям. Что проще, но может кому-то помочь.
ToolmakerSteve

@ Veedrac- спасибо; Я переписал, чтобы построить набор первым. Как вы думаете, более быстрое решение, чем SilentGhost? (Я не считаю достаточно важным , чтобы на самом деле время, просто спрашивая свое мнение.) Кроме того , я хотел бы переписать версию SilentGhost как indices_as_set = set(indices), [ value for (i, value) in enumerate(lst) if i not in indices_as_set ], ускорить его.
ToolmakerSteve

Есть ли причина стиля для двойного подчеркивания delete__by_values()?
Том

5

Альтернативный метод понимания списка, который использует значения индекса списка:

stuff = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
index = [0, 3, 6]
new = [i for i in stuff if stuff.index(i) not in index]

Это возвращает:

['b', 'c', 'e', 'f']

хороший ответ, но название списка индексов indexвводит в заблуждение, поскольку в итераторе списка используется методindex()
Joe

4

Вот еще один метод, который удаляет элементы на месте. Кроме того, если ваш список очень длинный, это быстрее.

>>> a = range(10)
>>> remove = [0,4,5]
>>> from collections import deque
>>> deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)

>>> timeit.timeit('[i for j, i in enumerate(a) if j not in remove]', setup='import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.1704120635986328

>>> timeit.timeit('deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)', setup='from collections import deque;import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.004853963851928711

+1: Интересное использование deque для выполнения действия for как части выражения, а не блока «for ..:». Тем не менее, для этого простого случая я считаю блок Nikhil более читабельным.
ToolmakerSteve

4

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

По O(n)решению будет:

indices = {0, 2}
somelist = [i for j, i in enumerate(somelist) if j not in indices]

Это действительно близко к версии SilentGhost , но добавляет две скобки.


Это не так, O(n)если вы считаете количество поисков, которые берутся log(len(indices))для каждой итерации.
Безумный физик

@MadPhysicist j not in indicesесть O(1).
Veedrac

Я не уверен, как вы получите этот номер. Поскольку индексы являются множеством, j not in indicesвсе еще требуется поиск, который есть O(log(len(indices))). Хотя я согласен, что поиск в 2-элементном наборе квалифицируется как O(1), в общем случае это будет O(log(N)). В любом случае O(N log(N))все еще бьет O(N^2).
Безумный физик


И что именно сделали две скобы?
Nuclear03020704

4
l = ['a','b','a','c','a','d']
to_remove = [1, 3]
[l[i] for i in range(0, len(l)) if i not in to_remove])

По сути, это то же самое, что и ответ с наибольшим количеством голосов, просто другой способ его написания. Обратите внимание, что использование l.index () не очень хорошая идея, потому что он не может обрабатывать дублированные элементы в списке.


2

Метод удаления вызовет большое смещение элементов списка. Я думаю, что лучше сделать копию:

...
new_list = []
for el in obj.my_list:
   if condition_is_true(el):
      new_list.append(el)
del obj.my_list
obj.my_list = new_list
...

2

технически, ответ НЕТ, невозможно удалить два объекта ОДНОВРЕМЕННО. Тем не менее, возможно удалить два объекта в одной строке красивого питона.

del (foo['bar'],foo['baz'])

восстановим foo['bar'], потомfoo['baz']


Это удаляет объект dict, а не список, но я все еще +1, потому что это чертовски красиво!
Ульф Аслак

Это относится и к списку, с соответствующим синтаксисом. Однако утверждение состоит в том, что невозможно удалить два объекта одновременно - ложь; см. ответ @bobince
Педро

2

мы можем сделать это с помощью цикла for, перебирающего индексы после сортировки списка индексов в порядке убывания

mylist=[66.25, 333, 1, 4, 6, 7, 8, 56, 8769, 65]
indexes = 4,6
indexes = sorted(indexes, reverse=True)
for i in index:
    mylist.pop(i)
print mylist

2

Для индексов 0 и 2 из списка А:

for x in (2,0): listA.pop(x)

Для удаления некоторых случайных индексов из списка A:

indices=(5,3,2,7,0) 
for x in sorted(indices)[::-1]: listA.pop(x)

2

Я хотел сравнить различные решения, которые позволили легко поворачивать ручки.

Сначала я сгенерировал свои данные:

import random

N = 16 * 1024
x = range(N)
random.shuffle(x)
y = random.sample(range(N), N / 10)

Затем я определил свои функции:

def list_set(value_list, index_list):
    index_list = set(index_list)
    result = [value for index, value in enumerate(value_list) if index not in index_list]
    return result

def list_del(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        del(value_list[index])

def list_pop(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        value_list.pop(index)

Тогда я использовал timeitдля сравнения решений:

import timeit
from collections import OrderedDict

M = 1000
setup = 'from __main__ import x, y, list_set, list_del, list_pop'
statement_dict = OrderedDict([
    ('overhead',  'a = x[:]'),
    ('set', 'a = x[:]; list_set(a, y)'),
    ('del', 'a = x[:]; list_del(a, y)'),
    ('pop', 'a = x[:]; list_pop(a, y)'),
])

overhead = None
result_dict = OrderedDict()
for name, statement in statement_dict.iteritems():
    result = timeit.timeit(statement, number=M, setup=setup)
    if overhead is None:
        overhead = result
    else:
        result = result - overhead
        result_dict[name] = result

for name, result in result_dict.iteritems():
    print "%s = %7.3f" % (name, result)

Вывод

set =   1.711
del =   3.450
pop =   3.618

Таким образом, генератор с индексами в setбыл победителем. И delнемного быстрее, чем тогда pop.


Спасибо за это сравнение, это привело меня к проведению моих собственных тестов (фактически только заимствованных из вашего кода) и к небольшому количеству элементов, которые нужно удалить, накладные расходы на создание SET делают его худшим решением (используйте 10, 100, 500 для длина «у», и вы увидите). Как и в большинстве случаев, это зависит от приложения.
tglaria

2

Вы можете использовать эту логику:

my_list = ['word','yes','no','nice']

c=[b for i,b in enumerate(my_list) if not i in (0,2,3)]

print c

2

Еще одна реализация идеи удаления из высшего индекса.

for i in range(len(yourlist)-1, -1, -1):
    del yourlist(i)

1

Я действительно могу придумать два способа сделать это:

  1. нарезать список как (это удаляет 1-й, 3-й и 8-й элементы)

    somelist = somelist [1: 2] + somelist [3: 7] + somelist [8:]

  2. сделать это на месте, но по одному:

    somelist.pop (2) somelist.pop (0)


1

Вы можете сделать это по указанию, а не по списку. В списке элементы расположены в последовательности. В диктовке они зависят только от индекса.

Простой код, чтобы объяснить это , выполнив :

>>> lst = ['a','b','c']
>>> dct = {0: 'a', 1: 'b', 2:'c'}
>>> lst[0]
'a'
>>> dct[0]
'a'
>>> del lst[0]
>>> del dct[0]
>>> lst[0]
'b'
>>> dct[0]
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    dct[0]
KeyError: 0
>>> dct[1]
'b'
>>> lst[1]
'c'

Способ "преобразовать" список в dict:

>>> dct = {}
>>> for i in xrange(0,len(lst)): dct[i] = lst[i]

Обратное:

lst = [dct[i] for i in sorted(dct.keys())] 

В любом случае, я думаю, что лучше начинать удаление с более высокого индекса, как вы сказали.


Python гарантирует, что [dct [i] для i в dct] всегда будет использовать увеличивающиеся значения i? Если это так, список (dct.values ​​()), безусловно, лучше.

Я не думал об этом. Ты прав. Пока я читаю [здесь] [1], нет гарантии, что предметы будут выбраны в порядке или, по крайней мере, в ожидаемом порядке. Я отредактировал. [1]: docs.python.org/library/stdtypes.html#dict.items
Андреа Амбу

2
Этот ответ говорит о словарях в корне неверно. В словаре есть КЛЮЧИ (не ИНДЕКСЫ). Да, пары ключ / значение не зависят друг от друга. Нет, не имеет значения, в каком порядке вы удаляете записи. Преобразование в словарь только для удаления некоторых элементов из списка было бы излишним.
ToolmakerSteve

1

Обобщить комментарий от @sth . Удаление элемента в любом классе, который реализует abc.MutableSequence , и listв частности, выполняется с помощью __delitem__магического метода. Этот метод работает аналогично __getitem__, то есть он может принимать целое число или фрагмент. Вот пример:

class MyList(list):
    def __delitem__(self, item):
        if isinstance(item, slice):
            for i in range(*item.indices(len(self))):
                self[i] = 'null'
        else:
            self[item] = 'null'


l = MyList(range(10))
print(l)
del l[5:8]
print(l)

Это будет выводить

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 'null', 'null', 'null', 8, 9]

1

Импортировать его только по этой причине может быть излишним, но если вы pandasвсе равно используете его , то решение простое и понятное:

import pandas as pd
stuff = pd.Series(['a','b','a','c','a','d'])
less_stuff = stuff[stuff != 'a']  # define any condition here
# results ['b','c','d']

1
some_list.remove(some_list[max(i, j)])

Позволяет избежать стоимости сортировки и необходимости явно копировать список.


0

Как насчет одного из них (я очень плохо знаком с Python, но они кажутся нормальными):

ocean_basin = ['a', 'Atlantic', 'Pacific', 'Indian', 'a', 'a', 'a']
for i in range(1, (ocean_basin.count('a') + 1)):
    ocean_basin.remove('a')
print(ocean_basin)

[«Атлантика», «Тихоокеанский», «Индийский»]

ob = ['a', 'b', 4, 5,'Atlantic', 'Pacific', 'Indian', 'a', 'a', 4, 'a']
remove = ('a', 'b', 4, 5)
ob = [i for i in ob if i not in (remove)]
print(ob)

[«Атлантика», «Тихоокеанский», «Индийский»]


0

Ни один из предложенных ответов пока не выполняет удаление в месте O (n) по длине списка для произвольного числа индексов, которые нужно удалить, поэтому вот моя версия:

def multi_delete(the_list, indices):
    assert type(indices) in {set, frozenset}, "indices must be a set or frozenset"
    offset = 0
    for i in range(len(the_list)):
        if i in indices:
            offset += 1
        elif offset:
            the_list[i - offset] = the_list[i]
    if offset:
        del the_list[-offset:]

# Example:
a = [0, 1, 2, 3, 4, 5, 6, 7]
multi_delete(a, {1, 2, 4, 6, 7})
print(a)  # prints [0, 3, 5]

0

Вы также можете использовать удалить.

delete_from_somelist = []
for i in [int(0), int(2)]:
     delete_from_somelist.append(somelist[i])
for j in delete_from_somelist:
     newlist = somelist.remove(j)

0

Я собрал все это вместе в list_diffфункцию, которая просто принимает два списка в качестве входных данных и возвращает их различие, сохраняя при этом первоначальный порядок первого списка.

def list_diff(list_a, list_b, verbose=False):

    # returns a difference of list_a and list_b,
    # preserving the original order, unlike set-based solutions

    # get indices of elements to be excluded from list_a
    excl_ind = [i for i, x in enumerate(list_a) if x in list_b]
    if verbose:
        print(excl_ind)

    # filter out the excluded indices, producing a new list 
    new_list = [i for i in list_a if list_a.index(i) not in excl_ind]
    if verbose:
        print(new_list)

    return(new_list)

Пример использования:

my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
# index = [0, 3, 6]

# define excluded names list
excl_names_list = ['woof', 'c']

list_diff(my_list, excl_names_list)
>> ['a', 'b', 'd', 'e', 'f']
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.