Понимание списка 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]]