ChatGPT解决这个技术问题 Extra ChatGPT

迭代时如何从列表中删除项目?

这个问题的答案是社区的努力。编辑现有答案以改进这篇文章。它目前不接受新的答案或交互。

我正在迭代 Python 中的元组列表,如果它们满足某些条件,我会尝试将它们删除。

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

我应该用什么来代替 code_to_remove_tup?我不知道如何以这种方式删除该项目。

此页面上的大多数答案并没有真正解释为什么在迭代列表时删除元素会产生奇怪的结果,但是 accepted answer in this question does 对于第一次遇到此问题的初学者来说可能是一个更好的骗局。

u
user2357112

您可以使用列表推导创建一个仅包含您不想删除的元素的新列表:

somelist = [x for x in somelist if not determine(x)]

或者,通过分配给切片 somelist[:],您可以改变现有列表以仅包含您想要的项目:

somelist[:] = [x for x in somelist if not determine(x)]

如果有其他对 somelist 的引用需要反映更改,则此方法可能很有用。

除了理解,您还可以使用 itertools。在 Python 2 中:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

或者在 Python 3 中:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

如果您知道只有少数将被删除,您能否使其更快?即,只删除那些并将其他留在原地而不是重新编写它们?
如果我的清单很大并且无法制作副本怎么办?
@jpcgt 您应该使用 somelist[:] = (x for x in somelist if determine(x)) 这将创建可能不会创建任何不必要的副本的生成器。
@RostislavKondratenko:在内部实现 somelist[:]= 调用 PySequence_Fast()list_ass_slice() 函数。这个函数总是返回一个列表,即 @Alex Martelli's solution that already uses a list instead of a generator is most probably more efficient
您是否愿意解释将列表理解分配给列表和列表克隆之间的区别?原始列表 somelist 不会在这两种方法中发生变异吗?
B
Boris Verkhovskiy

建议列表理解的答案几乎是正确的——除了他们构建了一个全新的列表,然后给它与旧列表相同的名称,他们不会修改旧列表。这与您在 @Lennart's suggestion 中通过选择性删除所做的不同 - 它更快,但如果您的列表是通过多个引用访问的,那么您只是重新安装其中一个引用而不更改列表对象本身就可能导致微妙的、灾难性的错误。

幸运的是,获得列表理解的速度和就地更改所需的语义非常容易——只需代码:

somelist[:] = [tup for tup in somelist if determine(tup)]

请注意与其他答案的细微差别:这个不是分配给一个barename - 它分配给一个恰好是整个列表的列表切片,从而替换同一个Python列表对象中的列表内容,而不仅仅是重新设置一个引用(从以前的列表对象到新的列表对象)像其他答案一样。


如何使用 dict 执行相同的切片分配?在 Python 2.6 中?
@Paul:由于字典是无序的,因此切片对字典毫无意义。如果您想用 dict b 的内容替换 dict a 的内容,请使用 a.clear(); a.update(b)
为什么可以通过替换变量引用的内容来“重新设置”引用之一导致错误?似乎这只是多线程应用程序中的潜在问题,而不是单线程应用程序。
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)]; 这会将 x 重新分配给列表解析的结果,但 y 仍然引用 原始 列表 ['foo','bar','baz']。如果您希望 xy 引用同一个列表,那么您可能已经引入了错误。正如 Alex 所示,您可以通过分配给整个列表的一部分来防止这种情况发生,我在这里展示:x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];。该列表已就地修改。确保对列表的所有引用(此处为 xy)都引用新列表。
事实上,使用 filter 函数也会创建一个新列表,不会修改元素......只有 olist[:] = [i for i in olist if not dislike(i)]
G
Georgy

您需要获取列表的副本并首先对其进行迭代,否则迭代将失败并可能出现意外结果。

例如(取决于什么类型的列表):

for tup in somelist[:]:
    etc....

一个例子:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]

