Pemahaman daftar: Mengembalikan dua (atau lebih) item untuk setiap item

89

Apakah mungkin untuk mengembalikan 2 (atau lebih) item untuk setiap item dalam pemahaman daftar?

Yang saya inginkan (contoh):

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

harus kembali [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Jadi, sesuatu untuk menggantikan blok kode ini:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))
Hashmush
sumber
3
Karena penasaran, mengapa Anda ingin melakukan ini? Mungkin ada cara yang lebih baik untuk mencapai tujuan akhir Anda tanpa mencoba melakukannya dengan cara ini.
murgatroid99
3
Terutama karena saya suka pemrograman fungsional. Saya ingin memetakan daftar koordinat ke tupel koordinat layar untuk digunakan dengan fungsi pyglet.graphics.draw.
Hashmush

Jawaban:

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]

Waktu:

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

jamylak
sumber
4
Kode ini membuat tupel yang tidak perlu (f(x), g(x)). Bisa lebih baik ditulis sebagai: def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
khachik
1
Anda bahkan bisa menggeneralisasikannya dengan chain.from_iterable((func(x) for func in funcs) for x in range(n))). Yang pada akhirnya akan menghilangkan keluhan khachik. (Meskipun dalam arti tertentu, milik saya dan miliknya pada dasarnya sama dalam hal proses. Kami hanya mendefinisikan generator dalam secara berbeda.)
JAB
Ini lebih baik daripada sum(..., [])jawaban saya karena tidak perlu membuat ulang daftar pada setiap + (sehingga memiliki kinerja O (N) daripada kinerja O (N ^ 2)). Saya masih akan menggunakan sum(..., [])saat saya ingin satu baris cepat atau saya sedang terburu-buru, atau saat jumlah suku yang digabungkan dibatasi (mis. <= 10).
ninjagecko
@ Khachik Saya pikir ini akan lebih cepat tetapi saya akan mengatur waktu kedua metode sekarang, tupel dihasilkan sangat cepat dengan python.
jamylak
3
Jawaban ketiga, yang menghilang, terlihat seperti ini: [y for x in range(n) for y in (f(x), g(x))]Tapi ini mungkin lebih lambat. @jamylak Anda dapat menguji ini juga jika Anda mau.
Hashmush
118

Pemahaman daftar ganda:

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

Demo:

>>> 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]
ninjagecko
sumber
10
Ini bagus karena menunjukkan bahwa comp daftar ganda tidak begitu menakutkan: mereka hanya bersarang untuk loop yang ditulis seperti untuk loop . for x in range(5): for f in (f1, f2): newlist.append(f(x)). Saya dulu merasa mereka sedikit membingungkan karena saya terus mencoba membalikkan urutan.
DSM
1
Ini harus menjadi jawaban yang diterima, terima kasih, luar biasa!
Wingjam
@DSM kurasa, ini akan membingungkan selamanya.)
Winand
11
sum( ([f(x),g(x)] for x in range(n)), [] )

Ini sama dengan [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

Anda juga bisa menganggapnya sebagai:

def flatten(list):
    ...

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

Catatan: Cara yang benar adalah dengan menggunakan itertools.chain.from_iterableatau pemahaman daftar ganda. (Tidak perlu membuat ulang daftar pada setiap +, sehingga memiliki kinerja O (N) daripada kinerja O (N ^ 2).) Saya masih akan menggunakan sum(..., [])ketika saya ingin one-liner cepat atau saya sedang terburu-buru , atau ketika jumlah suku yang digabungkan dibatasi (misalnya <= 10). Itulah mengapa saya masih menyebutkannya di sini, dengan peringatan ini. Anda juga dapat menggunakan tupel: ((f(x),g(x)) for ...), ()(atau menurut komentar khachik, memiliki generator fg (x) yang menghasilkan dua tupel).

ninjagecko
sumber
@ArashThr: sedang berjalan[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...
ninjagecko
Bisakah Anda menjelaskan apa sebenarnya yang dilakukannya?
Rsh
Catatan: Ini memiliki runtime O (N ^ 2) sehingga bisa menjadi lambat pada daftar besar.
jamylak
1
@jamylak: ya, saya menyebutkan ini di jawaban Anda di komentar juga. =)
ninjagecko
Saya menganggap menyalahgunakan sum()cara ini sebagai antipattern, dan saya tidak melihat adanya pembenaran untuk menggunakannya dalam keadaan apa pun. Kode di jawaban Anda yang lain kurang mengetik, jadi bahkan alasan "ketika saya ingin satu baris cepat atau saya sedang terburu-buru" tidak benar-benar memotongnya.
Sven Marnach
2

Fungsi lambda ini menggabungkan dua daftar menjadi satu:

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

Contoh:

>>> 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]
Daniel Reis
sumber
2

Saya tahu OP sedang mencari solusi pemahaman daftar, tetapi saya ingin menawarkan penggunaan alternatif 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)))

yang sedikit lebih cepat daripada menggunakan pemahaman daftar ganda.

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)

Versi Python: 3.8.0

remykarem.dll
sumber
0

Solusi dengan menggunakan pengurangan :

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

Meskipun bukan pemahaman daftar, ini adalah cara fungsional untuk mendekati masalah. Pemahaman daftar pada dasarnya adalah cara lain untuk mapmengolah data, tetapi dalam kasus ini di mana pemetaan tidak satu lawan satu antara masukan dan keluaran, reducememungkinkan beberapa ruang gerak dengan bagaimana keluaran dapat dihasilkan.

Secara umum, setiap forimplementasi bentuk:

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

(Yaitu untuk loop yang dimaksudkan untuk menghasilkan efek samping pada daftar atau struktur data serupa)

Dapat direfraktorisasi menjadi map/reduce/filterimplementasi deklaratif .

0112
sumber