Python中的“for in”循环以打印列表中的最后一项。

5

最近我学习了关于列表和for循环的知识,以及命令.pop(),它可以指示并删除列表中的最后一个项目。

因此,我尝试编写代码,逐个删除列表中的最后一个项目,直到只剩下一个项目为止。

代码如下:

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

for i in list_A:
    print(list_A.pop())
    if 'c' not in list_A:
        break

print("job done.")

Python 3.6 的输出如下:

/Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
j
i
h
g
f
job done.

如您所见,它确实起作用了,但只有一半?

我本来期望得到:

j
i
h
g
f
e
d
c
job done

我的意思是,如果返回一些错误,我会更加舒适,这说明代码不正确。但为什么它能工作,但无法完全通过?


你没有得到预期的输出是因为你在迭代过程中修改了可迭代对象。 - Christian Dean
不要使用 list_A.pop() - DYZ
大家,非常感谢!!!我确实在尝试玩弄这些东西,看看它们如何协同工作。显然,我不熟悉“迭代”,现在我明白while循环是更好的选择。 - jxie0755
4个回答

6
你正在遍历列表时对其进行改变。
你可以使用一个 while 循环来完成这个操作:
list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

while 'c' in list_A:
    print(list_A.pop())

print('job done')

输出:

j
i
h
g
f
e
d
c
任务完成

更加高效的方法是确定哨兵字符第一次出现的索引,然后移除它和列表中剩余的元素(虽然被移除的字符没有被打印):

try:
    pos = list_A.index('c')
    list_A[:] = list_A[:pos]
    # del list_A[pos:]           # more efficient alternative suggested by @ShadowRanger
except ValueError as e:
    pass

当然,您始终可以在它们被删除之前切出这些部分并在之后打印它们。removed = list_A[pos:]del list_A[pos:](使用del避免使涉及list_A [:] = list_A [:pos]的临时变量),for x in reversed(removed): print(x) - ShadowRanger
这个代码在每次迭代中都会检查c是否在列表中,为什么不能只循环最多n次而不是n²*(n+1)/2 - DjaouadNM
@ShadowRanger:是的,del 更好。 - mhawke
@MrGeek:你可以这样做。这只是演示代码 - 我不建议在实践中这样做。无论如何,我已经提供了一个更有效的版本。 - mhawke
@mhawke 非常好,更好了。 - DjaouadNM

3
在Python中使用for...in循环时,不应该修改列表。
这里发生的是以下情况:
  • 循环从第一项到最后一项,因此从a开始。
  • pop()删除了最后一个列表条目,因此在第一个循环迭代中,您摆脱了最后一个字母j并将其打印出来。
  • 这一切都继续进行下一次5个字母。您从左边迭代它们,但同时从右边删除最后一个字母。
  • 遇到e时,您会从列表中删除并打印f
  • 之后,列表包含字母ae,由于您刚刚迭代了e,因此循环的工作完成了。
很难说你想做什么,因为更多地是玩耍而不是完成任务。我建议每当您打算在循环内部编辑列表时使用while循环。您带有适当语义的示例可能如下:
while list_A:
    print(list_A.pop())
    if "c" not in list_A:
        break

这个循环会一直执行,直到列表中没有 c 才停止。

非常感谢您的快速回答。我理解您所说的一切,除了“迭代”这个词。我会尝试通过谷歌来理解它。我必须说,我确实有点困惑于for循环和while循环之间的区别。正如您所提到的,for循环不应修改列表。那么在这两个循环命令之间有其他建议吗?我如何快速确定在任何情况下使用哪一个? - jxie0755
这个代码在每次迭代中都会检查c是否在列表中,为什么不能只循环最多n次而不是n²*(n+1)/2 - DjaouadNM
@MrGeek 你说得很对,这不是最高效的解决方案。但在这种情况下,我想尽可能保持简单,因为OP只是在学习Python。 - Hubert Grzeskowiak
@HubertGrzeskowiak 我知道,我的解决方案也不是最好的,我只是认为有更好的方法来解决它。 - DjaouadNM
@MrGeek 如果没有问题定义,就无法确定最佳解决方案。OP想要理解他的代码,我认为我们都帮助解释了它。 - Hubert Grzeskowiak