@Zen 因为第二个迭代列表的副本。因此,当您修改原始列表时,您不会修改您迭代的副本。
与 list(somelist) 相比,做 somelist[:] 有什么好处?
list(somelist) 会将可迭代对象转换为列表。 somelist[:] 制作支持切片的对象的副本。所以他们不一定做同样的事情。在这种情况下,我想复制 somelist 对象,因此我使用 [:]
请注意阅读本文的任何人,这对于列表来说非常慢。 remove() 每次迭代都必须遍历整个列表,因此需要很长时间。
在处理只有十几个项目的列表时,大 O 时间并不重要。对于未来的程序员来说,清晰和简单的理解往往比性能更有价值。
J
Jean-François Fabre
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

你需要倒退,否则这有点像锯掉你坐在的树枝:-)

Python 2 用户:将 range 替换为 xrange 以避免创建硬编码列表


在最新版本的 Python 中,您可以使用内置的 reversed() 更简洁地执行此操作
reversed() 不会创建新列表,它会在提供的序列上创建一个反向迭代器。与 enumerate() 一样,您必须将其包装在 list() 中才能真正从中获取列表。你可能会想到 sorted(),它每次都会创建一个新列表(它必须这样做,所以它可以对它进行排序)。
@Mauris 因为 enumerate 返回一个迭代器,而 reversed 需要一个序列。如果您不介意在内存中创建一个额外的列表,我想您可以执行 reversed(list(enumerate(somelist)))
这对于数组来说是 O(N*M),如果从一个大列表中删除许多项目,它会非常慢。所以不推荐。
@SamWatkins 是的,这个答案适用于当您从一个非常大的数组中删除几个元素时。内存使用量减少,但速度可能会慢 m 倍。
C
Ciro Santilli Путлер Капут 六四事

解决方法概述

任何一个:

使用链表实现/自己滚动。链表是支持有效删除项目的适当数据结构,不会强迫您进行空间/时间权衡。 CPython 列表是使用此处提到的动态数组实现的,这不是支持删除的好数据类型。然而,标准库中似乎没有链表:Python 中是否存在链表预定义库? https://github.com/ajakubek/python-llist

Python中是否有链表预定义库?

https://github.com/ajakubek/python-llist

从头开始一个新的 list() ,并在末尾返回 .append() ,如下所述:https://stackoverflow.com/a/1207460/895245 这一次效率很高,但空间效率较低,因为它保留了额外的副本迭代期间周围的数组。

将 del 与索引一起使用,如下所述:https://stackoverflow.com/a/1207485/895245 这更节省空间,因为它分配了数组副本,但时间效率较低,因为从动态数组中删除需要转移所有以下内容项目减一,即 O(N)。

通常,如果您的操作又快又脏,并且不想添加自定义 LinkedList 类,则默认情况下您只想选择更快的 .append() 选项,除非内存是一个大问题。

官方 Python 2 教程 4.2。 “声明”

https://docs.python.org/2/tutorial/controlflow.html#for-statements

这部分文档清楚地表明:

您需要复制迭代列表以对其进行修改

一种方法是使用切片符号 [:]

如果您需要在循环内修改您正在迭代的序列(例如复制选定的项目),建议您首先制作一个副本。对序列进行迭代不会隐式生成副本。切片表示法使这变得特别方便: >>> words = ['cat', 'window', 'defenestrate'] >>> for w in words[:]: # 循环整个列表的切片副本。 ... if len(w) > 6: ... words.insert(0, w) ... >>> words ['defenestrate', 'cat', 'window', 'defenestrate']

Python 2 文档 7.3。 “for 语句”

https://docs.python.org/2/reference/compound_stmts.html#for

这部分文档再次说您必须制作副本,并给出了一个实际的删除示例:

注意:当序列被循环修改时有一个微妙之处(这只会发生在可变序列,即列表中)。内部计数器用于跟踪接下来使用哪个项目,并在每次迭代时递增。当此计数器达到序列的长度时,循环终止。这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已处理的当前项目的索引)。同样,如果套件在当前项目之前插入序列中的项目,则当前项目将在下一次循环中再次被处理。这可能会导致讨厌的错误,可以通过使用整个序列的切片制作临时副本来避免这些错误,例如,对于 a[:] 中的 x:

    if x < 0: a.remove(x)

但是,我不同意这种实现,因为 .remove() 必须遍历 整个列表 才能找到值。

Python 可以做得更好吗?

似乎可以改进这个特定的 Python API。例如,将其与:

Java ListIterator::remove 哪些文档“每次调用下一个或上一个调用只能进行一次此调用”

