Понимание списка Python - хотите избежать повторной оценки

У меня есть список, который приближается к:

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

Где l - список, а f (x) - дорогая функция, которая возвращает список.

Я хочу избежать оценки f (x) дважды для каждого непустого заполнения f (x). Есть ли способ сохранить свой результат в понимании списка?

Я мог бы удалить окончательное условие, сгенерировать весь список и затем обрезать его, но это кажется расточительным.

Edit

Были предложены два основных подхода:

Внутреннее понимание генератора:

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

или memoization.

Я думаю, что внутреннее понимание генератора элегантно для проблемы, как указано. На самом деле я упростил вопрос, чтобы дать понять, я действительно хочу:

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

Для этой более сложной ситуации, я думаю, memoization дает более чистый конечный результат.

Ответ 1

Решение (лучшее, если у вас есть повторяющееся значение x) будет memoize функция 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 каждый раз по определению, а если второй не удается, можно использовать несколько более надежные подходы.

Вы можете найти много реализаций memoization в сети, и я думаю, что в новых версиях python есть что-то, что включено в них.

На боковой ноте никогда не используйте малый L в качестве имени переменной, это плохая привычка, так как его можно путать с я или 1 на некоторых терминалах.

EDIT:

как прокомментировано, возможным решением с использованием генераторов (чтобы избежать создания бесполезных дублированных времен) было бы это выражение:

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

Вам нужно весить свой выбор, учитывая вычислительную стоимость f, количество дубликатов в исходном списке и память при вашем расположении. Воспоминание делает компромисс между космическими скоростями, что означает, что он сохраняет следы каждого результата, сохраняя его, поэтому, если у вас есть огромные списки, это может стать дорогостоящим на фронте памяти.

Ответ 2

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

Сделаю.

Ответ 3

Вы должны использовать декоратор memoize. Вот интересная ссылка.


Использование memoization из ссылки и вашего 'кода':

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

Ответ 4

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

Ответ 5

Неа. Там нет (чистого) способа сделать это. Нет ничего плохого в хорошем старомодном цикле:

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

Если вы считаете, что трудно читать, вы всегда можете обернуть его в функцию.

Ответ 6

Как показали предыдущие ответы, вы можете использовать двойное понимание или использовать memoization. Для проблем с разумным размером это вопрос вкуса (и я согласен с тем, что memoization выглядит более чистым, поскольку он скрывает оптимизацию). Но если вы изучаете очень большой список, существует огромная разница: Memoization будет хранить каждое вычисленное значение и может быстро сдуть вашу память. Двойное понимание с генератором (круглые парсеры, а не квадратные скобки) сохраняет только то, что вы хотите сохранить.

Чтобы перейти к вашей фактической проблеме:

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

Снова: следует использовать генератор (round parens), а не список (квадратные скобки). В противном случае вы создадите весь список, прежде чем приступать к фильтрации результатов. Это версия для понимания списка:

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

Ответ 7

Вы можете использовать memoization. Это метод, который используется для того, чтобы избежать выполнения одного и того же вычисления дважды, сохраняя где-то результат для каждого рассчитанного значения. Я видел, что уже есть ответ, который использует memoization, но я хотел бы предложить общую реализацию, используя декораторы 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 является memoized версией самой. С помощью этой реализации вы можете memoize любую функцию, используя декоратор @memoize.

Ответ 8

Было много ответов на memoizing. Стандартная библиотека Python 3 теперь имеет lru_cache, который является последним использованным кэшем. Таким образом, вы можете:

from functools import lru_cache

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

Таким образом, ваша функция будет вызываться только один раз. Вы также можете указать размер lru_cache, по умолчанию это 128. Проблема с декораторами memoize, показанная выше, заключается в том, что размер списков может значительно увеличиться.

Ответ 9

Используйте map()!!

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

f - это функция f(X), l - это список

map() вернет результат f(X) для каждого x в списке.

Ответ 10

Вот мое решение:

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

Ответ 11

Начиная с Python 3.8 и введением выражений присваивания (PEP 572) (:= оператор), можно использовать локальную переменную в пределах понимания списка, чтобы избежать вызова дважды одной и той же функции:

В нашем случае мы можем назвать оценку f(x) как переменную y, используя результат выражения для фильтрации списка, но также и как отображенное значение:

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

Ответ 12

Как насчет определения:

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

Так, например,

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

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