Python中的列表推导:高效选择列表中的元素
假设我有一个元素列表,我想根据某个特定的函数(比如与另一个元素的距离)来选择其中的一些元素。
我希望得到一个包含距离和元素的元组列表。所以,我写了以下代码:
result = [ ( myFunction(C), C) for C in originalList if myFunction(C) < limit ]
但是,myFunction
是一个非常耗时的函数,而originalList
又相当大。所以这样做的话,myFunction
会对每个被选中的元素调用两次。
那么,有没有办法避免这种情况呢?
我还有另外两种选择,但效果都不是很好:
第一种是先创建一个未过滤的列表:
unfiltered = [ (myFunction(C),C) for C in originalList ]
然后再对它进行排序:
result = [ (dist,C) for dist,C in unfiltered if dist < limit ]
但这样的话,我就会复制我的
originalList
,浪费一些内存(因为列表可能很大——超过10,000个元素)。第二种方法比较复杂,不太符合Python的风格,但效率高(这是我们能做的最好方法,因为函数应该对每个元素只计算一次)。
myFunction
会把它上一次的结果存储在一个全局变量中(比如叫lastResult
),然后在列表推导中重用这个值。result = [ (lastResult,C) for C in originalList if myFunction(C) < limit ]
你有没有更好的主意,能以高效且符合Python风格的方式实现这个呢?
谢谢你的回答。
7 个回答
3
先计算好距离,然后再筛选出结果:
with_distances = ((myFunction(C), C) for C in originalList)
result = [C for C in with_distances if C[0] < limit]
注意:我不是创建一个新的列表,而是用生成器表达式来构建距离和元素的配对。
4
别用列表推导式,这里用普通的for循环就可以了。
9
当然,下面这两者之间的区别是:
[f(x) for x in list]
而这个:
(f(x) for x in list)
第一个会把列表直接生成在内存里,而第二个是一个新的生成器,它会延迟计算。
所以,简单来说,把“未过滤”的列表写成一个生成器就可以了。这里是你的代码,生成器直接写在里面:
def myFunction(x):
print("called for: " + str(x))
return x * x
originalList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
limit = 10
result = [C2 for C2 in ((myFunction(C), C) for C in originalList) if C2[0] < limit]
# result = [C2 for C2 in [(myFunction(C), C) for C in originalList] if C2[0] < limit]
注意,你在打印输出时不会看到这两者有什么区别,但如果你查看内存使用情况,注释掉的第二个语句会占用更多的内存。
要对你问题中的代码做一个简单的修改,把未过滤的部分改成这样:
unfiltered = [ (myFunction(C),C) for C in originalList ]
^ ^
+---------- change these to (..) ---------+
|
v
unfiltered = ( (myFunction(C),C) for C in originalList )