C++ std::vector::erase 在删除元素后返回一个有效的插入器

两者都清楚地表明,除了迭代器本身之外,您无法修改正在迭代的列表,并为您提供了无需复制列表的有效方法。

可能的基本原理是 Python 列表被假定为支持动态数组,因此任何类型的删除无论如何都会是时间效率低下的,而 Java 具有更好的接口层次结构,具有 ListIteratorArrayListLinkedList 实现。

Python 标准库中似乎也没有明确的链表类型:Python Linked List


最后有人指出了实际的文档。在此之前我无法理解任何答案。
E
Eli Courtwright

对于此类示例,您最好的方法是 list comprehension

somelist = [tup for tup in somelist if determine(tup)]

如果您正在执行比调用 determine 函数更复杂的事情,我更喜欢构建一个新列表并在进行时简单地附加到它。例如

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

使用 remove 复制列表可能会使您的代码看起来更简洁,如下面的答案之一所述。您绝对不应该对非常大的列表执行此操作,因为这涉及到首先复制整个列表,并且还对要删除的每个元素执行 O(n) remove 操作,从而使其成为 O(n^2) 算法。

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)

T
Tim

对于那些喜欢函数式编程的人:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

或者

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))

1. 列表推导和生成器表达式是从纯函数式语言 Haskell 中借用的;它们的功能与 filter 完全相同,而且更加 Pythonic。 2. 如果您需要一个 lambda 来使用 mapfilter,列表 comp 或geneexpr 总是 更好的选择;当转换/谓词函数是在 C 中实现的 Python 内置函数并且可迭代不是非常小时,mapfilter 可能会稍快一些,但当您需要 listcomp 的 lambda 时,它们总是较慢/genexpr 可以避免。
M
Michael

我需要用一个巨大的列表来做到这一点,而复制列表似乎很昂贵,特别是因为在我的情况下,与剩余的项目相比,删除的数量很少。我采用了这种低级方法。

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

我不知道与复制大列表相比,几次删除的效率如何。如果您有任何见解,请发表评论。


就我而言,我需要将那些“不需要的”元素移动到另一个列表中。您对此解决方案有什么新评论吗?我也认为最好使用一些删除而不是重复列表。
如果性能是一个问题,这是正确的答案(尽管与@Alexey 相同)。也就是说,首先应该仔细考虑选择 list 作为数据结构,因为从列表中间移除需要在列表长度上线性时间。如果您真的不需要随机访问第 k 个连续项目,可以考虑 OrderedDict
@GVelascoh 为什么不创建 newlist = [],然后在 del array[i] 之前创建 newlist.append(array[i])
请注意,这可能是时间效率低下的:如果 list() 是链表,随机访问代价高昂,如果 list() 是数组,删除代价高昂,因为它们需要将所有后续元素向前移动。一个体面的迭代器可以使链表实现变得更好。然而,这可能是节省空间的。
@CiroSantilli郝海东冠状病毒六四事件法功轮:pop(i)操作还是O(n)。我将把存储效率置于 O(n) 的增量改进之上,但我明白为什么有人可能会以不同的方式做这件事。
M
Mujeeb

这里的大多数答案都希望您创建列表的副本。我有一个用例,其中列表很长(110K 项),而是继续减少列表更聪明。

首先你需要用while循环替换foreach循环,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

if 块中 i 的值不会更改,因为一旦删除旧项目,您将希望从相同的索引中获取新项目的值。


我不想喜欢这个,但我喜欢:)
我觉得这个很有创意!我希望看到更多关于这个算法的社区意见。它很容易理解,并且似乎被贡献者忽略了!
@tonysepia 很高兴看到这个解决方案仍然有用:)
@Mujeeb 哦是的,你可以在这里看到我在我的算法中使用它:stackoverflow.com/questions/71810348/…
n
ntk4

如果当前列表项符合所需条件,也可以只创建一个新列表。

所以:

for item in originalList:
   if (item != badValue):
        newList.append(item)

并避免必须使用新列表名称重新编码整个项目:

originalList[:] = newList

注意,来自 Python 文档:

copy.copy(x) 返回 x 的浅拷贝。 copy.deepcopy(x) 返回 x 的深层副本。


