【问题标题】:List Comprehensions in Python : efficient selection in a listPython中的列表理解:列表中的有效选择
【发布时间】:2009-08-03 14:30:18
【问题描述】:

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

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

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 的方式吗??

感谢您的回答。

【问题讨论】:

    标签: python list-comprehension


    【解决方案1】:

    当然,以下两者的区别:

    [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 )
    

    【讨论】:

      【解决方案2】:

      不要使用列表推导;正常的 for 循环在这里就可以了。

      【讨论】:

      • @ThibThib 确实提到他的功能非常耗时。所以loop是很不合适的。
      • @Selinap:为什么循环不合适?它的工作原理与列表推导相同,而不会因为尝试保存中间结果而感到困惑。
      • @Selinap,我不同意。通过使用常规的 for 循环,您可以存储 myFunction(C) 的结果并第二次使用该结果,这正是 ThibThib 所寻找的。也就是说,我最喜欢 Lasse V. Karisen 的回答。
      【解决方案3】:

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

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

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

      【讨论】:

      • 这与 Lasse V. Karlsen 的回答不同吗?
      • 答案是一样的。除了有一个小错别字。我猜最后一行是 result = [ C for C in with_distances if C[0]
      【解决方案4】:

      一些选项:

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

      【讨论】:

      • Python 是否实现了一些记忆技术?还是由程序员来实现,例如,最新结果的缓存?
      • 这取决于程序员,但您可以编写一个通用函数,将函数作为参数并返回该函数的记忆版本。
      【解决方案5】:

      Lasse V. Karlsen 对您的问题的回答非常出色。

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

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

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

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

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

      边界框必须预先缓存。

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

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

      【讨论】:

      • 感谢您的回答。我没有使用折线(仅通过计算正交距离来寻找邻居的球体上的点)。而且我已经在使用这些技术来计算距离的上限(但速度很快),并用它来删除很多候选者。距离函数不是很慢(在我的旧计算机上计算 (1000^2)/2 距离需要 1 秒),但我只是不想计算不必要的距离。
      【解决方案6】:

      而不是像选项 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
      

      【讨论】:

      • 这不需要 OP 编写自己的类吗?我的理解是 C 是一个整数。
      【解决方案7】:

      选项 1 有什么问题?

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

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

      【讨论】:

      • 是的,当然只有10000个指针。但如果可以避免它们,我更喜欢。它更干净。
      猜你喜欢
      • 2015-10-23
      • 1970-01-01
      • 2014-08-26
      • 2020-12-03
      • 2020-10-23
      • 1970-01-01
      • 2018-03-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多