Python:取模运算符表现异常

3
解决方案可能相当简单,但我就是想不出来。这里是代码,它是一个简单的斐波那契数列生成器。目标是将所有小于4,000,000的偶数斐波那契数相加。
我的方法是首先生成所有小于4,000,000的斐波那契数,然后: a) 生成一个新列表("even"),其中包含偶数(这个方法很好用) b) 从列表"all"中删除奇数
然而,在后一种情况下,输出结果是这样的(我不明白原因): [2, 5, 8, 21, 34, 89, 144, 377, 610, 1597, 2584, 6765, 10946, 28657, 46368, 121393, 196418, 514229, 832040, 2178309, 3524578]
非常感谢您的帮助!
all = []
even = []

def fibonacci():
    a, b = 1, 2
    while a < 4000000:
        all.append(a)
        a, b = b, a + b
    print all

##Putting all the even fibonacci numbers in a different list and summing them up works fine
#    for i in all:
#        if i % 2 == 0:
#            even.append(i)
#    print even                   
#    print sum(even)


# But for some strange reason I can't figure out how to remove the odd numbers from the list
    for i in all:
        if i % 2 != 0:
            all.remove(i)

    print all
    print sum(all)

fibonacci()

我建议您不要使用“all”作为变量名称。有一个非常有用的内置函数叫做 all,它非常方便。 - DSM
DSM,感谢您指出这一点! - talkinghead
另外,如果有人对斐波那契数列感到困惑:是的,它通常以0、1、1、2...开头,但是Project Euler针对这个问题的说明要求从1、2...开始。 - talkinghead
感谢以下精彩的回答!我觉得我从中学到了很多。 - talkinghead
4个回答

4

这是一种“陷阱”情况:当你遍历列表并移除其中的项目时,会修改列表,导致你的遍历表现出意外行为。尝试以下操作:

...
# But for some strange reason I can't figure out how to remove the odd numbers from the list
    for i in all[:]:
        if i % 2 != 0:
            all.remove(i)
...

这就是所谓的“切片”符号,它会导致你迭代一个一次性的列表副本,这样你的迭代不会受到所有.remove()调用的影响。

3
一种更为简洁、或许更易理解且至少同样有效的备选方案(因为你复制了整个列表),是使用过滤器生成一个新列表:all = [x for x in all if x % 2 == 0] - user395760
是的,列表推导式看起来不错,如果我自己编写代码,我可能会这样写。如果您将其编写为答案,我会投赞成票!然而,我通常发现,我的答案对于提问者越接近问题,就越有用,因此我的 SO 风格是尽量最小化修改问题以使其正确运行。 - mattbornski
Matt,非常感谢您的解释。我发现您对我的原始代码所做的修改比delnan的解决方案更易于理解。虽然对于经验丰富的Python开发人员来说,delnan的代码可能更清晰,但对我来说有点过于简洁了。不过我已经记下来了。几个月后,也许我会更喜欢它... - talkinghead

3

在迭代列表时,您无法删除其中的项目。Python使用迭代器,它只知道相对于列表开头的当前索引。当您从列表的前面删除项目时,所有元素的位置都会改变,您会跳过下一个元素。

有很多方法可以避免这个问题,例如使用生成器:

def fibonacci():
    a, b = 1, 2
    while a < 4000000:
        yield a
        a, b = b, a + b

def even(seq):
    for item in seq:
        if item % 2 == 0:
            yield item

print sum(even(fibonacci()))

0
如果我们仔细观察以下代码中迭代发生的方式。
for i in all:
        if i % 2 != 0:
            all.remove(i)

            # Add these two lines for debugging.. 
            # Or to know how this iteration functions

            print "when %d: " %i 
            print all

    print "Remaining Evens",
    print all

如果最大数是100,输出将会是这个样子。

original series [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
when 1: 
[2, 3, 5, 8, 13, 21, 34, 55, 89]
when 3: 
[2, 5, 8, 13, 21, 34, 55, 89]
when 13: 
[2, 5, 8, 21, 34, 55, 89]
when 55: 
[2, 5, 8, 21, 34, 89]
Remaining Evens [2, 5, 8, 21, 34, 89]

当 Python 开始迭代列表时,它只在技术上记住了必须迭代的数字的位置。

如果我们观察输出:

在第一次迭代中,它删除了 1。

在下一次迭代中,它记住了必须计算第二个位置。现在列表从 "2" 开始。因此第二个位置是 "3"。因此,它将其删除。

在下一次迭代中,它记住了必须从第三个位置开始计数。现在在当前列表中,第三个位置是 "8"。因此,它从那里开始计数... 而不是从 "5" 开始。因此,因为 8 不满足条件,它跳过了这些数字。

因此,它跳过了所有那些数字...

如何解决:

实际上,您需要复制 "all" 列表并进行迭代。(它不应该引用相同的对象..)。如果这样做,则会发生相同的事情。

您可以通过简单地使用切片操作符来完成:

copy_all= all[:] 

#or else, you need to use deepcopy()

import copy
copy_all = copy.deepcopy(all)

# you iterate copy_all but delete in all. 

However, prefer the first method. Its very simple.

-1

这是因为您删除的是索引 i 而不是数字 "i"。


3
我不相信那是真的。 `Python 2.7.2 (default, Nov 14 2011, 19:37:59) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information.
foo = [5, 4, 3, 2, 1] foo.remove(5) foo [4, 3, 2, 1]`
- mattbornski

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