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


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

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))).
хачик

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

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 x in range(5): for f in (f1, f2): newlist.append(f(x)). Раніше я вважав їх трохи заплутаними, бо постійно намагався інвертувати порядок.
DSM

1
Це повинна бути прийнята відповідь, дякую, дивовижно!
Wingjam

@DSM, я думаю, це буде незрозуміло назавжди.)
Вінанд,

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

Примітка: Це має час виконання O (N ^ 2), тому у величезних списках це може бути повільним.
jamylak

1
@jamylak: так, я також згадав про це у вашій відповіді в коментарях. =)
ninjagecko

Я вважаю, що зловживання sum()таким чином є протиобразцем, і я не бачу жодного виправдання використовувати його за будь-яких обставин. Код у вашій іншій відповіді менше набирає текст, тому навіть виправдання "коли я хочу швидкий однокласник або я поспішаю" насправді це не вирізає.
Sven Marnach

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

Рішення з використанням зменшення :

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реалізацію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.