Standart cartesian_product
(neredeyse)
Bu soruna farklı özelliklere sahip birçok yaklaşım vardır. Bazıları diğerlerinden daha hızlı, bazıları ise daha genel amaçlıdır. Çok fazla test ve ayar yaptıktan sonra, bir n-boyutlu hesaplayan aşağıdaki fonksiyonun cartesian_product
birçok girdi için diğerlerinden daha hızlı olduğunu gördüm . Biraz daha karmaşık, ancak çoğu durumda biraz daha hızlı olan bir çift yaklaşım için Paul Panzer'in cevabına bakın .
Bu cevap göz önüne alındığında, artık bildiğim kartezyen ürünün en hızlı uygulaması değil numpy
. Ancak, basitliğinin gelecekteki iyileştirmeler için yararlı bir kriter haline getirmeye devam edeceğini düşünüyorum:
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)
Bu işlevin ix_
alışılmadık bir şekilde kullanıldığını belirtmek gerekir ; belgelenmiş kullanımı ise ix_
olan endeksler oluşturmak içine bir dizi, sadece çok aynı şekle sahip diziler yayınlanan atama için kullanılabilir olur. Bu şekilde kullanmayı denemem için bana ilham veren mgilson'a ve bu öneriyi kullanma önerisi de dahil olmak üzere son derece yararlı geri bildirim sağlayan unutbu'ya çok teşekkürler .ix_
numpy.result_type
Dikkate değer alternatifler
Bazen Fortran düzeninde bitişik bellek blokları yazmak daha hızlıdır. cartesian_product_transpose
Bazı donanımlarda daha hızlı olduğu kanıtlanan bu alternatifin temeli budur cartesian_product
(aşağıya bakınız). Ancak Paul Panzer'in aynı prensibi kullanan cevabı daha da hızlı. Yine de, bunu ilgilenen okuyucular için buraya ekliyorum:
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
Panzer'in yaklaşımını anlamaya başladıktan sonra, neredeyse onun kadar hızlı ve neredeyse basit olan yeni bir sürüm yazdım 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
Bu, sabit girişler için küçük girişler için Panzer'lerden daha yavaş çalışmasını sağlıyor. Ancak daha büyük girdiler için, koştuğum tüm testlerde, en hızlı uygulama kadar iyi performans sergiliyor ( cartesian_product_transpose_pp
).
İlerleyen bölümlerde, diğer alternatiflerin bazı testlerini de ekliyorum. Bunlar şimdi biraz güncel değil, ancak yinelenen çabalardan ziyade, onları tarihsel çıkarların dışında bırakmaya karar verdim. Güncel testler için Panzer'in yanı sıra Nico Schlömer'in cevabına bakınız .
Alternatiflere karşı testler
İşte bu işlevlerin bazılarının bir dizi alternatife göre sağladığı performans artışını gösteren bir test pili. Burada gösterilen tüm testler, Mac OS 10.12.5, Python 3.6.1 ve numpy
1.12.1 çalıştıran dört çekirdekli bir makinede gerçekleştirildi . Donanım ve yazılımdaki varyasyonların farklı sonuçlar verdiği bilinmektedir, bu nedenle YMMV. Emin olmak için bu testleri yapın!
Tanımlar:
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)]
Test sonuçları:
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)
Her durumda, cartesian_product
bu cevabın başında tanımlandığı gibi en hızlıdır.
Rastgele sayıda giriş dizisini kabul eden işlevler için, performansı da denetlemeye değer len(arrays) > 2
. ( cartesian_product_recursive
Bu durumda neden bir hata atılacağını belirleyene kadar , bu testlerden kaldırdım.)
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)
Bu testlerin gösterdiği gibi, cartesian_product
girdi dizisi sayısı dördün üzerine (kabaca) yükselinceye kadar rekabetçi kalır. Bundan sonra, cartesian_product_transpose
hafif bir kenarı var.
Diğer donanım ve işletim sistemlerine sahip kullanıcıların farklı sonuçlar görebileceğini yinelemeye değer. Örneğin unutbu, Ubuntu numpy
14.04 , Python 3.4.3 ve 1.14.0.dev0 + b7050a9 kullanarak bu testler için aşağıdaki sonuçları gördüğünü bildiriyor:
>>> %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
Aşağıda, bu çizgiler boyunca yürüttüğüm önceki testler hakkında birkaç ayrıntıya giriyorum. Bu yaklaşımların göreceli performansı, farklı donanım ve farklı Python ve sürümleri için zamanla değişmiştir numpy
. Güncel sürümlerini kullanan kişiler için hemen kullanışlı olmasa da numpy
, bu cevabın ilk versiyonundan bu yana durumların nasıl değiştiğini göstermektedir.
Basit bir alternatif: meshgrid
+dstack
Şu anda kabul edilen cevap tile
ve repeat
iki diziyi birlikte yayınlamak için kullanılır. Ancak meshgrid
işlev pratik olarak aynı şeyi yapar. İşte çıktısı var tile
ve repeat
devrik geçirilmeden önce:
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])]
Ve işte çıktısı meshgrid
:
In [4]: numpy.meshgrid(x, y)
Out[4]:
[array([[1, 2, 3],
[1, 2, 3]]), array([[4, 4, 4],
[5, 5, 5]])]
Gördüğünüz gibi, neredeyse aynı. Tam olarak aynı sonucu elde etmek için yalnızca sonucu yeniden şekillendirmemiz gerekir.
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])]
Aksine bu noktada yeniden şekillendirilmesi daha olsa da, biz çıktısını geçebileceği meshgrid
için dstack
bazı işler kaydeder ve daha sonra yeniden biçimlendirilmesi,:
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]])
İstem aksine bu yorumun , farklı girişler farklı şekilli çıktılar üretecek hiçbir kanıt gördüm ve yukarıda ortaya koyduğu gibi yaptılar eğer oldukça garip olurdu ararlar, bu nedenle çok benzer şeyler yaparlar. Karşı örnek bulursanız lütfen bize bildirin.
Test meshgrid
+ dstack
ile repeat
+ karşılaştırmasıtranspose
Bu iki yaklaşımın göreceli performansı zaman içinde değişmiştir. Python'un (2.7) daha önceki bir sürümünde, küçük girdiler için meshgrid
+ dstack
kullanımı önemli ölçüde daha hızlıydı. (Bu testlerin bu cevabın eski bir versiyonuna ait olduğunu unutmayın.) Tanımlar:
>>> 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)
...
Orta boyutlu giriş için önemli bir hız gördüm. Ancak bu testleri daha yeni numpy
bir makinede Python'un (3.6.1) ve (1.12.1) daha yeni sürümleriyle tekrar denedim. İki yaklaşım şimdi neredeyse aynı.
Eski Test
>>> 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
Yeni Test
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)
Her zaman olduğu gibi, YMMV, ancak bu, Python ve numpy'nin son sürümlerinde bunların değiştirilebilir olduğunu göstermektedir.
Genelleştirilmiş ürün fonksiyonları
Genel olarak, küçük girişler için yerleşik işlevlerin kullanılmasının daha hızlı olacağını beklerken, büyük girişler için özel amaçlı bir işlev daha hızlı olabilir. Dahası, genelleştirilmiş n boyutlu bir ürün için tile
ve repeat
yardımcı olmaz, çünkü net yüksek boyutlu analogları yoktur. Bu nedenle, amaca yönelik oluşturulan işlevlerin davranışını da araştırmaya değer.
İlgili testlerin çoğu bu cevabın başında görünür, ancak burada Python'un önceki sürümlerinde ve numpy
karşılaştırma için yapılan testlerden birkaçı .
cartesian
Tanımlanan fonksiyon başka bir yanıt daha büyük girişler için oldukça iyi bir performans kullandı. ( cartesian_product_recursive
Yukarıda belirtilen işlevle aynıdır .) Karşılaştırmak cartesian
için dstack_prodct
sadece iki boyut kullanıyoruz.
Burada yine, eski test önemli bir fark gösterirken, yeni test neredeyse hiç göstermiyor.
Eski Test
>>> 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
Yeni Test
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)
Daha önce olduğu gibi, dstack_product
hala cartesian
daha küçük ölçeklerde atıyor .
Yeni Test ( gereksiz eski test gösterilmemiştir )
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)
Bu ayrımlar bence ilginç ve kayda değer; ama sonunda akademikler. Bu cevabın başlangıcındaki testlerin gösterdiği gibi, bu sürümlerin hepsi neredeyse her zaman cartesian_product
bu cevabın en başında tanımlanandan daha yavaştır - ki bu da bu sorunun cevapları arasındaki en hızlı uygulamalardan biraz daha yavaştır.