这不会增加几年前接受的答案中没有的新信息。
这很简单,只是查看问题@MarkAmery 的另一种方式。对于那些不喜欢压缩编码语法的人来说,它的浓缩程度较低。
C
Community

此答案最初是为了回答一个已被标记为重复的问题:Removing coordinates from list on python

您的代码中有两个问题:

1) 使用 remove() 时,您尝试删除整数,而您需要删除元组。

2) for 循环将跳过列表中的项目。

让我们来看看执行代码时会发生什么:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

第一个问题是您将“a”和“b”都传递给 remove(),但 remove() 只接受一个参数。那么我们如何才能让 remove() 与您的列表一起正常工作呢?我们需要弄清楚列表中的每个元素是什么。在这种情况下,每个都是一个元组。为了看到这一点,让我们访问列表的一个元素(索引从 0 开始):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

啊哈! L1 的每个元素实际上是一个元组。这就是我们需要传递给 remove() 的内容。 python 中的元组非常简单,只需将值括在括号中即可。 "a, b" 不是元组,但 "(a, b)" 是元组。所以我们修改你的代码并再次运行它:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

这段代码运行没有任何错误,但让我们看看它输出的列表:

L1 is now: [(1, 2), (5, 6), (1, -2)]

为什么 (1,-2) 仍在您的列表中?事实证明,在使用循环对其进行迭代时修改列表是一个非常糟糕的主意,无需特别注意。 (1, -2) 保留在列表中的原因是列表中每个项目的位置在 for 循环的迭代之间发生了变化。让我们看看如果我们给上面的代码提供一个更长的列表会发生什么:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

正如您可以从该结果中推断的那样,每次条件语句的计算结果为 true 并删除列表项时,循环的下一次迭代将跳过对列表中下一项的评估,因为它的值现在位于不同的索引处。

最直观的解决方案是复制列表,然后遍历原始列表,只修改副本。您可以尝试这样做:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

但是,输出将与之前相同:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

这是因为当我们创建 L2 时,python 实际上并没有创建新对象。相反,它只是将 L2 引用到与 L1 相同的对象。我们可以用 'is' 来验证这一点,这与仅仅“等于”(==)不同。

>>> L2=L1
>>> L1 is L2
True

我们可以使用 copy.copy() 制作一个真实的副本。然后一切都按预期工作:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

最后,有一个比制作 L1 的全新副本更简洁的解决方案。 reversed() 函数:

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

不幸的是,我无法充分描述 reversed() 的工作原理。将列表传递给它时,它会返回一个“listreverseiterator”对象。出于实际目的,您可以将其视为创建其参数的反向副本。这是我推荐的解决方案。


N
NoName

如果要在迭代时从列表中删除元素,请使用 while 循环,以便在每次删除后更改当前索引和结束索引。

例子:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1

f
fantabolous

如果您想在迭代期间做任何其他事情,最好同时获取索引(这可以保证您能够引用它,例如,如果您有一个 dicts 列表)和实际的列表项内容。

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumerate 使您可以同时访问项目和索引。 reversed 是为了让您稍后要删除的索引不会在您身上发生变化。


为什么在你有一个字典列表的情况下获取索引比在任何其他类型的列表的情况下更相关?据我所知,这没有任何意义。
X
Xenolion

一种可能的解决方案,如果您不仅想删除一些东西,而且还想在一个循环中对所有元素做一些事情,那么它很有用:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1

你真的应该只使用理解。它们更容易理解。
如果我想在一个循环中删除 bad 个东西,用它做点什么,还要用 good 个东西做点什么?
实际上,我意识到这里有一些聪明之处在于您使用开放切片 (alist[:]) 制作列表的副本并且由于您可能正在做一些花哨的事情,它实际上有一个用例。好的修订是好的。接受我的投票。
M
Mohideen bin Mohammed

for 循环将遍历索引..

考虑你有一个清单,

[5, 7, 13, 29, 65, 91]

您使用了名为 lis 的列表变量。你用同样的方法来删除..

你的变量

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

在第 5 次迭代中,

您的数字 35 不是质数,因此您将其从列表中删除。

lis.remove(y)

然后下一个值 (65) 移动到上一个索引。

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

所以第四次迭代完成指针移动到第五次..

