programing

파이썬 목록 이해-반복되는 평가를 피하고 싶다

itsource 2021. 1. 14. 08:17
반응형

파이썬 목록 이해-반복되는 평가를 피하고 싶다


대략적인 목록 이해력이 있습니다.

[f(x) for x in l if f(x)]

여기서 l은 목록이고 f (x)는 목록을 반환하는 값 비싼 함수입니다.

f (x)가 비어 있지 않을 때마다 f (x)를 두 번 평가하는 것을 피하고 싶습니다. 목록 이해력 내에 출력을 저장하는 방법이 있습니까?

최종 조건을 제거하고 전체 목록을 생성 한 다음 정리할 수 있지만 이는 낭비적인 것 같습니다.

편집 :

두 가지 기본 접근 방식이 제안되었습니다.

내부 생성기 이해 :

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

또는 메모.

나는 내부 생성기 이해가 언급 된 문제에 대해 우아하다고 생각합니다. 사실 저는 명확하게하기 위해 질문을 단순화했습니다.

[g(x, f(x)) for x in l if f(x)]

이보다 복잡한 상황에서는 메모가 더 깨끗한 최종 결과를 낳는다 고 생각합니다.


해결책 (반복되는 x 값이있는 경우 가장 좋은 방법)은 함수 f 메모 하는 것입니다 . 즉, 함수가 호출되는 인수를 저장하고 저장하는 래퍼 함수를 ​​만들고 동일한 값이 요청되면 반환하는 것입니다. .

정말 간단한 구현은 다음과 같습니다.

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

그런 다음 목록 이해에서이 함수를 사용합니다. 이 접근 방식은 이론적 및 실제적 두 가지 조건에서 유효합니다. 첫 번째는 함수 f 가 결정적이어야한다는 것입니다. 즉, 동일한 입력이 주어지면 동일한 결과를 반환하고 다른 하나는 객체 x 를 사전 키로 사용할 수 있다는 것입니다. 첫 번째 것이 유효하지 않은 경우 정의에 따라 매번 f를 다시 계산해야하며 두 번째 것이 실패하면 약간 더 강력한 접근 방식을 사용할 수 있습니다.

인터넷에서 많은 메모 구현을 찾을 수 있으며 새 버전의 파이썬에도 포함되어 있다고 생각합니다.

참고로 작은 L을 변수 이름으로 사용하지 마십시오. 일부 터미널에서 i 또는 a 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]

할 것입니다.


메모 데코레이터를 사용해야합니다. 여기에 흥미로운 링크가 있습니다.


링크 및 '코드'의 메모 사용 :

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]

아니. 이를 수행하는 ( 깨끗한 ) 방법이 없습니다. 좋은 구식 루프에는 잘못된 것이 없습니다.

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

읽기 어려운 경우 언제든지 함수로 래핑 할 수 있습니다.


이전 답변에서 알 수 있듯이 이중 이해를 사용하거나 메모를 사용할 수 있습니다. 합리적인 크기의 문제는 취향의 문제입니다 (그리고 최적화를 숨기므로 메모가 더 깔끔해 보인다는 데 동의합니다). 그러나 매우 큰 목록을 검토하는 경우 에는 큰 차이가 있습니다. Memoization은 계산 한 모든 단일 값을 저장하고 빠르게 기억을 날려 버릴 수 있습니다. 생성기를 사용한 이중 이해 (대괄호가 아닌 둥근 괄호)는 유지하려는 것만 저장합니다.

실제 문제를 해결하려면 :

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

Again: this should be using a generator (round parens), not a list comprehension (square brackets). Otherwise you will build the whole list before you start filtering the results. This is the list comprehension version:

[g(x, y) for (x, y) in [ (x, f(x)) for x in series ] if y ] # DO NOT USE THIS

You can use memoization. It is a technique which is used in order to avoid doing the same computation twice by saving somewhere the result for each calculated value. I saw that there is already an answer that uses memoization, but I would like to propose a generic implementation, using python decorators:

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):
...

Now f is a memoized version of itself. With this implementation you can memoize any function using the @memoize decorator.


There have been a lot of answers regarding memoizing. The Python 3 standard library now has a lru_cache, which is a Last Recently Used Cache. So you can:

from functools import lru_cache

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

This way your function will only be called once. You can also specify the size of the lru_cache, by default this is 128. The problem with the memoize decorators shown above is that the size of the lists can grow well out of hand.


Use map() !!

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

f is the function f(X), l is the list

map() will return the result of f(x) for each x in the list.


Starting Python 3.8, and the introduction of assignment expressions (PEP 572) (:= operator), it's possible to use a local variable within a list comprehension in order to avoid calling twice the same function:

In our case, we can name the evaluation of f(x) as a variable y while using the result of the expression to filter the list but also as the mapped value:

[y for x in l if (y := f(x))]

Here is my solution:

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

How about defining:

def truths(L):
    """Return the elements of L that test true"""
    return [x for x in L if x]

So that, for example

> [wife.children for wife in henry8.wives]
[[Mary1], [Elizabeth1], [Edward6], [], [], []]

> truths(wife.children for wife in henry8.wives) 
[[Mary1], [Elizabeth1], [Edward6]]

ReferenceURL : https://stackoverflow.com/questions/15812779/python-list-comprehension-want-to-avoid-repeated-evaluation

반응형