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

89

Можно ли вернуть 2 (или более) элемента для каждого элемента в понимании списка?

Что хочу (пример):

[f(x), g(x) for x in range(n)]

должен вернуться [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Итак, что-то для замены этого блока кода:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))
Хашмуш
источник
3
Из любопытства, почему вы хотите это сделать? Возможно, есть лучший способ достичь вашей конечной цели, не пытаясь сделать это таким образом.
murgatroid99 08
3
В основном потому, что мне нравится функциональное программирование. Я хочу сопоставить список координат с кортежем экранных координат для использования с функцией pyglet.graphics.draw.
Hashmush

Ответы:

52
>>> from itertools import chain
>>> f = lambda x: x + 2
>>> g = lambda x: x ** 2
>>> list(chain.from_iterable((f(x), g(x)) for x in range(3)))
[2, 0, 3, 1, 4, 4]

Сроки:

from timeit import timeit

f = lambda x: x + 2
g = lambda x: x ** 2

def fg(x):
    yield f(x)
    yield g(x)

print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='list(chain.from_iterable(fg(x) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='[func(x) for x in range(3) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2')


print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='list(chain.from_iterable(fg(x) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='[func(x) for x in xrange(10**6) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

2,69210777094

3,13900787874

1,62461071932

25,5944058287

29.2623711793

25,7211849286

Джамылак
источник
4
Этот код создает ненужные кортежи (f(x), g(x)). Может быть лучше записать в виде: def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
хачик 08
1
Вы даже можете обобщить это с помощью chain.from_iterable((func(x) for func in funcs) for x in range(n))). Что, кстати, сняло бы жалобу хачика. (Хотя в некотором смысле, мой и его, по сути, одинаковы с точки зрения процесса. Мы просто по-разному определяем внутренний генератор.)
JAB
Это лучше, чем мой sum(..., [])ответ, потому что не требуется воссоздавать список на каждом + (таким образом, имеет производительность O (N), а не производительность O (N ^ 2)). Я все равно буду использовать, sum(..., [])когда мне нужен быстрый однострочный текст, или я тороплюсь, или когда количество объединяемых терминов ограничено (например, <= 10).
ninjagecko
@khachik Я думаю, что это будет быстрее, но сейчас я проверю оба метода, хотя кортежи в python генерируются очень быстро.
jamylak 08
3
Третий ответ, который исчез, выглядел так: [y for x in range(n) for y in (f(x), g(x))]но, вероятно, это медленнее. @jamylak Вы тоже можете это проверить, если хотите.
Hashmush
118

Понимание двойного списка:

[f(x) for x in range(5) for f in (f1,f2)]

Демо:

>>> f1 = lambda x: x
>>> f2 = lambda x: 10*x

>>> [f(x) for x in range(5) for f in (f1,f2)]
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]
ниндзягеко
источник
10
Это приятно, потому что показывает, что составление двойных списков не так уж и страшно: они просто вложены в циклы, написанные так же, как циклы for . for x in range(5): for f in (f1, f2): newlist.append(f(x)). Раньше я находил их немного запутанными, потому что я все время пытался изменить порядок.
DSM
1
Это должен быть принятый ответ, спасибо, потрясающе!
Wingjam
@DSM, я думаю, это будет вечно сбивать с толку.)
Winand
11
sum( ([f(x),g(x)] for x in range(n)), [] )

Это эквивалентно [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

Вы также можете думать об этом как о:

def flatten(list):
    ...

flatten( [f(x),g(x)] for x in ... )

примечание: правильный способ - использовать itertools.chain.from_iterableили понимание двойного списка. (Это не требует повторного создания списка на каждом +, поэтому производительность O (N), а не O (N ^ 2).) Я все равно буду использовать, sum(..., [])когда мне нужен быстрый однострочный текст или я тороплюсь. , или когда количество объединяемых терминов ограничено (например, <= 10). Вот почему я до сих пор упоминаю об этом здесь с этой оговоркой. Вы также можете использовать кортежи: ((f(x),g(x)) for ...), ()(или для каждого комментария хачика, имея генератор fg (x), который дает двухкортеж).

ниндзягеко
источник
@ArashThr: это делает[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...
ninjagecko
Вы можете объяснить, что именно он делает?
Rsh 08
Примечание: у него время выполнения O (N ^ 2), поэтому он может быть медленным в огромных списках.
jamylak 08
1
@jamylak: да, я тоже упомянул об этом в вашем ответе в комментариях. =)
ninjagecko 08
Я считаю sum()такое злоупотребление антипаттерном и не вижу никакого оправдания его использованию при любых обстоятельствах. Код в вашем другом ответе меньше набирает, поэтому даже оправдание «когда мне нужен быстрый однострочный текст или я тороплюсь» на самом деле не помогает.
Свен Марнах 09
2

Эта лямбда-функция объединяет два списка в один:

zipped = lambda L1, L2: [L[i] 
                         for i in range(min(len(L1), len(L2))) 
                         for L in (L1, L2)]

Пример:

>>> f = [x for x in range(5)]
>>> g = [x*10 for x in range(5)]
>>> zipped(f, g)
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]
Даниэль Рейс
источник
2

Я знаю, что OP ищет решение для понимания списка, но я хотел бы предложить альтернативное использование list.extend().

f = lambda x: x
g = lambda x: 10*x

result = []
extend = result.extend
for x in range(5):
    extend((f(x),g(x)))

что немного быстрее, чем использование двойного списка.

nums = range(100000)

def double_comprehension():
    return [func(x) for x in nums for func in (f,g)]

def list_extend():
    result = []
    extend = result.extend
    for x in nums:
        extend((f(x),g(x)))
    return result

%timeit -n100 double_comprehension()
23.4 ms ± 67 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit -n100 list_extend()
20.5 ms ± 213 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Версия Python: 3.8.0

Ремыкарем
источник
0

Решение с использованием reduce :

from functools import reduce

f    = lambda x: f"f({x})" ## Just for example
g    = lambda x: f"g({x})"
data = [1, 2, 3]

reduce(lambda acc, x: acc + [f(x), g(x)], data, [])
# => ['f(1)', 'g(1)', 'f(2)', 'g(2)', 'f(3)', 'g(3)']

Хотя это и не понимание списка, это функциональный способ решения проблемы. Понимание списка - это, по сути, еще один способ обработки mapданных, но в этом случае, когда отображение не однозначно между входом и выходом, reduceостается некоторое пространство для маневра с тем, как может быть сгенерирован выход.

В общем, любая forреализация формы:

result = []
for n in some_data:
  result += some_operation()
  ## etc.

(Т.е. для циклов, предназначенных для создания побочного эффекта для списка или аналогичной структуры данных)

Может быть преобразован в декларативную map/reduce/filterреализацию.

0112
источник