【问题标题】:Unexpected IndexError while removing list items [duplicate]删除列表项时出现意外的 IndexError [重复]
【发布时间】:2013-10-19 04:08:35
【问题描述】:

我是 Python 的初学者。我之前学过其他语言,比如C++(初学者)和JQuery。但是我发现python中的循环很混乱。

嗯,我想实现一个简单的结果。程序将遍历一个单词列表,然后它将与列表中的下一个单词两个字母匹配的单词删除:

test = ['aac', 'aad', 'aac', 'asd', 'msc']
for i in range(len(test)):
    if test[i][0:2] == test[i+1][0:2]:
        test.remove(test[i])

# This should output only ['aac', 'asd', 'msc']
print test

上面的代码应该从列表中删除'aac''aad'。但实际上,这会引发IndexError。此外,我无法达到预期的结果。能解释一下吗?

【问题讨论】:

    标签: python list python-2.7


    【解决方案1】:

    您正在更改列表的长度,同时在一个范围内循环,该范围一直到列表的起始长度;从列表中删除一项,最后一个索引不再有效。

    移动,因为项目从当前索引处的列表中删除,列表索引的其余部分移位;索引i + 1 中的内容现在位于索引i 中,并且您的循环索引不再有用。

    最后但并非最不重要的一点是,您正在循环直到test 的最后一个索引,但仍然尝试访问test[i + 1];即使您没有从列表中删除元素,该索引也不存在。

    您可以使用while 循环来实现您想要做的事情:

    test = ['aac', 'aad', 'aac', 'asd', 'msc']
    i = 0
    while i < len(test) - 1:
        if test[i][:2] == test[i+1][:2]:
            del test[i]
            continue
        i += 1
    

    现在 i 在每次循环迭代中都针对 new 长度进行测试,如果没有删除任何元素,我们只会增加 i。请注意,循环的长度限制为 minus 1,因为您想在每次迭代时测试 test[i + 1]

    注意我使用del test[i];无需扫描列表来搜索要删除的值再次;如果值在列表中出现多次但只有 later 实例应该被删除,这也可能导致细微的错误;例如['aac', 'foo', 'aac', 'aad'] 应该导致['aac', 'foo', 'aad']不是 ['foo', 'aac', 'aad'],这就是test.remove(test[i]) 的结果。

    演示:

    >>> test = ['aac', 'aad', 'aac', 'asd', 'msc']
    >>> i = 0
    >>> while i < len(test) - 1:
    ...     if test[i][:2] == test[i+1][:2]:
    ...         del test[i]
    ...         continue
    ...     i += 1
    ... 
    >>> test
    ['aac', 'asd', 'msc']
    

    您可以使用列表推导来避免缩小列表问题:

    >>> [t for i, t in enumerate(test) if i == len(test) - 1 or t[:2] != test[i + 1][:2]]
    ['aac', 'asd', 'msc']
    

    这两种方法都只需要一个循环遍历输入列表。

    【讨论】:

    • 我现在明白了。起初我认为 len(range(test)) 会随着列表项的删除而更新。但后来我明白了,我的想法是愚蠢的!我会使用 while 方法,因为它看起来最适合我。谢谢。一个问题:您使用了“继续”方法,但这真的有必要吗?
    • 如果你不使用continue,那么你必须使用else:;当您刚刚删除 test[i] 时,您不希望 i += 1 运行。
    • @Manoj 建议的方法应该部分有效。该方法能够处理“i+1 不存在”错误。但结果完全出乎意料。该代码仅删除列表的第一项并输出 ['aad', 'aac'。 'asd', 'msc']
    【解决方案2】:

    当您从列表中删除项目时,range(len(test)) 仍然具有相同的值。因此,即使您的 test 列表只剩下任何项目,循环仍在继续。

    我有两个解决方案:

    1. 将您想要的项目复制到新列表中,而不是删除它:

      test2 = test[i]
      

      并且不要忘记反转条件。

    2. 向后循环。像这样:

      n = len(test)
      for i in range(n):
          j = n - i - 1
          if j > 1:
          if test[j][0:2] == test[j-1][0:2]:
              test.remove(test[j])
      

      或者,正如 martijn 建议的那样:

      n = len(test)
      for i in range(n-1, 0, -1):
          if i > 1:
          if test[i][0:2] == test[i-1][0:2]:
              test.remove(test[i])
      

    希望对您有所帮助!

    P.S 对不起,我之前的回答很愚蠢

    【讨论】:

    • 好吧,从技术上讲,他在从列表中删除项目时并不是在迭代列表。他正在迭代 range(len(test)) 并从 test 中删除项目,而不是在删除 test 时迭代。问题是他每次杀死test中的东西时都需要从range(len(test))中弹出一个元素
    • 另外,你仍在从test 中删除,这将再次导致同样的错误
    • testand test2 以相同的大小开始。但是当您删除test2 中的内容时,它的大小会缩小。这意味着test[i]test2[i] 将不再引用同一个对象。因此,您可能仍会在此处遇到索引错误。此外,test2=test 使两个变量引用同一个列表,而不是 test 的两个单独副本。所以test2.remove(…) 在这种情况下等同于test.remove(…)。我强烈建议在发布之前测试您的代码
    • 不,现在真的修好了。之前我完全没有想到。对不起先生!
    • 与其反转i,为什么不使用range() 向后循环呢? range(len(test) - 1, 0, -1);这个循环从len(test) - 11,向下。
    【解决方案3】:

    正如其他人所说,当您删除项目时,列表会变短,从而导致索引错误。

    与原始问题保持一致。如果您希望使用 list.remove() 删除项目,您可以将找到的项目添加到列表中,然后遍历它们并将它们从原始列表中删除,如下所示:

    # Set up the variables
    test = ['aac', 'aad', 'aac', 'asd', 'msc']
    found = []
    # Loop Over the range of the lenght of the set
    for i in range(len(test)):
        try:
            if test[i].startswith(test[i+1][0:2]):
                found.append(test[i])  # Add the found item to the found list
        except IndexError: # You'll hit this when you do test[i+1]
            pass
    
    # Remove the Items at this point so you don't cause any issues
    for item in found:
        test.remove(item)  # If an item has been found remove the first instance
    
    # This sholuld output only ['aac', 'asd', 'msc']
    print test
    

    编辑:

    根据 Martins 的评论,您不需要列出需要删除的项目的第二个列表,而是可以像这样列出不需要删除的项目:

    # Set up the variables
    test = ['aac', 'aad', 'aac', 'asd', 'msc']
    found = []
    
    # Loop Over the range of the lenght of the set
    for i in range(len(test)):
        try:
            if not test[i].startswith(test[i+1][0:2]):
                found.append(test[i])  # Add the found item to the found list
        except IndexError: # You'll hit this when you do test[i+1]
            found.append(test[i]) # If there is no test[i+1], test[i] must be cool.
    
    
    # This sholuld output only ['aac', 'asd', 'msc']
    print found
    

    【讨论】:

    • 为什么不从不需要需要删除的项目构建found?那么你已经有了你的新列表!
    • 好主意马丁会更新我的答案,感谢您抽出宝贵时间发表评论!
    • 如果你可以看一下它,我的答案已经更新了 Martijn 它会有所帮助
    • 现在看起来不错;一个循环比两个好(当然,前提是一个循环不会在迭代中翻倍)。
    • 感谢您的帮助,谢谢。
    【解决方案4】:

    for i in range(len(test)) 为您提供一个包含test 有效索引的列表。但是,随着您在循环中不断从test 中删除项目,test 的大小会减小,从而导致一些原本有效的索引变得无效。

    你正在做的事情是这样的:

    L = range(len(test))
    for i in L:
      if condition:
        # remove something from test <- the size of test has changed.
                                     # L[-1] is no longer a valid index in test
    

    您可以做的是累积您想要删除的事物的索引并在以后删除它们:

    deleteThese = set()
    for i,item in enumerate(test[:-1]):
      if item[0:2] == test[i+1][0:2]:
        deleteThese.add(i)
    test = [item for i,item in enumerate(test) if i not in deleteThese]
    

    输出

    In [70]: test = ['aac', 'aad', 'aac', 'asd', 'msc']
    
    In [71]: %paste
    deleteThese = set()
    for i,item in enumerate(test[:-1]):
      if item[0:2] == test[i+1][0:2]:
        deleteThese.add(i)
    test = [item for i,item in enumerate(test) if i not in deleteThese]
    
    ## -- End pasted text --
    
    In [72]: test
    Out[72]: ['aac', 'asd', 'msc']
    

    【讨论】:

    • 您可以通过构建一个要keep的项目列表来避免循环两次。
    猜你喜欢
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 2021-05-04
    • 1970-01-01
    • 2022-01-10
    • 2012-04-17
    • 1970-01-01
    相关资源
    最近更新 更多