从列表中删除多个元素

206

有没有可能同时从列表中删除多个元素?如果我想删除索引为0和2的元素,并尝试类似于del somelist [0]然后跟着del somelist [2],第二条语句实际上会删除somelist[3]

我猜我可以总是先删除较高编号的元素,但我希望有更好的方法。


如果你关心效率,可以使用多个切片。 - tejasvi88
32个回答

2
您可以简单地使用np.delete:
list_indices = [0, 2]
original_list = [0, 1, 2, 3]
new_list = np.delete(original_list, list_indices)

输出

array([1, 3])

这里,第一个参数是原始列表,第二个参数是您要删除的索引或索引列表。

如果有ndarrays,可以使用第三个参数:axis(对于ndarrays,0表示行,1表示列)。


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 索引的生成器是获胜者。而且,delpop 稍微快一点。


谢谢你提供这个比较,它促使我进行了自己的测试(实际上只是借用了你的代码)。对于需要移除的少量项目,创建SET所需的开销使其成为最糟糕的解决方案(使用长度为10、100、500的“y”进行测试即可看出)。像大多数情况一样,这取决于应用程序。 - tglaria

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

避免排序成本和显式复制列表。

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

我可以想到两种方法来实现:

  1. 像这样切片列表(这将删除第1、3和8个元素)

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

  2. 逐个在原地进行操作:

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


1

我使用perfplot测试了建议的解决方案,并发现NumPy的性能最佳。

np.delete(lst, remove_ids)

如果列表长度超过100条,使用set()是最快的解决方案。在此之前,所有解决方案的时间都在10^-5秒左右。那么列表推导式看起来足够简单:

out = [item for i, item in enumerate(lst) if i not in remove_ids]

在这里输入图片描述


用于重现图形的代码:

import perfplot
import random
import numpy as np
import copy


def setup(n):
    lst = list(range(n))
    random.shuffle(lst)
    # //10 = 10%
    remove_ids = random.sample(range(n), n // 10)
    return lst, remove_ids


def if_comprehension(lst, remove_ids):
    return [item for i, item in enumerate(lst) if i not in remove_ids]


def del_list_inplace(lst, remove_ids):
    out = copy.deepcopy(lst)
    for i in sorted(remove_ids, reverse=True):
        del out[i]
    return out


def del_list_numpy(lst, remove_ids):
    return np.delete(lst, remove_ids)


b = perfplot.bench(
    setup=setup,
    kernels=[if_comprehension, del_list_numpy, del_list_inplace],
    n_range=[2**k for k in range(20)],
)
b.save("out.png")
b.show()

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] for i in dct]始终使用递增的i值?如果是这样,那么list(dct.values())肯定更好。 - Roger Pate
我没有考虑到那个问题。你是对的。根据我在这里阅读的内容,不能保证按顺序选择项目,或者至少不是预期的顺序。我进行了编辑。 - Andrea Ambu
2
这个答案从根本上错误地谈到了字典。字典有键(不是索引)。是的,键/值对彼此独立。不,删除条目的顺序并不重要。仅为了从列表中删除一些元素而转换为字典将是过度的。 - ToolmakerSteve

1

到目前为止提供的答案都无法在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
使用 numpy.delete,它肯定比 Python 列表更快 (后面显示 376 倍)。
第一种方法 (使用 numpy):
import numpy as np

arr = np.array([0,3,5,7])
# [0,3,5,7]
indexes = [0,3]
np.delete(arr, indexes)
# [3,5]

第二种方法(使用Python列表):

arr = [0,3,5,7]
# [0,3,5,7]
indexes = [0,3]
for index in sorted(indexes, reverse=True):
    del arr[index]
arr
# [3,5]

编写代码对一个包含500000个元素的数组进行基准测试,随机删除其中一半元素:

import numpy as np
import random
import time

start = 0
stop = 500000
elements = np.arange(start,stop)
num_elements = len(temp)

temp = np.copy(elements)
temp2 = elements.tolist()

indexes = random.sample(range(0, num_elements), int(num_elements/2))

start_time = time.time()

temp = np.delete(temp, indexes)

end_time = time.time()
total_time = end_time - start_time
print("First method: ", total_time)

start_time = time.time()

for index in sorted(indexes, reverse=True):
    del temp2[index]

end_time = time.time()
total_time = end_time - start_time
print("Second method: ", total_time)

# First method:  0.04500985145568848
# Second method:  16.94180393218994

第一种方法比第二种方法快约376倍。


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)

['大西洋', '太平洋', '印度洋']


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接