web-dev-qa-db-ja.com

Pythonリスト内包表記-繰り返し評価を避けたい

私は以下に近似するリスト内包を持っています:

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

このより複雑な状況では、メモ化によってよりクリーンな最終結果が得られると思います。

57
Stefan

解決策(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の計算コスト、元のリストの重複数、処理後のメモリを考慮して、選択に重みを付ける必要があります。メモ化は、スペースと速度のトレードオフをもたらします。つまり、各結果を追跡して保存します。そのため、巨大なリストがある場合、メモリの占有率が高くなる可能性があります。

11
EnricoGiampieri
[y for y in (f(x) for x in l) if y]

しましょう。

43
RobertT

メモ化デコレータを使用する必要があります。ここに興味深い 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)]
11
Inbar Rose
[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]
9
Vaughn Cato

以前の回答が示したように、二重理解またはメモを使用できます。適度なサイズの問題の場合、それは好みの問題です(そして、最適化が隠されているため、メモ化はよりきれいに見えることに同意します)。しかし、非常に大きなリストを調べている場合、大きな違いがあります:メモ化は、計算したすべての値を保存し、すぐにメモリを破壊します。二重内包ジェネレータ付き(角括弧ではなく丸括弧)は、保持したいものだけを保存します。

あなたの実際の問題に来るには:

_[g(x, f(x)) for x in series if f(x)]
_

最終的な値を計算するには、xf(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
_
8
alexis

いいえ。これを行う(clean)方法はありません。古き良きループに問題はありません。

output = []
for x in l:
    result = f(x)
    if result: 
        output.append(result)

読みにくい場合は、いつでも関数にラップできます。

8
mgilson

メモ化に関しては多くの回答がありました。 Python 3標準ライブラリにlru_cacheが追加されました最後に使用されたキャッシュです。したがって、次のことができます。

from functools import lru_cache

@lru_cache()
def f(x):
    # function body here

このようにして、関数は一度だけ呼び出されます。 lru_cacheのサイズを指定することもできます。デフォルトでは128です。上記のメモ化デコレータの問題は、リストのサイズが手に負えなくなることです。

3
Games Brainiac

map()を使用してください!!

_comp = [x for x in map(f, l) if x]
_

fは関数f(X)lはリストです

map()は、リスト内のxごとにf(x)の結果を返します。

3

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デコレータを使用して任意の関数をメモできます。

3
ehudt

_Python 3.8_の開始、および 代入式(PEP 572) (_:=_演算子)の導入により、2回の呼び出しを回避するためにリスト内包内でローカル変数を使用することが可能になりました。同じ機能:

この場合、式の結果を使用してリストをフィルタリングする一方で、マップされた値として、f(x)の評価を変数yとして指定できます。

_[y for x in l if (y := f(x))]
_
2
Xavier Guihot

これが私の解決策です:

filter(None, [f(x) for x in l])
1
Eastsun