4

我正在阅读有关 Python 的内容,我想解决列表推导的问题。问题很简单:

编写一个程序,在某个 n 之前给出 3 和 5 的倍数之和

取 n = 1000(欧拉项目,第一个问题)

我想做这样的事情:

[mysum = mysum + i for i in range(2,1000) if i%3==0 or i%5==0]

只有一条线......但这不起作用。

  1. 这可以通过列表推导来实现吗?如何??
  2. 另外,什么时候最好使用列表推导?
4

3 回答 3

10

列表推导的要点是生成一个结果值列表,每个源值一个(或每个匹配的源值一个,如果您有if子句)。

换句话说,它与map(或一系列mapfilter调用,如果您有多个子句)相同,只是您可以将每个新值描述为根据旧值的表达式,而不必将其包装在一个函数。

您不能将语句(如mysum = mysum + i)放入理解中,只能放入表达式。而且,即使你能想出一个具有你想要的副作用的表达式,这仍然是对理解的令人困惑的误用。如果您不想要结果值列表,请不要使用列表推导。

如果您只是想在循环中执行计算,请编写一个显式for循环。


如果你真的需要它是一条线,你总是可以这样做:

for i in [i for i in range(2, 10) if i%2==0 or i%5==0]: mysum += i

建立一个要循环遍历的事物列表;for在循环中进行副作用-y 计算。

(当然,这是假设您已经获得了一些mysum可以添加的价值,例如,使用mysum = 0.)

而且,一般来说,每当您想要一个仅用于循环一次的推导时,您想要的推导类型就是生成器表达式,而不是列表推导。所以,把这些方括号变成括号,你会得到:

for i in (i for i in range(2, 10) if i%2==0 or i%5==0): mysum += i

但是,无论哪种方式,它都更具可读性和 pythonic 两行:

for i in (i for i in range(2, 10) if i%2==0 or i%5==0):
    mysum += i

……甚至三个:

not2or5 = (i for i in range(2, 10) if i%2==0 or i%5==0) 
for i in not2or5:
    mysum += i

如果您来自一种比循环更直观的语言,那么 Python 有一个reduce功能。但是,通常不认为 Pythonic 仅使用它来消除循环并将块语句转换为单行语句。foldreducefor

更一般地说,试图把东西塞进一行通常会使事情的可读性降低,而不是更多,在 Python 中,并且通常意味着你最终会在脑海中输入更多的字符和处理更多的标记,这不仅仅是抵消任何收益在节省线路。


当然,在这种特定情况下,您真正​​想做的就是总结一个列表的值。而这正是这样sum做的。这很容易理解。所以:

mysum += sum(i for i in range(2, 10) if i%2==0 or i%5==0)

(同样,这是假设您已经有了mysum要添加的内容。如果没有,只需将 更改+==。后面的所有示例都是如此,因此我将停止解释。)


说了这么多,我可能会把它写成一个显式的嵌套块:

for i in range(2, 10):
    if i%2==0 or i%5==0:
        mysum += i

…或者作为一系列迭代器转换(在这种情况下实际上只是一个转换):

not2or5 = (i for i in range(2, 10) if i%2==0 or i%5==0)
mysum += sum(not2to5)

以这种方式拆分事情确实没有任何成本(只要您使用生成器表达式而不是列表推导式),它通常会使您的代码意图更加明显。


关于生成器表达式的一些进一步解释:

生成器表达式就像列表推导式一样,只是它构建的是迭代器而不是列表。迭代器类似于某些函数式语言中的“惰性列表”,只是您只能使用一次。(通常,这不是问题。在上面的所有示例中,我们唯一想做的就是将它传递给sum函数或在for循环中使用它,然后我们再也不会引用它。)当你迭代它时,每个值都是按需构建的,然后在您到达下一个值之前被释放。

这意味着空间复杂度是恒定的,而不是线性的。您一次只能在内存中获得一个值,而对于列表,您显然已经获得了所有值。这通常是一个巨大的胜利。

但是,时间复杂度不变。列表推导会预先完成所有工作,因此它是构建的线性时间,然后可以免费使用。生成器表达式在您对其进行迭代时完成工作,因此可以免费构建,然后线性使用。无论哪种方式,同一时间。(由于缓存/内存局部性、流水线等原因,生成器表达式实际上可以明显更快,更不用说避免所有内存移动和分配成本。另一方面,对于琐碎的情况,它会更慢,至少在CPython,因为它必须通过完整的迭代器协议而不是列表的快速特殊情况。)

(我在这里假设每个步骤的工作是恒定的——显然[sum(range(i)) for i in range(n)]是 n 的二次方,而不是线性的……)

于 2013-09-30T22:12:05.937 回答
4

你快到了!尝试这个:

mysum = sum([i for i in range(2,10) if i%2==0 or i%5==0])

这将从“循环”中创建一个列表,然后将此列表传递给sum函数。

列表理解 likemylist = [*some expression using i* for i in iterable]

mylist = []
for i in iterable:
    mylist.append(*some expression using i*)

列表理解 likemylist = [*some expression using i* for i in iterable if *boolean with i*]

mylist = []
for i in iterable:
    if *boolean with i*:
        mylist.append(*some expression using i*)

每当您需要使用某个表达式构建新列表时,您都可以使用它们。列表推导实际上通常比等效for循环更有效,因为它们在底层执行代码C,而不是通过解释的 python。

于 2013-09-30T22:12:26.780 回答
0

这是我使用过滤器、求和和减少的 2 行实现:

def f(x): return x%3 == 0 or x%5 == 0
print sum(filter(f,range(2,1000)))

不错吧?你能解释一下这段代码吗:

not2or5 = (i for i in range(2, 1000) if i%3==0 or i%5==0)
print sum(not2or5)     
于 2013-09-30T22:50:50.577 回答