Python中的列表理解:列表中的有效选择

时间:2009-08-03 14:30:19

标签: python list-comprehension

假设我有一个元素列表,我想根据某个函数(例如 distance 到另一个元素)只选择其中一些元素。

我希望得到一个元组列表,包括距离和元素。所以,我写了以下代码

result = [ ( myFunction(C), C) for C in originalList if myFunction(C) < limit ]

但是myFunction是一个非常耗时的功能,originalList非常大。这样做,myFunction将为每个选定的元素调用两次。

那么,有没有办法避免这种情况?

我还有其他两种可能性,但它们并不是那么好:

  1. 第一个,就是创造了 未过滤的清单

    unfiltered = [ (myFunction(C),C) for C in originalList ]
    

    然后对其进行排序

    result = [ (dist,C) for dist,C in unfiltered if dist < limit ]
    

    但在那种情况下,我复制我的 originalList并浪费一些记忆 (清单可​​能很大 - 更多 超过10,000个元素)

  2. 第二个是棘手的,不是非常pythonic,但有效(我们可以做的最好,因为每个元素应该评估一次函数)。 myFunction最后存储它 得到一个全局变量(例如lastResult),并在该值中重用该值 列表理解

    result = [ (lastResult,C) for C in originalList if myFunction(C) < limit ]
    
  3. 你是否有更好的想法以高效和pythonic的方式实现这一目标?

    感谢您的回答。

7 个答案:

答案 0 :(得分: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 )

答案 1 :(得分:3)

不要使用列表理解;一个正常的循环就好了。

答案 2 :(得分:3)

只需预先计算距离,然后过滤结果:

with_distances = ((myFunction(C), C) for C in originalList)
result = [C for C in with_distances if C[0] < limit]

注意:我使用生成器表达式来构建距离/元素对,而不是构建新列表。

答案 3 :(得分:1)

一些选项:

  • 使用memoization
  • 使用普通for循环
  • 创建未过滤的列表,然后过滤它(您的选项1)。 GC会很快收回'浪费'的记忆 - 这不是你需要担心的事情。

答案 4 :(得分:1)

Lasse V. Karlsen对您的问题给出了很好的答复。

如果你的距离计算很慢,我想你的元素是折线,或类似的东西,对吗?

有很多方法可以让它更快:

  • 如果对象的边界框之间的距离是> X,那么这些对象之间的距离是> X.所以你只需要计算边界框之间的距离。

  • 如果您想要距对象A的距离小于X的所有对象,则只有边界框与A放大X的边界框相交的对象才是潜在匹配。

使用第二个点你可能会丢弃很多候选匹配,只在需要时进行慢速计算。

必须事先缓存边界框。

如果你真的有很多对象,你也可以使用空间分区......

如果您处于3D中,则为凸包围多边形

答案 5 :(得分:0)

不是像选项2那样使用全局变量,而是可以依赖于Python参数是由对象传递的事实 - 也就是说,传递给myFunction函数的对象是与列表中的对象相同的对象(这与通过引用调用不完全相同,但它足够接近)。

因此,如果你的myFunction在对象上设置了一个属性 - 比如_result - 你可以按该属性过滤:

result = [(_result, C) for C in originalList if myFunction(C) < limit]

你的myFunction可能如下所示:

def myFunction(obj):
    obj._result = ... calculation ....
    return obj._result

答案 6 :(得分:0)

选项1有什么问题?

“复制我的原始列表并浪费一些内存(列表可能非常大 - 超过10,000个元素)”

10,000个元素只有10,000个指向指向现有对象的元组的指针。想想160K左右的内存。非常值得一谈。