如何同时从列表中删除多个索引?

216

假设我有这个列表:

list = [a, b, c, d, e, f, g]

如何同时删除索引为2, 3, 45的元素?

pop()方法不支持同时删除多个值,我该如何实现?

8个回答

402
你需要在循环中执行此操作,没有内置的一次删除多个索引的操作。
你的示例实际上是一个连续的索引序列,所以可以这样做:
del my_list[2:6]

这将删除从2开始并在6之前结束的切片。

从您的问题中无法确定您是否需要删除任意集合的索引,还是它将始终是一系列连续的索引。

如果您有任意集合的索引,则:

indexes = [2, 3, 5]
for index in sorted(indexes, reverse=True):
    del my_list[index]

请注意,您需要以相反的顺序删除它们,这样就不会影响后续的索引。


11
如果我们使用my_list.pop(index)代替del my_list[index],会有什么不同?这对效率或语义有影响吗? - Levon
2
@kitensei 它确实重新索引,但是因为首先按相反的顺序对索引进行排序,所以我们从列表的右侧删除,因此我们在删除元素之前从未重新索引过它。 - Andrew Christianson
93
倒序遍历索引。真是太聪明了!我一直在想象着一些荒谬的情况,要一边遍历一边递减索引,而这种方法却更加优雅! - Neil
尽管在反向删除顺序方面值得称赞,但我认为这可能会导致负索引的问题。 - Lars
3
我认为主要区别在于 pop返回被删除的值。由于我们不需要删除的值,所以在这种情况下应该使用del,虽然两种方法都可以。 - Pro Q
显示剩余3条评论

80
remove_indices = [1,2,3]
somelist = [i for j, i in enumerate(somelist) if j not in remove_indices]

示例:

In [9]: remove_indices = [1,2,3]

In [10]: somelist = range(10)

In [11]: somelist = [i for j, i in enumerate(somelist) if j not in remove_indices]

In [12]: somelist
Out[12]: [0, 4, 5, 6, 7, 8, 9]

6
如下@C.Yduqoli所指出的,这是最有效的解决方案(我们不需要不断地重建列表),但你必须首先将remove_indices转换为一个set! - c z
最优雅的解决方案 - David Bridgeland

39

针对不同方法的性能没有太多提示,因此我在所有三种一般不同的方法中测试了从50000个项目中删除5000个项目的测试,对于我来说numpy是赢家(如果你的元素适合numpy):

  • 枚举列表理解花费7.5秒[另一台PC上为4.5秒]
  • 反向顺序删除项目需要0.08秒[0.017(!)秒]
  • numpy.delete需要0.009秒[0.006秒]

这是我计时的代码(在第三个函数中,如果直接在numpy数组上工作允许,则可以删除从/到列表的转换):

import time
import numpy as np
import random

def del_list_indexes(l, id_to_del):
    somelist = [i for j, i in enumerate(l) if j not in id_to_del]
    return somelist

def del_list_inplace(l, id_to_del):
    for i in sorted(id_to_del, reverse=True):
        del(l[i])

def del_list_numpy(l, id_to_del):
    arr = np.array(l, dtype='int32')
    return list(np.delete(arr, id_to_del))

l = range(50000)
random.shuffle(l)
remove_id = random.sample(range(len(l)), 5000) # 10% ==> 5000

# ...

3
如果您首先将id_to_del转换为一个集合,那么del_list_indexes的速度应该会更快。 - C. Yduqoli
6
@C.Yduqoli,如果使用set进行enumerate,我得到的时间是:enumerate=0.0041,del=0.035,numpy=0.0079。正如预期的那样,enumerate是最快的。 - c z

19

如果您可以使用numpy,那么您可以删除多个索引:

>>> import numpy as np
>>> a = np.arange(10)
>>> np.delete(a,(1,3,5))
array([0, 2, 4, 6, 7, 8, 9])

如果您使用 np.r_,您可以将切片与单独的索引组合:

>>> np.delete(a,(np.r_[0:5,7,9]))
array([5, 6, 8])

然而,删除不是在原地进行的,所以您必须对其进行分配。


17

如果它们是连续的,你只需要这样做

x[2:6] = []

如果你想要移除非连续的索引,那就需要有点技巧了。

x = [v for i,v in enumerate(x) if i not in frozenset((2,3,4,5))] 

1
作为一条注释,你必须使用枚举来完成这个操作,因为你不能在迭代过程中删除某些东西,那样会很糟糕。 - Trickfire
@Trick 从技术上讲,只有在使用真正的迭代器时才是正确的。如果您使用一个简单的整数来维护列表中的位置,那么就没有问题,除非您在当前位置下面的位置删除了某些内容,否则索引将会跳过。 - Antimony
是的,这可能会超出界限,通常不是一个好习惯。 - Trickfire

6
lst = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
lst = lst[0:2] + lst[6:]

这是一个单步操作。它不使用循环,因此执行速度快。它使用列表切片。


你应该在代码中包含解释。通常,描述某些命令或代码如何以及为什么解决问题更有用,因为它可以帮助提问者和其他人解决类似的问题。 - Anthony Geoghegan
请查看编辑并指导。 - sreejesh appathinkattil
3
我想知道为什么经过1460天和23410次浏览,还没有想出这么简单的解决方法。这有点令人沮丧。 - sreejesh appathinkattil
我已经编辑了你的答案,以改善语法,并将变量名使用lst,因为list是Python数据类型。之所以没有人建议这个作为解决方案,是因为这会创建一个新的列表对象(基于原始内容),而不是实际修改原始列表对象。由于最终结果相似,我会点赞你的答案。欢迎来到[se]。 - Anthony Geoghegan
[6:] 是什么意思? - weteamsteve
这个答案需要更高一些。SMH - tejasvi88

2

另一种选项(在原处,任意组合的索引):

_marker = object()

for i in indices:
    my_list[i] = _marker  # marked for deletion

obj[:] = [v for v in my_list if v is not _marker]

1

虽然这是一个老问题,但我有一个答案。

首先,像这样浏览列表的元素:

for x in range(len(yourlist)):
    print '%s: %s' % (x, yourlist[x])

然后,使用一个元素索引列表调用此函数以弹出所需的元素。它足够健壮,不需要考虑列表的顺序。
def multipop(yourlist, itemstopop):
    result = []
    itemstopop.sort()
    itemstopop = itemstopop[::-1]
    for x in itemstopop:
        result.append(yourlist.pop(x))
    return result

作为奖励,结果应仅包含您想要删除的元素。

In [73]: mylist = ['a','b','c','d','charles']

In [76]: for x in range(len(mylist)):

      mylist[x])

....:

0: a

1: b

2: c

3: d

4: charles

...

In [77]: multipop(mylist, [0, 2, 4])

Out[77]: ['charles', 'c', 'a']

...

In [78]: mylist

Out[78]: ['b', 'd']


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