私は以下に近似するリスト内包を持っています:
[f(x) for x in l if f(x)]
ここで、lはリストであり、f(x)はリストを返す高価な関数です。
空でないf(x)の出現ごとにf(x)を2回評価することを避けたいと思います。出力をリスト内包に保存する方法はありますか?
最終的な条件を削除してリスト全体を生成し、それをプルーニングすることもできますが、それは無駄に思えます。
編集:
2つの基本的なアプローチが提案されています。
内部ジェネレーターの理解:
[y for y in (f(x) for x in l) if y]
またはメモ化。
述べたように、内部ジェネレーターの理解は問題に対してエレガントだと思います。実際、私は明確にするために質問を簡略化しました。
[g(x, f(x)) for x in l if f(x)]
このより複雑な状況では、メモ化によってよりクリーンな最終結果が得られると思います。
解決策(xの値が繰り返されている場合に最適)はmemoize関数fです。つまり、関数が呼び出される引数を保存して保存するラッパー関数を作成し、returnします。同じ値が要求された場合。
本当に簡単な実装は次のとおりです。
storage = {}
def memoized(value):
if value not in storage:
storage[value] = f(value)
return storage[value]
[memoized(x) for x in l if memoized(x)]
次に、この関数をリスト内包で使用します。このアプローチは、理論的なものと実用的なものの2つの条件下で有効です。 1つ目は、関数fが確定的でなければならない、つまり同じ入力を与えられた場合に同じ結果を返すこと、もう1つはオブジェクトxを辞書として使用できることです。キー。最初のものが有効でない場合は、timebyの定義ごとにfを再計算する必要がありますが、2つ目が失敗した場合は、少し堅牢な方法を使用できます。
ネット上のメモ化の実装はたくさんあります。pythonの新しいバージョンにも何かが含まれていると思います。
余談ですが、小さなLを変数名として使用しないでください。一部の端末ではiまたは1と混同される可能性があるため、これは悪い習慣です。
編集:
コメントされているように、ジェネレーターの理解を使用して可能な解決策は(無用な重複する一時ファイルの作成を回避するため)、次の式になります。
[g(x, fx) for x, fx in ((x,f(x)) for x in l) if fx]
Fの計算コスト、元のリストの重複数、処理後のメモリを考慮して、選択に重みを付ける必要があります。メモ化は、スペースと速度のトレードオフをもたらします。つまり、各結果を追跡して保存します。そのため、巨大なリストがある場合、メモリの占有率が高くなる可能性があります。
[y for y in (f(x) for x in l) if y]
しましょう。
メモ化デコレータを使用する必要があります。ここに興味深い link があります。
リンクと「コード」からメモを使用する:
def memoize(f):
""" Memoization decorator for functions taking one or more arguments. """
class memodict(dict):
def __init__(self, f):
self.f = f
def __call__(self, *args):
return self[args]
def __missing__(self, key):
ret = self[key] = self.f(*key)
return ret
return memodict(f)
@memoize
def f(x):
# your code
[f(x) for x in l if f(x)]
[y for y in [f(x) for x in l] if y]
更新された問題の場合、これは役立つ場合があります。
[g(x,y) for x in l for y in [f(x)] if y]
以前の回答が示したように、二重理解またはメモを使用できます。適度なサイズの問題の場合、それは好みの問題です(そして、最適化が隠されているため、メモ化はよりきれいに見えることに同意します)。しかし、非常に大きなリストを調べている場合、大きな違いがあります:メモ化は、計算したすべての値を保存し、すぐにメモリを破壊します。二重内包ジェネレータ付き(角括弧ではなく丸括弧)は、保持したいものだけを保存します。
あなたの実際の問題に来るには:
_[g(x, f(x)) for x in series if f(x)]
_
最終的な値を計算するには、x
とf(x)
の両方が必要です。問題ありません。両方とも次のように渡します。
_[g(x, y) for (x, y) in ( (x, f(x)) for x in series ) if y ]
_
再度:これは、リスト内包表記(角括弧)ではなく、ジェネレーター(丸括弧)を使用する必要があります。それ以外の場合、結果のフィルタリングを開始する前に、willリスト全体を作成します。これはリスト内包版です:
_[g(x, y) for (x, y) in [ (x, f(x)) for x in series ] if y ] # DO NOT USE THIS
_
いいえ。これを行う(clean)方法はありません。古き良きループに問題はありません。
output = []
for x in l:
result = f(x)
if result:
output.append(result)
読みにくい場合は、いつでも関数にラップできます。
メモ化に関しては多くの回答がありました。 Python 3標準ライブラリにlru_cache
が追加されました最後に使用されたキャッシュです。したがって、次のことができます。
from functools import lru_cache
@lru_cache()
def f(x):
# function body here
このようにして、関数は一度だけ呼び出されます。 lru_cache
のサイズを指定することもできます。デフォルトでは128です。上記のメモ化デコレータの問題は、リストのサイズが手に負えなくなることです。
map()
を使用してください!!
_comp = [x for x in map(f, l) if x]
_
f
は関数f(X)
、l
はリストです
map()
は、リスト内のxごとにf(x)
の結果を返します。
memoization を使用できます。これは、計算された各値の結果をどこかに保存することにより、同じ計算を2回行うことを回避するために使用される手法です。メモ化を使用する回答がすでにあることを確認しましたが、pythonデコレータを使用して、一般的な実装を提案します:
def memoize(func):
def wrapper(*args):
if args in wrapper.d:
return wrapper.d[args]
ret_val = func(*args)
wrapper.d[args] = ret_val
return ret_val
wrapper.d = {}
return wrapper
@memoize
def f(x):
...
現在、f
はそれ自体のメモ化されたバージョンです。この実装では、@memoize
デコレータを使用して任意の関数をメモできます。
_Python 3.8
_の開始、および 代入式(PEP 572) (_:=
_演算子)の導入により、2回の呼び出しを回避するためにリスト内包内でローカル変数を使用することが可能になりました。同じ機能:
この場合、式の結果を使用してリストをフィルタリングする一方で、マップされた値として、f(x)
の評価を変数y
として指定できます。
_[y for x in l if (y := f(x))]
_
これが私の解決策です:
filter(None, [f(x) for x in l])