【问题标题】:Python: Adding element to list while iteratingPython:在迭代时将元素添加到列表中
【发布时间】:2023-04-02 14:30:01
【问题描述】:

我知道在迭代列表时不允许删除元素,但是否允许在迭代时将元素添加到 python 列表中。这是一个例子:

    for a in myarr:
      if somecond(a):
          myarr.append(newObj())

我已经在我的代码中尝试过,它似乎工作正常,但我不知道是不是因为我很幸运,它会在未来的某个时候中断?

编辑:我不想复制列表,因为“myarr”很大,因此它太慢了。我还需要用“somecond()”检查附加的对象。

编辑:在某些时候“somecond(a)”会是假的,所以不可能有无限循环。

编辑:有人询问“somecond()”函数。 myarr 中的每个对象都有一个大小,每次 "somecond(a)" 为真并且将一个新对象附加到列表中时,新对象的大小都会小于 a。 “somecond()” 有一个 epsilon 来表示对象可以有多小,如果它们太小,它将返回“false”

【问题讨论】:

  • 复制列表不需要太多时间。这是浅拷贝,不是深拷贝。
  • @S.Lott:列表很容易超过一亿个元素,上面的循环重复了很多次。即使是浅拷贝也会很慢。
  • 既然你说你已经这样做了,你的循环是否会遍历附加的项目以及列表中的原始项目?
  • @WesDec:您似乎在谈论广度优先搜索。一个简单的列表不适合你正在做的事情。这听起来像某种树。不是一个简单的列表。
  • @WesDec:另外,不要添加 cmets 道歉。只需专注于正确回答问题。

标签: python iteration


【解决方案1】:

你为什么不直接用惯用的 C 方式来做呢?这应该是防弹的,但不会很快。我很确定在 Python 中对列表进行索引会遍历链表,所以这是一个“Shlemiel the Painter”算法。但我倾向于不担心优化,直到明确代码的特定部分确实是一个问题。首先让它工作;如有必要,请考虑加快速度。

如果你想遍历所有元素:

i = 0  
while i < len(some_list):  
  more_elements = do_something_with(some_list[i])  
  some_list.extend(more_elements)  
  i += 1  

如果您只想遍历列表中最初的元素:

i = 0  
original_len = len(some_list)  
while i < original_len:  
  more_elements = do_something_with(some_list[i])  
  some_list.extend(more_elements)  
  i += 1

【讨论】:

  • Python 的列表类似于 C 数组或 C++ 向量;索引它们是常数时间。这实际上是一个非常好的解决方案,因为它执行 OP 的算法所做的事情,而不依赖于未定义的行为。
【解决方案2】:

好吧,根据http://docs.python.org/tutorial/controlflow.html

修改序列是不安全的 在循环中被迭代(这个 只能发生在可变序列中 类型,例如列表)。如果你需要 修改您正在迭代的列表 (例如,复制选定的 items) 你必须遍历一个副本。

【讨论】:

  • 我知道该文本,但其他语言的迭代器通常支持在迭代时将新元素附加到列表的末尾。我希望 python 也支持这一点,因为它会使事情变得更简单和更具可读性。
  • 我同意,但唯一的解决方案是制作副本,教程中也对此进行了说明。
  • 使用索引而不是for a in myarr 遍历列表怎么样? IE。 i = 0; while i &lt; len(myarr): a = myarr[i]; i = i + 1; if somecond(a): myarr.append(newObj())
  • 文档似乎已经改变,现在它并没有说它不安全,只是它可能很棘手修改集合的代码同时迭代同一个集合可能很难做到正确。相反,循环遍历集合的副本或创建新集合通常更直接:
【解决方案3】:

您可以使用来自 itertools 的 islice 在列表的较小部分上创建迭代器。然后,您可以将条目附加到列表中,而不会影响您正在迭代的项目:

islice(myarr, 0, len(myarr)-1)

更好的是,您甚至不必遍历所有元素。您可以增加步长。

【讨论】:

  • 请不要这样做。据我了解,这是未定义的行为,可能导致崩溃。 Python 的列表在底层就像一个动态 C 数组(或 C++ std::vector):添加一个元素可能会导致整个数组的重新分配以适应新元素。如果发生这种重新分配,那么我相信 islice() 将指向旧的、现在悬空的内存。最受好评的答案绝对是在迭代列表时添加元素的唯一快速且安全的方法。它说“它不会很快”,但这是不正确的,它几乎和正常迭代一样快。
  • 对不起,我错了,你现在得到一个赞成票 :) (注意:我必须编辑你的答案才能改变我的投票)一个好的测试确实是另一个建议答案:a = [0]\nfor i in a:\n print(a)\n if i &lt; 100:\n a.append(i+1)。这正确打印了从 0 到 100 的所有整数。这证明了 python 列表迭代器实际上并不直接指向内存:它们存储指向列表对象的指针和索引。
【解决方案4】:

简而言之:如果您绝对确定所有新对象都未通过somecond() 检查,那么您的代码工作正常,只是浪费一些时间迭代新添加的对象。

在给出正确答案之前,您必须了解为什么在迭代时更改 list/dict 是一个坏主意。使用for 语句时,Python 尽量聪明,每次返回一个动态计算的项。以list 为例,python 记住一个索引,每次它都会返回l[index] 给你。如果要更改l,结果l[index] 可能会很乱。

