एक विहित cartesian_product
(लगभग)
विभिन्न गुणों के साथ इस समस्या के कई दृष्टिकोण हैं। कुछ दूसरों की तुलना में तेज़ हैं, और कुछ अधिक सामान्य-उद्देश्य हैं। बहुत सारे परीक्षण और ट्विकिंग के बाद, मैंने पाया है कि निम्नलिखित फ़ंक्शन, जो एन-डायमेंशनल की गणना करता है, cartesian_product
कई इनपुटों के लिए अन्य लोगों की तुलना में तेज है। दृष्टिकोणों की एक जोड़ी के लिए जो थोड़ा अधिक जटिल हैं, लेकिन कई मामलों में थोड़ा तेज भी हैं, पॉल पैंजर द्वारा उत्तर देखें ।
उस जवाब को देखते हुए, यह अब कार्टेशियन उत्पाद का सबसे तेज़ कार्यान्वयन नहीं है, numpy
जिसमें मैं जानता हूं। हालाँकि, मुझे लगता है कि इसकी सादगी इसे भविष्य में सुधार के लिए एक उपयोगी मानदंड बनाती रहेगी:
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)
यह उल्लेखनीय है कि यह फ़ंक्शन ix_
असामान्य तरीके से उपयोग करता है; जबकि प्रलेखित का उपयोग एक सरणी में सूचकांक उत्पन्नix_
करने के लिए है , यह सिर्फ इतना होता है कि एक ही आकार के साथ सरणियों को प्रसारण असाइनमेंट के लिए उपयोग किया जा सकता है। एमगिलसन के लिए बहुत धन्यवाद , जिन्होंने मुझे इस तरह का उपयोग करने की कोशिश करने के लिए प्रेरित किया , और अनटुब को , जिन्होंने इस उत्तर पर कुछ बेहद उपयोगी प्रतिक्रिया प्रदान की, जिसमें उपयोग करने का सुझाव भी शामिल हैix_
numpy.result_type
।
उल्लेखनीय विकल्प
यह कभी-कभी फोरट्रान क्रम में स्मृति के सन्निहित ब्लॉक लिखने के लिए तेज़ होता है। यह इस विकल्प का आधार है cartesian_product_transpose
, जो कुछ हार्डवेयर की तुलना में तेजी से साबित हुआ है cartesian_product
(नीचे देखें)। हालांकि, पॉल पैंजर का जवाब, जो एक ही सिद्धांत का उपयोग करता है, और भी तेज है। फिर भी, मैं इसमें रुचि रखने वाले पाठकों के लिए इसे शामिल करता हूं:
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
पैंज़र के दृष्टिकोण को समझने के बाद, मैंने एक नया संस्करण लिखा जो लगभग उतना ही तेज़ है, और लगभग उतना ही सरल है 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
ऐसा प्रतीत होता है कि कुछ निरंतर ओवरहेड है जो इसे पैंजर की तुलना में छोटे इनपुट के लिए धीमी गति से चलाता है। लेकिन बड़े आदानों के लिए, मेरे द्वारा चलाए गए सभी परीक्षणों में, यह केवल सबसे तेज़ कार्यान्वयन ( cartesian_product_transpose_pp
) के रूप में अच्छा प्रदर्शन करता है ।
निम्नलिखित वर्गों में, मैं अन्य विकल्पों के कुछ परीक्षणों को शामिल करता हूं। ये अब कुछ हद तक पुराने हैं, लेकिन डुप्लिकेट प्रयास के बजाय, मैंने उन्हें ऐतिहासिक हित से बाहर निकलने का फैसला किया है। अप-टू-डेट परीक्षणों के लिए, पैंजर का उत्तर, साथ ही निको श्लोमर देखें के बारे में भी देखें।
विकल्पों के खिलाफ परीक्षण
यहां परीक्षणों की एक बैटरी है जो प्रदर्शन को बढ़ावा देती है कि इनमें से कुछ फ़ंक्शन कई विकल्पों के सापेक्ष प्रदान करते हैं। यहां दिखाए गए सभी परीक्षण क्वाड-कोर मशीन पर किए गए थे, जो मैक ओएस 10.12.5, पायथन 3.6.1 और, चल रहे थेnumpy
1.12.1 पर । हार्डवेयर और सॉफ्टवेयर पर भिन्न भिन्न परिणाम उत्पन्न करने के लिए जाने जाते हैं, इसलिए YMMV। सुनिश्चित करने के लिए इन परीक्षणों को चलाएं!
परिभाषाएं:
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)]
परीक्षण के परिणाम:
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)
सभी मामलों में, cartesian_product
जैसा कि इस उत्तर की शुरुआत में परिभाषित किया गया है, सबसे तेज़ है।
उन कार्यों के लिए जो इनपुट सरणियों की एक मनमानी संख्या को स्वीकार करते हैं, यह तब भी प्रदर्शन की जांच करने के लायक है len(arrays) > 2
। (जब तक मैं यह निर्धारित नहीं कर सकता कि cartesian_product_recursive
इस मामले में कोई त्रुटि क्यों है, मैंने इसे इन परीक्षणों से हटा दिया है।)
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)
जब तक ये परीक्षण दिखाते हैं, cartesian_product
तब तक प्रतिस्पर्धी बना रहता है जब तक कि इनपुट सरणियों की संख्या चार से ऊपर नहीं बढ़ जाती (लगभग)। उसके बाद, cartesian_product_transpose
एक मामूली बढ़त है।
यह दोहराने लायक है कि अन्य हार्डवेयर और ऑपरेटिंग सिस्टम वाले उपयोगकर्ता अलग-अलग परिणाम देख सकते हैं। उदाहरण के लिए, Ubuntu 14.04, पायथन 3.4.3 और 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
नीचे, मैं इन लाइनों के साथ चलाए गए पहले परीक्षणों के बारे में कुछ विवरणों में जाता हूं। इन तरीकों के सापेक्ष प्रदर्शन समय के साथ बदल गए हैं, विभिन्न हार्डवेयर और पायथन के विभिन्न संस्करणों के लिए और numpy
। हालांकि, यह लोगों के लिए तत्काल उपयोगी नहीं है, लेकिन इसके अद्यतित संस्करण का उपयोग करते हैंnumpy
, यह दिखाता है कि इस उत्तर के पहले संस्करण के बाद से चीजें कैसे बदल गई हैं।
एक सरल विकल्प: meshgrid
+dstack
वर्तमान में स्वीकृत उत्तर उपयोग tile
और repeat
दो सरणियों को एक साथ प्रसारित करना है। लेकिन meshgrid
फ़ंक्शन व्यावहारिक रूप से एक ही काम करता है। यहाँ उत्पादन tile
और repeat
पारगमन से पहले पारित किया जा रहा है:
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])]
और यहाँ का आउटपुट है meshgrid
:
In [4]: numpy.meshgrid(x, y)
Out[4]:
[array([[1, 2, 3],
[1, 2, 3]]), array([[4, 4, 4],
[5, 5, 5]])]
जैसा कि आप देख सकते हैं, यह लगभग समान है। हमें केवल उसी परिणाम को प्राप्त करने के लिए परिणाम को नए सिरे से देखना चाहिए।
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])]
बल्कि इस बिंदु पर देगी से, हालांकि, हम के उत्पादन में दे सकते हैं meshgrid
करने के लिए dstack
और बाद में आकृति बदलें, जो कुछ काम की बचत होती है:
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]])
इस टिप्पणी में दावे के विपरीत , मैंने कोई सबूत नहीं देखा है कि अलग-अलग इनपुट अलग-अलग आकार के आउटपुट का उत्पादन करेंगे, और जैसा कि ऊपर दिखाया गया है, वे बहुत समान चीजें करते हैं, इसलिए यदि वे करते हैं तो यह काफी अजीब होगा। कृपया मुझे बताएं कि क्या आप एक प्रतिसाद पाते हैं।
परीक्षण meshgrid
+ dstack
बनाम repeat
+transpose
इन दोनों तरीकों के सापेक्ष प्रदर्शन समय के साथ बदल गए हैं। पायथन (2.7) के पुराने संस्करण में, छोटे इनपुट के लिए meshgrid
+ dstack
का उपयोग करने का परिणाम काफी तेज था। (ध्यान दें कि ये परीक्षण इस उत्तर के पुराने संस्करण से हैं।) परिभाषाएँ:
>>> 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)
...
मध्यम आकार के इनपुट के लिए, मैंने एक महत्वपूर्ण स्पीडअप देखा। लेकिन मैंने पायथन (3.6.1) और numpy
(1.12.1) के नए संस्करणों के साथ एक नई मशीन पर इन परीक्षणों को वापस ले लिया । दोनों दृष्टिकोण अब लगभग समान हैं।
पुराना टेस्ट
>>> 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
नया टेस्ट
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)
हमेशा की तरह, YMMV, लेकिन यह बताता है कि पायथन और हाल के संस्करणों में, ये विनिमेय हैं।
सामान्यीकृत उत्पाद कार्य
सामान्य तौर पर, हम उम्मीद कर सकते हैं कि अंतर्निहित कार्यों का उपयोग छोटे आदानों के लिए तेज होगा, जबकि बड़े इनपुट के लिए, एक उद्देश्य-निर्मित फ़ंक्शन तेज हो सकता है। इसके अलावा एक सामान्यीकृत एन-आयामी उत्पाद के लिए, tile
औरrepeat
मदद नहीं करेगा, क्योंकि उनके पास स्पष्ट उच्च-आयामी एनालॉग नहीं हैं। तो यह उद्देश्य-निर्मित कार्यों के व्यवहार की भी जांच करने के लायक है।
अधिकांश प्रासंगिक परीक्षण इस उत्तर की शुरुआत में दिखाई देते हैं, लेकिन यहां कुछ परीक्षण पायथन के पुराने संस्करणों और numpy
तुलना के लिए किए गए हैं।
cartesian
समारोह में परिभाषित किया गया एक और उत्तर बड़ा इनपुट के लिए बहुत अच्छी तरह से प्रदर्शन करने के लिए इस्तेमाल किया। (यह cartesian_product_recursive
ऊपर वर्णित फ़ंक्शन के समान है।) की तुलना cartesian
करने के लिए dstack_prodct
, हम केवल दो आयामों का उपयोग करते हैं।
यहां फिर से, पुराने परीक्षण में एक महत्वपूर्ण अंतर दिखा, जबकि नया परीक्षण लगभग कोई नहीं दिखाता है।
पुराना टेस्ट
>>> 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
नया टेस्ट
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)
पहले की तरह, dstack_product
अभी भी cartesian
छोटे पैमानों पर धड़कता है ।
नया परीक्षण ( अनावश्यक पुराना परीक्षण नहीं दिखाया गया )
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)
ये अंतर हैं, मुझे लगता है, दिलचस्प और लायक रिकॉर्डिंग; लेकिन वे अंत में अकादमिक हैं। जैसा कि इस उत्तर की शुरुआत में परीक्षणों से पता चलता है, इन सभी संस्करणों की तुलना में लगभग हमेशा धीमी होती है cartesian_product
, इस उत्तर की शुरुआत में परिभाषित की जाती है - जो कि इस प्रश्न के उत्तर के बीच सबसे तेज़ कार्यान्वयन से थोड़ा धीमा है।