Видалення декількох елементів зі списку


160

Чи можливо видалити декілька елементів зі списку одночасно? Якщо я хочу видалити елементи в індексах 0 і 2 і спробувати щось на кшталт цього del somelist[0], після чого del somelist[2]другий вислів буде фактично видалений somelist[3].

Я припускаю, що я завжди міг би спочатку видалити більш пронумеровані елементи, але сподіваюся, що є кращий спосіб.

Відповіді:


110

Напевно, не найкраще рішення цієї проблеми:

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

2
Майже, лише якщо ви видалите весь список. це буде len (індекси) * len (сомеліст). Він також створює копію, яка може бути, а може і не бажати
Річард Левассер

якщо ви перевіряєте значення у списку, воно є. оператор 'in' працює над значеннями списку, тоді як він працює на клавішах диктату. Якщо я помиляюсь, будь ласка, вкажіть мені на перегляд / довідку
Річард Левассер

5
Причиною того, що я вибрав кортеж для індексів, була лише простота запису. це буде ідеальною роботою для set () надання O (n)
SilentGhost

18
Це зовсім не видалення елементів із солістичного списку, а скоріше створення абсолютно нового списку. Якщо щось містить посилання на оригінальний список, воно все одно буде містити всі елементи в ньому.
Том Майбутнє

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) Для списку складність у часі була б вищою, ніж метод "зробити копію" (використовуючи набір індексів) в середньому (припускаючи випадкові індекси), оскільки деякі елементи потрібно зміщувати вперед кілька разів. (b) Принаймні для мене це важко читати, оскільки існує функція сортування, яка не відповідає жодній фактичній логіці програми, і існує виключно з технічних причин. Хоча до цього часу я вже досконало розумію логіку, я все ще відчуваю, що її важко буде читати.
Непрохідна ніч

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

109

Якщо ви видаляєте кілька суміжних елементів, то те, що ви описуєте, є найкращим способом (і так, не забудьте почати з найвищого індексу).

Якщо ваші предмети суміжні, ви можете використовувати синтаксис присвоєння фрагментів:

a[2:10] = []

95
Ви також можете сказати del a[2:10]з тим же ефектом.
sth

8
@sth Цікаво, що дель трохи швидше, ніж призначення.
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

ВЕЛИЧЕЗНІ бонусні бали за це рішення, порівняно з іншими, за швидкість. Що я можу сказати, це те, що для дуже великого набору даних мені знадобилося кілька хвилин, щоб досягти чогось, на що знадобилося всього кілька секунд з хорошим онімінням.
legel

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 журналу (n) часу, що повинно зробити це найбільш швидким правильним рішенням.


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

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

@ R. Pate: Першу версію я видалив з цієї причини. Дякую. @ SilentGhost: виправлено це.
Nikhil Chelliah

@Nikhil: ні, ти не був;) args = список (args) args.sort () args.reverse (), але кращим варіантом буде: args = sorted (args, reverse = True)
SilentGhost

2
n log n? Дійсно? Я не думаю, що del list[index]це O (1).
користувач202729

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).

Мені це здається більш зрозумілим. (Можливо, я відчував би інакше, якби я мав звичку використовувати перерахування.) CAVEAT: Я не перевіряв виконання двох підходів.

# 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])

соліст:

[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] )

соліст:

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

Це та сама відповідь, що і раніше, але цього разу ми надали ЦІННОСТІ, які потрібно видалити [0, 44, 55].


Я вирішив, що @ SilentGhost важко читати лише через не описові назви змінних, що використовуються для результату перерахування. Також парен полегшив би читання. Так ось як я б словом свого рішення (з «набором» доданий, для виконання): [ 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()
Джо

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 для виконання акта як частини виразу, а не вимагає блоку "для ..:". Однак для цього простого випадку я вважаю, що для блоку Нікіла більше читається.
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).
Ведрак

Я не впевнений, як ви отримаєте це число. Оскільки індекси - це безліч, j not in indicesвсе-таки потрібен пошук, який є O(log(len(indices))). Хоча я згоден, що пошук у наборі з двома елементами кваліфікується як 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'])

буде recurively видалити foo['bar'], тоfoo['baz']


Це видаляє з об'єкта dict, а не з списку, але я все ще стаю +1, тому що це гарно!
Ульф Аслак

Це стосується і списку, з відповідним синтаксисом. Однак твердження полягає в тому, що неможливо одночасно видалити два об’єкти, помилково; дивіться відповідь @bobince
Педро Гімено

2

ми можемо це зробити за допомогою ітерації циклу над індексами після сортування списку індексів у порядку зменшення

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 зі спискуA:

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

Тож генератор з індексами в a setстав переможцем. І delтрохи швидше pop.


Дякую за це порівняння, це змусило мене зробити власні тести (фактично щойно запозичив ваш код), а для того, щоб видалити невелику кількість елементів, накладні витрати на створення SET роблять це найгіршим рішенням (використовуйте 10, 100, 500 для довжина 'y' і побачите). Як і в більшості випадків, це залежить від програми.
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-й елементи)

    сомеліст = сомеліст [1: 2] + сомеліст [3: 7] + соліст [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'

Спосіб "перетворення" списку в диктат:

>>> 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 in dct], завжди використовувати зростаючі значення i? Якщо так, список (dct.values ​​()), безумовно, кращий.

Я про це не думав. Ти маєш рацію. Коли я читаю [тут] [1], немає гарантії, що товари будуть підібрані по порядку, або принаймні очікуване замовлення. Я редагував. [1]: docs.python.org/library/stdtypes.html#dict.items
Андреа Амбу

2
Ця відповідь говорить про словники принципово неправильно. Словник має KEYS (не INDICES). Так, пари ключ / значення не залежать одна від одної. Ні, не має значення, в якому порядку ви видаляєте записи. Перехід до словника просто для видалення деяких елементів зі списку буде надмірним.
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.