注意:这是一个stackoverflow question 来证明这一点。

在迭代时添加元素的最坏情况是无限循环,请在 python REPL 中尝试(或不尝试)以下内容:

import random

l = [0]
for item in l:
    l.append(random.randint(1, 1000))
    print item

它会不停地打印数字,直到内存用完,或者被系统/用户杀死。

了解内部原因,让我们讨论解决方案。以下是一些:

1。制作原产地列表的副本

迭代原始列表,并修改复制的。

result = l[:]
for item in l:
    if somecond(item):
        result.append(Obj())

2。控制循环何时结束

您决定如何迭代列表,而不是处理对 python 的控制:

length = len(l)
for index in range(length):
    if somecond(l[index]):
        l.append(Obj())

在迭代之前,计算列表长度,只循环length次。

3。将添加的对象存储在新列表中

不要修改原始列表,而是将新对象存储在新列表中,然后将它们连接起来。

added = [Obj() for item in l if somecond(item)]
l.extend(added)

【讨论】:

  • 你绝对的明星!像个傻瓜一样,只是创建一个新列表,头脑完全空白。
【解决方案5】:

你可以这样做。

bonus_rows = []
for a in myarr:
  if somecond(a):
      bonus_rows.append(newObj())
myarr.extend( bonus_rows )

【讨论】:

  • 这不好,因为我还需要检查 bonus_rows 中的对象,如果 somecond() 对其中一些对象为真,我还需要为这些对象创建新对象。
  • @WesDec:这就是你有“嵌套”循环的原因。将所有这些围绕在一个更大的循环中。
  • @WesDec: 或者停止使用简单列表并使用树。这听起来像是广度优先搜索,列表的结构是错误的。
  • @S.Lott 纯 python 中基于类的树可能效率低下。
【解决方案6】:

通过 i 直接访问您的列表元素。然后你可以追加到你的列表中:

for i in xrange(len(myarr)):
    if somecond(a[i]):
        myarr.append(newObj())

【讨论】:

  • @Justin Peel - 你用这个解决方案更快。
  • 这是一个,如果您想在附加项目上使用相同的循环代码,同时仍然保持干燥。似乎没有任何其他解决方案可以预见该用例。
【解决方案7】:

复制您的原始列表,对其进行迭代, 见下面修改后的代码

for a in myarr[:]:
      if somecond(a):
          myarr.append(newObj())

【讨论】:

  • 问题是列表很大,每次复制都会很慢。
  • @WesDec:在宣布它“非常慢”之前请测量。这是一个浅拷贝。很快。
  • @S.Lott:列表很容易超过一亿个元素,上面的循环重复了很多次。即使是浅拷贝也会很慢。
【解决方案8】:

我今天遇到了类似的问题。我有一份需要检查的项目清单;如果对象通过了检查,则将它们添加到结果列表中。如果他们没有通过,我对它们进行了一些更改,如果它们仍然可以工作(更改后大小 > 0),我会将它们添加到列表的后面以进行重新检查。

我选择了类似的解决方案

items = [...what I want to check...]
result = []
while items:
    recheck_items = []
    for item in items:
        if check(item):
            result.append(item)
        else:
            item = change(item)  # Note that this always lowers the integer size(),
                                 # so no danger of an infinite loop
            if item.size() > 0:
                recheck_items.append(item)
    items = recheck_items  # Let the loop restart with these, if any

我的列表实际上是一个队列,可能应该使用某种队列。但是我的列表很小(比如 10 项),这也可以。

【讨论】:

    【解决方案9】:

    如果您希望循环也循环遍历在循环期间添加到列表中的元素,您可以使用索引和 while 循环来代替 for 循环:

    i = 0
    while i < len(myarr):
        a = myarr[i];
        i = i + 1;
        if somecond(a):
            myarr.append(newObj())
    

    【讨论】:

      【解决方案10】:

      扩展 S.Lott 的答案以便处理新项目:

      todo = myarr
      done = []
      while todo:
          added = []
          for a in todo:
              if somecond(a):
                  added.append(newObj())
          done.extend(todo)
          todo = added
      

      最终名单在done

      【讨论】:

        【解决方案11】:

        替代解决方案:

        reduce(lambda x,newObj : x +[newObj] if somecond else x,myarr,myarr)
        

        【讨论】:

          【解决方案12】:

          假设你在这个列表的最后添加arr,你可以试试我常用的这个方法,

          arr = [...The list I want to work with]
          current_length = len(arr)
          i = 0
          while i < current_length:
              current_element = arr[i]
              do_something(arr[i])
              # Time to insert
              insert_count = 1 # How many Items you are adding add the last
              arr.append(item_to_be inserted)
              # IMPORTANT!!!!  increase the current limit and indexer
              i += 1
              current_length += insert_count
          

          这只是样板文件,如果你运行它,你的程序将因为无限循环而冻结。除非您需要,否则不要忘记终止循环。

          【讨论】:

            猜你喜欢
            • 2012-07-22
            • 2014-10-28
            • 2011-11-16
            • 2020-08-19
            • 1970-01-01
            • 1970-01-01
            • 2014-01-02
            • 1970-01-01
            • 2015-02-07
            相关资源
            最近更新 更多