这就是为什么您的循环自从移入上一个索引以来不涵盖 65 的原因。

所以你不应该将列表引用到另一个仍然引用原始而不是副本的变量中。

ite = lis #dont do it will reference instead copy

使用 list[::] 的列表副本也是如此

现在你它会给,

[5, 7, 13, 29]

问题是您在迭代期间从列表中删除了一个值,然后您的列表索引将崩溃。

所以你可以尝试理解。

它支持所有可迭代的,如列表、元组、字典、字符串等


用一种更简单的方式:不要 迭代您要更改的列表。相反,在具有要删除条件的项目的列表上进行迭代:lis = [5, 7, 13, 29, 35, 65, 91] not_primes = [35,65] for item in not_primes: if item in lis: lis.remove(item) 我自己遇到了这个问题,在这里进行了讨论:stackoverflow.com/q/72478091/1973308
B
Beefster

其他答案是正确的,从您正在迭代的列表中删除通常是一个坏主意。反向迭代避免了一些陷阱,但要遵循这样做的代码要困难得多,因此通常最好使用列表推导式或 filter

但是,在一种情况下,从您正在迭代的序列中删除元素是安全的:如果您在迭代时只删除一项。这可以使用 returnbreak 来确保。例如:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

当您对列表中满足某些条件的第一个项目执行一些具有副作用的操作,然后立即从列表中删除该项目时,这通常比列表推导更容易理解。


Q
Queequeg

您可以尝试反向循环,因此对于 some_list 您将执行以下操作:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

这样索引对齐并且不会受到列表更新的影响(无论您是否弹出 cur 元素)。


循环 reversed(list(enumerate(some_list))) 比自己计算索引更简单。
@MarkAmery 认为您不能以这种方式更改列表。
B
Bharat Mane

您可能希望使用内置的 filter()

更多详情check here


c
chseng

最有效的方法是列表推导,很多人展示他们的案例,当然,通过filter获得iterator也是一个好方法。

过滤器接收一个函数和一个序列。过滤器将传入的函数依次应用于每个元素,然后根据函数返回值是True还是False来决定是保留还是丢弃该元素。

有一个例子(得到元组中的赔率):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

注意:您也不能处理迭代器。迭代器有时比序列更好。


我可能认为这是从列表中删除项目的最惯用的方法。这种行为也是线程安全的,因为您的应用程序不会改变变量。
r
rafa

我需要做一些类似的事情,在我的情况下,问题是内存 - 我需要在一个列表中合并多个数据集对象,在对它们做了一些事情之后,作为一个新对象,并且需要摆脱我要合并的每个条目避免复制所有这些并炸毁内存。在我的情况下,将对象放在字典而不是列表中效果很好:

```

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

```


C
Community

TLDR:

我写了一个库,允许你这样做:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

如果可能,最好使用另一种方法,在迭代时不需要修改您的可迭代对象,但对于某些算法,它可能不是那么简单。因此,如果您确定您确实需要原始问题中描述的代码模式,这是可能的。

应该适用于所有可变序列,而不仅仅是列表。

完整答案:

编辑:此答案中的最后一个代码示例提供了一个用例,说明您有时可能想要就地修改列表而不是使用列表理解。答案的第一部分用作如何就地修改数组的教程。

该解决方案来自 senderle 的 this 回答(针对相关问题)。这解释了在遍历已修改的列表时如何更新数组索引。下面的解决方案旨在正确跟踪数组索引,即使列表被修改。

here https://github.com/alanbacon/FluidIterator 下载 fluidIter.py,它只是一个文件,因此无需安装 git。没有安装程序,因此您需要确保该文件位于您自己的 python 路径中。该代码是为 python 3 编写的,在 python 2 上未经测试。

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

这将产生以下输出:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

上面我们在流体列表对象上使用了 pop 方法。还实现了其他常见的可迭代方法,例如 del fluidL[i].remove.insert.append.extend。也可以使用切片修改列表(未实现 sortreverse 方法)。

唯一的条件是您只能在适当的位置修改列表,如果在任何时候 fluidLl 被重新分配给不同的列表对象,则代码将不起作用。原来的 fluidL 对象仍会被 for 循环使用,但会超出我们修改的范围。

