บัญญัติcartesian_product
(เกือบ)
มีหลายวิธีในการแก้ไขปัญหานี้ด้วยคุณสมบัติที่แตกต่างกัน บางตัวเร็วกว่าตัวอื่นและบางตัวก็มีจุดประสงค์ทั่วไปมากกว่า หลังจากการทดสอบและปรับแต่งมากมายฉันพบว่าฟังก์ชั่นต่อไปนี้ซึ่งคำนวณ n-dimension 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_
คือการสร้างดัชนี ลงในอาร์เรย์มันเกิดขึ้นเพียงเพื่อที่อาร์เรย์ที่มีรูปร่างเหมือนกันสามารถนำมาใช้สำหรับการกำหนดออกอากาศ ขอบคุณมากที่mgilsonที่เป็นแรงบันดาลใจให้ผมลองใช้ix_
วิธีนี้และจะunutbunumpy.result_type
ที่ให้ข้อเสนอแนะบางอย่างที่เป็นประโยชน์อย่างมากในคำตอบนี้รวมทั้งคำแนะนำในการใช้งาน
ทางเลือกที่โดดเด่น
บางครั้งมันเร็วกว่าที่จะเขียนบล็อกหน่วยความจำที่ต่อเนื่องกันตามลำดับของ Fortran นั่นเป็นพื้นฐานของทางเลือกนี้cartesian_product_transpose
ซึ่งได้พิสูจน์แล้วว่าเร็วกว่าสำหรับฮาร์ดแวร์บางตัวcartesian_product
(ดูด้านล่าง) อย่างไรก็ตามคำตอบของ Paul Panzer ซึ่งใช้หลักการเดียวกันนั้นเร็วกว่า ยังฉันรวมถึงที่นี่สำหรับผู้อ่านที่สนใจ:
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
ดูเหมือนว่าจะมีค่าใช้จ่ายที่คงที่ซึ่งทำให้มันทำงานช้ากว่า Panzer สำหรับการป้อนข้อมูลขนาดเล็ก แต่สำหรับอินพุตที่ใหญ่ขึ้นในการทดสอบทั้งหมดที่ฉันวิ่งมันทำงานได้ดีเท่ากับการใช้งานที่เร็วที่สุดของเขา ( cartesian_product_transpose_pp
)
ในส่วนต่อไปนี้ฉันรวมการทดสอบทางเลือกอื่น ๆ ตอนนี้สิ่งเหล่านี้ค่อนข้างล้าสมัย แต่แทนที่จะพยายามทำซ้ำฉันตัดสินใจทิ้งพวกเขาไว้ที่นี่เพื่อผลประโยชน์ทางประวัติศาสตร์ สำหรับการทดสอบ up-to-date, ดูคำตอบของยานเกราะเช่นเดียวกับนิโก้Schlömer 's
ทดสอบกับทางเลือกอื่น
นี่คือแบตเตอรี่ของการทดสอบที่แสดงการเพิ่มประสิทธิภาพซึ่งฟังก์ชั่นบางอย่างเหล่านี้ให้ความสัมพันธ์กับทางเลือกอื่น ๆ การทดสอบทั้งหมดที่แสดงในที่นี้ได้ดำเนินการบนเครื่อง quad-core โดยใช้ Mac OS 10.12.5, Python 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
จะมีขอบเล็กน้อย
ผู้ใช้ที่มีฮาร์ดแวร์และระบบปฏิบัติการอื่นอาจเห็นผลลัพธ์ต่างกัน ตัวอย่างเช่นรายงาน unutbu ที่เห็นผลลัพธ์ต่อไปนี้สำหรับการทดสอบเหล่านี้โดยใช้ Ubuntu 14.04, Python 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
ด้านล่างนี้เป็นรายละเอียดเล็ก ๆ น้อย ๆ เกี่ยวกับการทดสอบก่อนหน้านี้ที่ฉันใช้ในบรรทัดเหล่านี้ ประสิทธิภาพสัมพัทธ์ของวิธีการเหล่านี้มีการเปลี่ยนแปลงเมื่อเวลาผ่านไปสำหรับฮาร์ดแวร์ที่แตกต่างกันและ Python และเวอร์ชั่น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
ประสิทธิภาพสัมพัทธ์ของสองแนวทางนี้เปลี่ยนแปลงไปตามกาลเวลา ใน Python เวอร์ชันก่อนหน้า (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)
...
สำหรับอินพุตขนาดปานกลางฉันเห็นการเร่งความเร็วที่สำคัญ แต่ฉันลองทดสอบเหล่านี้อีกครั้งด้วย Python (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 แต่สิ่งนี้ชี้ให้เห็นว่าใน Python และ numpy เวอร์ชันล่าสุดสิ่งเหล่านี้สามารถใช้แทนกันได้
ฟังก์ชั่นสินค้าทั่วไป
โดยทั่วไปเราอาจคาดหวังว่าการใช้ฟังก์ชั่นในตัวจะเร็วขึ้นสำหรับอินพุตขนาดเล็กในขณะที่สำหรับอินพุตขนาดใหญ่ฟังก์ชั่นที่สร้างขึ้นตามวัตถุประสงค์อาจเร็วขึ้น นอกจากนี้สำหรับผลิตภัณฑ์ n-dimension ทั่วไปtile
และrepeat
จะไม่ช่วยเพราะพวกเขาไม่มี analogues มิติที่สูงขึ้นชัดเจน ดังนั้นจึงควรตรวจสอบพฤติกรรมของฟังก์ชันที่สร้างขึ้นด้วยวัตถุประสงค์เช่นกัน
การทดสอบที่เกี่ยวข้องส่วนใหญ่จะปรากฏขึ้นที่จุดเริ่มต้นของคำตอบนี้ แต่นี่คือการทดสอบเล็กน้อยที่ดำเนินการกับ Python รุ่นก่อนหน้าและ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
กำหนดไว้ที่จุดเริ่มต้นของคำตอบนี้ - ซึ่งเป็นตัวเองช้ากว่าการใช้งานที่เร็วที่สุดในบรรดาคำตอบสำหรับคำถามนี้