9

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

因此,我想要一个包含距离和元素的元组列表。所以,我写了下面的代码

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 ]
    

你有什么更好的想法来实现这一点,以一种高效和 Pythonic 的方式吗?

感谢您的回答。

4

7 回答 7

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 重写为:

unfiltered = [ (myFunction(C),C) for C in originalList ]
             ^                                         ^
             +---------- change these to (..) ---------+
                                 |
                                 v
unfiltered = ( (myFunction(C),C) for C in originalList )
于 2009-08-03T14:38:39.950 回答
3

不要使用列表推导;正常的 for 循环在这里很好。

于 2009-08-03T14:39:03.040 回答
3

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

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

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

于 2009-08-03T14:42:03.303 回答
1

一些选项:

  • 使用记忆
  • 使用普通的 for 循环
  • 创建一个未过滤的列表,然后过滤它(您的选项 1)。“浪费”的内存将很快被 GC 回收——这不是您需要担心的事情。
于 2009-08-03T14:52:33.523 回答
1

Lasse V. Karlsen 对您的问题有很好的答复。

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

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

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

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

使用第二点,您可能会丢弃大量候选匹配,并且只在需要时进行缓慢的计算。

边界框必须预先缓存。

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

或者如果您在 3D 中,则为凸封闭多边形

于 2009-08-04T09:37:38.860 回答
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
于 2009-08-03T14:42:05.127 回答
0

选项1有什么问题?

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

10,000 个元素只是 10,000 个指向指向现有对象的元组的指针。想想160K左右的内存。几乎不值得谈论。

于 2009-08-03T14:42:59.573 回答