IE

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

如果我们想访问列表的当前索引值,我们不能使用枚举,因为它只计算 for 循环运行了多少次。相反,我们将直接使用迭代器对象。

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

这将输出以下内容:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

FluidIterable 类只是为原始列表对象提供了一个包装器。原始对象可以作为流体对象的属性访问,如下所示:

originalList = fluidArr.fixedIterable

更多示例/测试可以在 fluidIter.py 底部的 if __name__ is "__main__": 部分找到。这些值得一看,因为它们解释了在各种情况下会发生什么。如:使用切片替换列表的大部分。或者在嵌套的 for 循环中使用(和修改)相同的迭代。

正如我所说的:这是一个复杂的解决方案,会损害代码的可读性并使其更难以调试。因此,应首先考虑其他解决方案,例如 David Raznick 的 answer 中提到的列表推导式。话虽如此,我发现这个类对我有用并且比跟踪需要删除的元素的索引更容易使用。

编辑:正如评论中提到的,这个答案并没有真正提出这种方法提供解决方案的问题。我将尝试在这里解决这个问题:

列表推导式提供了一种生成新列表的方法,但这些方法倾向于孤立地查看每个元素,而不是整个列表的当前状态。

IE

newList = [i for i in oldList if testFunc(i)]

但是,如果 testFunc 的结果取决于已经添加到 newList 的元素怎么办?或者仍然在 oldList 中的元素接下来可能会添加?可能仍然有一种使用列表推导式的方法,但它会开始失去它的优雅,而且对我来说,在适当的位置修改列表感觉更容易。

下面的代码是遭受上述问题的算法的一个示例。该算法将减少一个列表,以便没有元素是任何其他元素的倍数。

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

输出和最终简化列表如下所示

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]

很难说这是否是过度设计的,因为不清楚它试图解决什么问题。使用这种方法删除元素可以实现 some_list[:] = [x for x in some_list if not some_condition(x)] 无法实现的什么?如果没有答案,为什么有人会相信下载和使用包含拼写错误和注释掉的代码的 600 行库比单行代码更好地解决他们的问题? -1。
@MarkAmery。主要用例是当尝试确定是否应该删除(或添加或移动)项目时,不仅基于项目本身,还基于列表中另一个项目的状态或列表的状态作为所有的。例如,列表推导式不可能编写像 some_list[:] = [x for x in some_list if not some_condition(y)] 这样的内容,其中 y 是与 x 不同的列表元素。也不可能写出some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)]
M
MathKid

在某些情况下,您所做的不仅仅是一次过滤列表中的一项,您还希望在迭代时更改迭代。

这是一个示例,其中事先复制列表是不正确的,反向迭代是不可能的,并且列表推导也不是一种选择。

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p

S
Siddharth Satpathy

我可以想到三种方法来解决您的问题。例如,我将创建一个随机的元组列表 somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]。我选择的条件是sum of elements of a tuple = 15。在最终列表中,我们将只有总和不等于 15 的那些元组。

我选择的是一个随机选择的例子。随意更改元组列表和我选择的条件。

方法 1.> 使用您建议的框架(在 for 循环中填写代码)。我使用带有 del 的小代码来删除满足上述条件的元组。但是,如果两个连续放置的元组满足给定条件,则此方法将丢失一个元组(满足上述条件)。

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

方法 2.> 构造一个新列表,其中包含不满足给定条件的元素(元组)(这与删除满足给定条件的列表元素相同)。以下是代码:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

方法 3.> 查找满足给定条件的索引,然后使用删除与这些索引对应的元素(元组)。以下是它的代码。

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

方法一和方法二比方法三快。方法 2 和方法 3 比方法 1 更有效。我更喜欢方法2。对于上述示例,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7


M
Mark Zhang

如果以后要使用新的列表,可以简单的将elem设置为None,然后在后面的循环中进行判断,像这样

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

这样,您就不需要复制列表,并且更容易理解。


C
CENTURION

对于任何有可能变得非常大的东西,我使用以下内容。

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

这应该比其他任何事情都要快得多。


根据我的测量,NumPy 对于超过 20 个元素的列表开始变得更快,并且对于 1000 个或更多元素的大列表的过滤速度达到 12 倍以上。