我正在阅读关于Python的内容,我想对列表推导做一个问题。问题很简单:
Write a Program that gives the sum of the multiples of 3 and 5 before some n
take n = 1000 (Euler project, 1st problem)
我想做这样的事情:
[mysum = mysum + i for i in range(2,1000) if i%3==0 or i%5==0]
只有一行...但这不起作用。
1 - 这可以通过List comprehensions实现吗?怎么样??
2 - 还有,当使用List comprehensions时很好吗?
谢谢!
答案 0 :(得分:10)
列表推导的要点是生成一个结果值列表,每个源值一个(或者每个匹配源一个值,如果你有if
个子句)。
换句话说,它与map
(或map
和filter
调用链(如果您有多个子句)相同,除了您可以描述每个新值作为旧值的表达式,而不是必须将其包含在函数中。
您不能将语句(如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
如果您使用的语言使reduce
/ fold
比循环更直观,则Python具有reduce
功能。但是,通常不会将Pythonic用于消除for
循环并将块语句转换为单行。
更一般地说,尝试将事物塞进一行通常会使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中是二次的,而不是线性的......)
答案 1 :(得分:4)
你快到了!试试这个:
mysum = sum([i for i in range(2,10) if i%2==0 or i%5==0])
这将创建一个“循环”列表,然后将此列表传递给sum
函数。
像mylist = [*some expression using i* for i in iterable]
这样的列表理解是
mylist = []
for i in iterable:
mylist.append(*some expression using i*)
像mylist = [*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*)
只要您需要使用某个表达式构建 new 列表,就可以使用这些。列表推导实际上通常比等价for
循环更有效,因为它们在引擎盖下的C
中执行代码,而不是通过解释的python。
答案 2 :(得分:0)
这是我的2行实现,包含filter,sum和reduce:
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)