Kanonik cartesian_product
(hampir)
Ada banyak pendekatan untuk masalah ini dengan properti yang berbeda. Beberapa lebih cepat dari yang lain, dan beberapa lebih untuk tujuan umum. Setelah banyak pengujian dan penyesuaian, saya menemukan bahwa fungsi berikut, yang menghitung dimensi-n cartesian_product
, lebih cepat daripada kebanyakan yang lain untuk banyak input. Untuk sepasang pendekatan yang sedikit lebih kompleks, tetapi bahkan sedikit lebih cepat dalam banyak kasus, lihat jawabannya oleh Paul Panzer .
Mengingat jawaban itu, ini bukan lagi implementasi tercepat dari produk cartesian numpy
yang saya sadari. Namun, saya pikir kesederhanaannya akan terus menjadikannya tolok ukur yang berguna untuk peningkatan di masa depan:
def cartesian_product(*arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[...,i] = a
return arr.reshape(-1, la)
Perlu disebutkan bahwa fungsi ini digunakan ix_
dengan cara yang tidak biasa; sedangkan penggunaan terdokumentasi ix_
adalah untuk menghasilkan indeks ke dalam array, kebetulan bahwa array dengan bentuk yang sama dapat digunakan untuk penugasan yang disiarkan. Terima kasih banyak kepada mgilson , yang mengilhami saya untuk mencoba menggunakan ix_
cara ini, dan kepada unutbu , yang memberikan umpan balik yang sangat membantu pada jawaban ini, termasuk saran untuk menggunakannumpy.result_type
.
Alternatif penting
Terkadang lebih cepat untuk menulis blok memori yang berdekatan dalam urutan Fortran. Itulah dasar dari alternatif ini cartesian_product_transpose
,, yang telah terbukti lebih cepat pada beberapa perangkat keras daripada cartesian_product
(lihat di bawah). Namun, jawaban Paul Panzer, yang menggunakan prinsip yang sama, bahkan lebih cepat. Namun, saya memasukkan ini di sini untuk pembaca yang tertarik:
def cartesian_product_transpose(*arrays):
broadcastable = numpy.ix_(*arrays)
broadcasted = numpy.broadcast_arrays(*broadcastable)
rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
dtype = numpy.result_type(*arrays)
out = numpy.empty(rows * cols, dtype=dtype)
start, end = 0, rows
for a in broadcasted:
out[start:end] = a.reshape(-1)
start, end = end, end + rows
return out.reshape(cols, rows).T
Setelah memahami pendekatan Panzer, saya menulis versi baru yang hampir secepat miliknya, dan hampir sesederhana cartesian_product
:
def cartesian_product_simple_transpose(arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty([la] + [len(a) for a in arrays], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[i, ...] = a
return arr.reshape(la, -1).T
Ini tampaknya memiliki beberapa overhead waktu konstan yang membuatnya berjalan lebih lambat daripada Panzer untuk input kecil. Tetapi untuk input yang lebih besar, dalam semua tes yang saya jalankan, performanya sama baiknya dengan implementasi tercepatnya (cartesian_product_transpose_pp
).
Di bagian berikut, saya menyertakan beberapa tes alternatif lain. Ini sekarang agak ketinggalan zaman, tetapi daripada upaya duplikat, saya telah memutuskan untuk meninggalkan mereka di sini karena kepentingan sejarah. Untuk tes terbaru, lihat jawaban Panzer, serta Nico Schlömer .
Tes terhadap alternatif
Berikut ini adalah serangkaian tes yang menunjukkan peningkatan kinerja yang disediakan beberapa fungsi ini relatif terhadap sejumlah alternatif. Semua tes yang ditunjukkan di sini dilakukan pada mesin quad-core, menjalankan Mac OS 10.12.5, Python 3.6.1, dannumpy
1.12.1. Variasi pada perangkat keras dan lunak diketahui menghasilkan hasil yang berbeda, demikian YMMV. Jalankan tes ini untuk diri Anda sendiri untuk memastikan!
Definisi:
import numpy
import itertools
from functools import reduce
### Two-dimensional products ###
def repeat_product(x, y):
return numpy.transpose([numpy.tile(x, len(y)),
numpy.repeat(y, len(x))])
def dstack_product(x, y):
return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
### Generalized N-dimensional products ###
def cartesian_product(*arrays):
la = len(arrays)
dtype = numpy.result_type(*arrays)
arr = numpy.empty([len(a) for a in arrays] + [la], dtype=dtype)
for i, a in enumerate(numpy.ix_(*arrays)):
arr[...,i] = a
return arr.reshape(-1, la)
def cartesian_product_transpose(*arrays):
broadcastable = numpy.ix_(*arrays)
broadcasted = numpy.broadcast_arrays(*broadcastable)
rows, cols = numpy.prod(broadcasted[0].shape), len(broadcasted)
dtype = numpy.result_type(*arrays)
out = numpy.empty(rows * cols, dtype=dtype)
start, end = 0, rows
for a in broadcasted:
out[start:end] = a.reshape(-1)
start, end = end, end + rows
return out.reshape(cols, rows).T
# from https://stackoverflow.com/a/1235363/577088
def cartesian_product_recursive(*arrays, out=None):
arrays = [numpy.asarray(x) for x in arrays]
dtype = arrays[0].dtype
n = numpy.prod([x.size for x in arrays])
if out is None:
out = numpy.zeros([n, len(arrays)], dtype=dtype)
m = n // arrays[0].size
out[:,0] = numpy.repeat(arrays[0], m)
if arrays[1:]:
cartesian_product_recursive(arrays[1:], out=out[0:m,1:])
for j in range(1, arrays[0].size):
out[j*m:(j+1)*m,1:] = out[0:m,1:]
return out
def cartesian_product_itertools(*arrays):
return numpy.array(list(itertools.product(*arrays)))
### Test code ###
name_func = [('repeat_product',
repeat_product),
('dstack_product',
dstack_product),
('cartesian_product',
cartesian_product),
('cartesian_product_transpose',
cartesian_product_transpose),
('cartesian_product_recursive',
cartesian_product_recursive),
('cartesian_product_itertools',
cartesian_product_itertools)]
def test(in_arrays, test_funcs):
global func
global arrays
arrays = in_arrays
for name, func in test_funcs:
print('{}:'.format(name))
%timeit func(*arrays)
def test_all(*in_arrays):
test(in_arrays, name_func)
# `cartesian_product_recursive` throws an
# unexpected error when used on more than
# two input arrays, so for now I've removed
# it from these tests.
def test_cartesian(*in_arrays):
test(in_arrays, name_func[2:4] + name_func[-1:])
x10 = [numpy.arange(10)]
x50 = [numpy.arange(50)]
x100 = [numpy.arange(100)]
x500 = [numpy.arange(500)]
x1000 = [numpy.arange(1000)]
Hasil tes:
In [2]: test_all(*(x100 * 2))
repeat_product:
67.5 µs ± 633 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
dstack_product:
67.7 µs ± 1.09 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product:
33.4 µs ± 558 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_transpose:
67.7 µs ± 932 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
cartesian_product_recursive:
215 µs ± 6.01 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_itertools:
3.65 ms ± 38.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [3]: test_all(*(x500 * 2))
repeat_product:
1.31 ms ± 9.28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
dstack_product:
1.27 ms ± 7.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product:
375 µs ± 4.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_transpose:
488 µs ± 8.88 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
cartesian_product_recursive:
2.21 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
105 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [4]: test_all(*(x1000 * 2))
repeat_product:
10.2 ms ± 132 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
dstack_product:
12 ms ± 120 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product:
4.75 ms ± 57.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.76 ms ± 52.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_recursive:
13 ms ± 209 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
422 ms ± 7.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Dalam semua kasus, cartesian_product
sebagaimana didefinisikan pada awal jawaban ini adalah yang tercepat.
Untuk fungsi-fungsi yang menerima jumlah array input yang sewenang-wenang, ada baiknya memeriksa kinerja len(arrays) > 2
juga. (Sampai saya dapat menentukan mengapa cartesian_product_recursive
melempar kesalahan dalam kasus ini, saya telah menghapusnya dari tes ini.)
In [5]: test_cartesian(*(x100 * 3))
cartesian_product:
8.8 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_transpose:
7.87 ms ± 91.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
518 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [6]: test_cartesian(*(x50 * 4))
cartesian_product:
169 ms ± 5.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
184 ms ± 4.32 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_itertools:
3.69 s ± 73.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [7]: test_cartesian(*(x10 * 6))
cartesian_product:
26.5 ms ± 449 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
cartesian_product_transpose:
16 ms ± 133 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
cartesian_product_itertools:
728 ms ± 16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [8]: test_cartesian(*(x10 * 7))
cartesian_product:
650 ms ± 8.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_transpose:
518 ms ± 7.09 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
cartesian_product_itertools:
8.13 s ± 122 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Seperti yang ditunjukkan oleh tes ini, cartesian_product
tetap kompetitif hingga jumlah array input naik di atas (kira-kira) empat. Setelah itu, cartesian_product_transpose
memang ada sedikit keunggulan.
Perlu ditegaskan kembali bahwa pengguna dengan perangkat keras dan sistem operasi lain mungkin melihat hasil yang berbeda. Misalnya, unutbu melaporkan melihat hasil berikut untuk pengujian ini menggunakan Ubuntu 14.04, Python 3.4.3, dan numpy
1.14.0.dev0 + b7050a9:
>>> %timeit cartesian_product_transpose(x500, y500)
1000 loops, best of 3: 682 µs per loop
>>> %timeit cartesian_product(x500, y500)
1000 loops, best of 3: 1.55 ms per loop
Di bawah ini, saya masuk ke beberapa detail tentang tes sebelumnya yang saya jalankan di sepanjang garis ini. Kinerja relatif dari pendekatan ini telah berubah dari waktu ke waktu, untuk berbagai perangkat keras dan versi Python dan numpy
. Meskipun tidak segera bermanfaat bagi orang yang menggunakan versi terbarunumpy
, ini menggambarkan bagaimana banyak hal telah berubah sejak versi pertama dari jawaban ini.
Alternatif sederhana: meshgrid
+dstack
Jawaban yang saat ini diterima menggunakan tile
dan repeat
untuk menyiarkan dua array bersama-sama. Tetapi meshgrid
fungsinya praktis melakukan hal yang sama. Inilah output dari tile
dan repeat
sebelum diteruskan ke transpose:
In [1]: import numpy
In [2]: x = numpy.array([1,2,3])
...: y = numpy.array([4,5])
...:
In [3]: [numpy.tile(x, len(y)), numpy.repeat(y, len(x))]
Out[3]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]
Dan inilah output dari meshgrid
:
In [4]: numpy.meshgrid(x, y)
Out[4]:
[array([[1, 2, 3],
[1, 2, 3]]), array([[4, 4, 4],
[5, 5, 5]])]
Seperti yang Anda lihat, ini hampir identik. Kita hanya perlu membentuk ulang hasilnya untuk mendapatkan hasil yang persis sama.
In [5]: xt, xr = numpy.meshgrid(x, y)
...: [xt.ravel(), xr.ravel()]
Out[5]: [array([1, 2, 3, 1, 2, 3]), array([4, 4, 4, 5, 5, 5])]
Namun, alih-alih membentuk kembali pada titik ini, kita dapat meneruskan output dari meshgrid
ke dstack
dan membentuk kembali setelahnya, yang menghemat beberapa pekerjaan:
In [6]: numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
Out[6]:
array([[1, 4],
[2, 4],
[3, 4],
[1, 5],
[2, 5],
[3, 5]])
Bertentangan dengan klaim dalam komentar ini , saya tidak melihat bukti bahwa input yang berbeda akan menghasilkan output yang berbeda bentuk, dan seperti ditunjukkan di atas, mereka melakukan hal yang sangat mirip, sehingga akan sangat aneh jika mereka melakukannya. Harap beri tahu saya jika Anda menemukan contoh tandingan.
Menguji meshgrid
+ dstack
vs. repeat
+transpose
Kinerja relatif dari kedua pendekatan ini telah berubah seiring waktu. Dalam versi Python sebelumnya (2.7), hasil menggunakan meshgrid
+ dstack
terasa lebih cepat untuk input kecil. (Perhatikan bahwa tes ini berasal dari versi lama dari jawaban ini.) Definisi:
>>> def repeat_product(x, y):
... return numpy.transpose([numpy.tile(x, len(y)),
numpy.repeat(y, len(x))])
...
>>> def dstack_product(x, y):
... return numpy.dstack(numpy.meshgrid(x, y)).reshape(-1, 2)
...
Untuk input berukuran sedang, saya melihat peningkatan yang signifikan. Tapi saya mencoba kembali tes ini dengan versi Python (3.6.1) dan numpy
(1.12.1) yang lebih baru, pada mesin yang lebih baru. Kedua pendekatan itu hampir identik sekarang.
Tes Lama
>>> x, y = numpy.arange(500), numpy.arange(500)
>>> %timeit repeat_product(x, y)
10 loops, best of 3: 62 ms per loop
>>> %timeit dstack_product(x, y)
100 loops, best of 3: 12.2 ms per loop
Tes Baru
In [7]: x, y = numpy.arange(500), numpy.arange(500)
In [8]: %timeit repeat_product(x, y)
1.32 ms ± 24.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [9]: %timeit dstack_product(x, y)
1.26 ms ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Seperti biasa, YMMV, tetapi ini menunjukkan bahwa dalam versi terbaru Python dan numpy, ini dapat dipertukarkan.
Fungsi produk umum
Secara umum, kita mungkin berharap bahwa menggunakan fungsi bawaan akan lebih cepat untuk input kecil, sedangkan untuk input besar, fungsi yang dibangun khusus mungkin lebih cepat. Selanjutnya untuk produk n-dimensi umum, tile
danrepeat
tidak akan membantu, karena mereka tidak memiliki analog dimensi yang lebih tinggi. Jadi ada baiknya menyelidiki perilaku fungsi yang dibangun juga.
Sebagian besar tes yang relevan muncul di awal jawaban ini, tetapi di sini ada beberapa tes yang dilakukan pada versi Python sebelumnya dan numpy
untuk perbandingan.
The cartesian
fungsi yang didefinisikan dalam jawaban lain digunakan untuk melakukan cukup baik untuk input yang lebih besar. (Ini sama dengan fungsi yang disebut cartesian_product_recursive
di atas.) Dalam rangka untuk membandingkan cartesian
untuk dstack_prodct
, kita menggunakan hanya dua dimensi.
Di sini sekali lagi, tes lama menunjukkan perbedaan yang signifikan, sedangkan tes baru menunjukkan hampir tidak ada.
Tes Lama
>>> x, y = numpy.arange(1000), numpy.arange(1000)
>>> %timeit cartesian([x, y])
10 loops, best of 3: 25.4 ms per loop
>>> %timeit dstack_product(x, y)
10 loops, best of 3: 66.6 ms per loop
Tes Baru
In [10]: x, y = numpy.arange(1000), numpy.arange(1000)
In [11]: %timeit cartesian([x, y])
12.1 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [12]: %timeit dstack_product(x, y)
12.7 ms ± 334 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Seperti sebelumnya, dstack_product
masih berdetak cartesian
pada skala yang lebih kecil.
Tes Baru ( tes lama yang berlebihan tidak ditampilkan )
In [13]: x, y = numpy.arange(100), numpy.arange(100)
In [14]: %timeit cartesian([x, y])
215 µs ± 4.75 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [15]: %timeit dstack_product(x, y)
65.7 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Menurut saya, perbedaan-perbedaan ini menarik dan layak dicatat; tetapi pada akhirnya mereka akademis. Seperti yang ditunjukkan oleh tes pada awal jawaban ini, semua versi ini hampir selalu lebih lambat dari yang cartesian_product
didefinisikan pada awal jawaban ini - yang sedikit lebih lambat daripada implementasi tercepat di antara jawaban untuk pertanyaan ini.