2

我最近回答了一个类似的问题,归纳起来就是:不要修改你正在迭代的序列。

使用自定义迭代器(来自我的另一个答案)可以说明发生了什么:

class CustomIterator(object):
    def __init__(self, seq):
        self.seq = seq
        self.idx = 0

    def __iter__(self):
        return self

    def __next__(self):
        print('give next element:', self.idx)
        for idx, item in enumerate(self.seq):
            if idx == self.idx:
                print(idx, '--->', item)
            else:
                print(idx, '    ', item)
        try:
            nxtitem = self.seq[self.idx]
        except IndexError:
            raise StopIteration
        self.idx += 1
        return nxtitem

    next = __next__  # py2 compat

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

for i in CustomIterator(list_A):
    print(list_A.pop())
    if 'c' not in list_A:
        break

这将打印:

give next element: 0
0 ---> a
1      b
2      c
3      d
4      e
5      f
6      g
7      h
8      i
9      j
j
give next element: 1
0      a
1 ---> b
2      c
3      d
4      e
5      f
6      g
7      h
8      i
i
give next element: 2
0      a
1      b
2 ---> c
3      d
4      e
5      f
6      g
7      h
h
give next element: 3
0      a
1      b
2      c
3 ---> d
4      e
5      f
6      g
g
give next element: 4
0      a
1      b
2      c
3      d
4 ---> e
5      f
f
give next element: 5
0      a
1      b
2      c
3      d
4      e

所以这并不是因为break而结束,而是因为它遍历了整个列表(或更好的说法:直到没有更多项!)。
此外,'c' not in listA是一种O(n)操作,因此您的循环实际上是O(n**2)。为什么不只找到'c'的第一个索引,然后简单地迭代直到你到达那里:
list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

try:
    c_index = list_A.index('c')
except ValueError:
    # no 'c' in the list, probably should do something more useful here ...
    pass
else:
    for item in reversed(list_A[c_index:]):  # print the items
        print(item)
    del list_A[c_index:]  # remove the items from the list

输出(如预期):

j
i
h
g
f
e
d
c

1
啊,使用自定义迭代器的想法非常好。+1 真的有助于说明在迭代过程中修改可迭代对象为什么是一个坏主意。 - Christian Dean

1
list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

while list_A: # while list_A has elements, in case 'c' wasn't present
    el = list_A.pop() # save the last element
    print(el)
    if 'c'==el: # if 'c' was popped (reached)
        break
print("job done.")

这样做,即使'c'不存在,它也会打印所有内容然后退出。这也避免了在每次迭代中检查'c'是否存在,这需要时间。
根据@MSeifert的评论,如果循环不应该在第一个弹出的c处停止,而是在列表没有c时停止,对上面的代码进行一些修改即可得到:
list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'c', 'h', 'i', 'j']

while list_A:
    print(list_A.pop())
    if 'c' not in list_A:
        break
print("job done.")

我们可以更快地进行,但我不知道你是否已经学习了列表切片和推导式,因此这里有一个更好、更快的解决方案:

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
try:
  p=list_A.index('c')
  r='\n'.join(list_A[list_A.index('c'):][::-1])
except ValueError:
  r='\n'.join(list_A[::-1])
print(r)
print('job done.')

@HubertGrzeskowiak 如果 'c' 不存在,我不会收到异常,因为此时数组将被打印并清空,因此 while list_A: 将评估为 False,循环终止。 - DjaouadNM
你说得对。我的错。我想我把它和另一个答案混淆了。我删除了不必要的评论。 - Hubert Grzeskowiak
3
这个答案基于这样一个假设:它应该在第一个弹出的 'c' 处停止,而问题中的代码表明它应该在列表中不再有 'c' 的情况下立即停止。如果只有一个 'c' 那么这是等效的,但如果包含多个(或没有)则结果会有所不同。 - MSeifert
@MSeifert 我没有注意到,谢谢。无论如何,我更新了我的答案。 - DjaouadNM
但是使用新方法,您不必“保存”弹出的元素。 - MSeifert
显示剩余2条评论

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