9

要素のリストがあり、特定の関数 (たとえば、他の要素までの距離) に従って、それらの一部のみを選択したいとします。

結果として、距離と要素を含むタプルのリストが必要です。だから、私は次のコードを書いた

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

しかしmyFunction、非常に時間のかかる機能であり、originalListかなり大きいです。そのようにmyFunctionすると、選択した要素ごとに が 2 回呼び出されます。

では、これを回避する方法はありますか??

他に 2 つの可能性がありますが、あまり良くありません。

  1. 最初のものは、フィルタリングされていないリストを作成することです

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

    そしてそれを並べ替えます

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

    しかし、その場合、私は私のものを複製し、 originalListいくらかのメモリを浪費します (リストは非常に大きくなる可能性があります - 10,000 要素以上)

  2. 2 つ目はトリッキーで、あまり Pythonic ではありませんが、効率的です (関数は要素ごとに 1 回評価する必要があるため、できる限りのことです)。myFunction最後の
    結果をグローバル変数(lastResultたとえば)に保存し、この値はリスト内包表記で再利用されます

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

効率的でPythonicな方法でそれを達成するためのより良いアイデアはありますか??

回答ありがとうございます。

4

7 に答える 7

9

確かに、次の2つの違い:

[f(x) for x in list]

この:

(f(x) for x in list)

1 つ目はメモリ内にリストを生成するのに対し、2 つ目は遅延評価を使用する新しいジェネレータです。

そのため、代わりに「フィルター処理されていない」リストをジェネレーターとして単純に記述します。ジェネレーターをインラインにしたコードは次のとおりです。

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]

この 2 つの出力結果に違いは見られませんが、メモリ使用量を見ると、コメント アウトされている 2 番目のステートメントがより多くのメモリを使用することに注意してください。

質問のコードを簡単に変更するには、 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

事前に距離を計算してから、結果をフィルタリングするだけです。

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 に答える
3

リスト内包表記を使用しないでください。ここでは通常の for ループで問題ありません。

于 2009-08-03T14:39:03.040 に答える
1

いくつかのオプション:

  • メモ化を使用する
  • 通常の for ループを使用する
  • フィルター処理されていないリストを作成し、それをフィルター処理します (オプション 1)。「無駄な」メモリは、GC によって非常に迅速に再利用されます。これについて心配する必要はありません。
于 2009-08-03T14:52:33.523 に答える
1

Lasse V. Karlsen は、あなたの質問に対して優れた回答をしています。

距離の計算が遅い場合、要素はポリラインなどであると思いますよね?

高速化する方法はたくさんあります:

  • オブジェクトのバウンディング ボックス間の距離が > X の場合、それらのオブジェクト間の距離は > X になります。したがって、バウンディング ボックス間の距離を計算するだけで済みます。

  • オブジェクト A から X 未満の距離にあるすべてのオブジェクトが必要な場合、バウンディング ボックスが X だけ拡大された A のバウンディング ボックスと交差するオブジェクトのみが一致する可能性があります。

2 番目のポイントを使用すると、おそらく多くの候補一致を削除し、必要な場合にのみ遅い計算を行うことができます。

境界ボックスは事前にキャッシュする必要があります。

本当にたくさんのオブジェクトがある場合は、スペース パーティショニングを使用することもできます...

または、3D の場合は凸包囲ポリゴン

于 2009-08-04T09:37:38.860 に答える
0

myFunctionオプション 2 のようにグローバル変数を使用するのではなく、Python ではパラメーターがオブジェクトによって渡されるという事実に依存できます。つまり、関数に渡されるオブジェクトは、リスト内のオブジェクトと同じオブジェクトです (この参照渡しとまったく同じではありませんが、十分に近いものです)。

したがって、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 に答える