Produk Cartesian dari beberapa larik dalam JavaScript


112

Bagaimana Anda akan mengimplementasikan produk Cartesian dari beberapa larik di JavaScript?

Sebagai contoh,

cartesian([1, 2], [10, 20], [100, 200, 300]) 

harus kembali

[
  [1, 10, 100],
  [1, 10, 200],
  [1, 10, 300],
  [2, 10, 100],
  [2, 10, 200]
  ...
]


3
Ini diimplementasikan dalam modul js-combinatorics: github.com/dankogai/js-combinatorics
Erel Segal-Halevi


Saya setuju tentang underscore.js tetapi saya tidak yakin saya melihat bagaimana menghapus tag pemrograman-fungsional akan membantu @le_m
viebel

Fwiw, d3 ditambahkan d3.cross(a, b[, reducer])pada bulan Februari. github.com/d3/d3-array#cross
Toph

Jawaban:


106

Pembaruan 2017: Jawaban 2 baris dengan vanilla JS

Semua jawaban di sini terlalu rumit , kebanyakan mengambil 20 baris kode atau bahkan lebih.

Contoh ini hanya menggunakan dua baris vanilla JavaScript , tanpa lodash, garis bawah, atau pustaka lainnya:

let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;

Memperbarui:

Ini sama seperti di atas tetapi ditingkatkan untuk mengikuti Panduan Gaya JavaScript Airbnb dengan ketat - divalidasi menggunakan ESLint dengan eslint-config-airbnb-base :

const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);

Terima kasih khusus kepada ZuBB karena telah memberi tahu saya tentang masalah linter dengan kode asli.

Contoh

Ini adalah contoh tepat dari pertanyaan Anda:

let output = cartesian([1,2],[10,20],[100,200,300]);

Keluaran

Ini adalah keluaran dari perintah itu:

[ [ 1, 10, 100 ],
  [ 1, 10, 200 ],
  [ 1, 10, 300 ],
  [ 1, 20, 100 ],
  [ 1, 20, 200 ],
  [ 1, 20, 300 ],
  [ 2, 10, 100 ],
  [ 2, 10, 200 ],
  [ 2, 10, 300 ],
  [ 2, 20, 100 ],
  [ 2, 20, 200 ],
  [ 2, 20, 300 ] ]

Demo

Lihat demo di:

Sintaksis

Sintaks yang saya gunakan di sini bukanlah hal baru. Contoh saya menggunakan operator penyebaran dan parameter lainnya - fitur JavaScript yang ditentukan dalam standar ECMA-262 edisi ke-6 yang diterbitkan pada Juni 2015 dan dikembangkan jauh lebih awal, lebih dikenal sebagai ES6 atau ES2015. Lihat:

Itu membuat kode seperti ini begitu sederhana sehingga dosa untuk tidak menggunakannya. Untuk platform lama yang tidak mendukungnya secara native, Anda selalu dapat menggunakan Babel atau alat lain untuk mentranspilasinya ke sintaks yang lebih lama - dan sebenarnya contoh saya yang ditranspilasi oleh Babel masih lebih pendek dan sederhana daripada kebanyakan contoh di sini, tetapi tidak sangat penting karena keluaran transpilasi bukanlah sesuatu yang perlu Anda pahami atau pertahankan, itu hanya fakta yang menurut saya menarik.

Kesimpulan

Tidak perlu menulis ratusan baris kode yang sulit dipelihara dan tidak perlu menggunakan seluruh pustaka untuk hal yang sesederhana itu, ketika dua baris vanilla JavaScript dapat dengan mudah menyelesaikan pekerjaan. Seperti yang Anda lihat, sangat bermanfaat untuk menggunakan fitur-fitur modern dari bahasa tersebut dan dalam kasus di mana Anda perlu mendukung platform kuno tanpa dukungan asli dari fitur-fitur modern, Anda selalu dapat menggunakan Babel atau alat lain untuk memindahkan sintaks baru ke yang lama. .

Jangan membuat kode seperti tahun 1995

JavaScript berkembang dan melakukannya karena suatu alasan. TC39 melakukan pekerjaan luar biasa dalam desain bahasa dengan menambahkan fitur baru dan vendor browser melakukan pekerjaan luar biasa dalam mengimplementasikan fitur tersebut.

Untuk melihat status dukungan asli saat ini dari setiap fitur yang diberikan di browser, lihat:

Untuk melihat dukungan dalam versi Node, lihat:

Untuk menggunakan sintaks modern pada platform yang tidak mendukungnya secara native, gunakan Babel:


Berikut adalah versi skrip dengan sedikit perubahan untuk menjelaskan cara skrip jenis melakukan penyebaran array. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe

1
@rsp terima kasih banyak untuk jawaban yang sangat bagus. meskipun saya ingin meminta Anda untuk memperbaikinya sedikit untuk mendapatkan peringatan dari variabel ab
bayangan

7
"Jangan membuat kode seperti tahun 1995" - tidak perlu bersikap tidak menyenangkan, belum semua orang berhasil.
Godwhacker

7
Ini baik-baik saja tetapi gagal ketika diberi makan ['a', 'b'], [1,2], [[9], [10]]yang akan menghasilkan [ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]sebagai hasilnya. Maksud saya tidak akan menyimpan jenis barang [[9], [10]].
Redu

1
Karena kita sudah menggunakan ..., bukankah seharusnya [].concat(...[array])menjadi sederhana [...array]?
Lazar Ljubenović

89

Berikut adalah solusi fungsional untuk masalah (tanpa variabel yang bisa berubah !) Menggunakan reducedan flatten, disediakan oleh underscore.js:

function cartesianProductOf() {
    return _.reduce(arguments, function(a, b) {
        return _.flatten(_.map(a, function(x) {
            return _.map(b, function(y) {
                return x.concat([y]);
            });
        }), true);
    }, [ [] ]);
}

// [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]]
console.log(cartesianProductOf([1, 2], [3, 4], ['a']));  
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>

Catatan: Solusi ini terinspirasi oleh http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/


Ada kesalahan ketik dalam jawaban ini, seharusnya tidak ada ", benar" (mungkin lodash telah berubah sejak Anda membuat postingan ini?)
Chris Jefferson

@ChrisJeerson parameter kedua yang harus dilakukan flattenadalah membuat perataan dangkal. Di sini wajib!
viebel

4
Maaf, ini adalah ketidaksesuaian lodash / garis bawah, mereka bertukar bendera.
Chris Jefferson

1
Jadi saat meratakan, gunakan truedengan garis bawah dan gunakan falsedengan lodash untuk memastikan perataan dangkal.
Akseli Palén

Bagaimana cara memodifikasi fungsi ini sehingga akan menerima array array?

44

Berikut adalah versi modifikasi dari kode @ viebel dalam Javascript biasa, tanpa menggunakan pustaka apa pun:

function cartesianProduct(arr) {
    return arr.reduce(function(a,b){
        return a.map(function(x){
            return b.map(function(y){
                return x.concat([y]);
            })
        }).reduce(function(a,b){ return a.concat(b) },[])
    }, [[]])
}

var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(JSON.stringify(a));


2
Gagal untuk CartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]) karena mendatar ['gamma'] menjadi 'gamma' dan [['alpha']] menjadi ['alpha']
Mzn

karena .concat(y)alih-alih.concat([ y ])
Terima kasih pada

@Terima kasih Anda dapat mengedit jawaban secara langsung alih-alih berkomentar, lakukan saja jadi tidak perlu sekarang: P
Olivier Lalonde

28

Sepertinya komunitas menganggap ini sepele dan atau mudah untuk menemukan implementasi referensi, setelah pemeriksaan singkat saya tidak bisa atau mungkin hanya saja saya suka menciptakan kembali roda atau menyelesaikan masalah pemrograman seperti kelas baik itu hari keberuntungan Anda :

function cartProd(paramArray) {

  function addTo(curr, args) {

    var i, copy, 
        rest = args.slice(1),
        last = !rest.length,
        result = [];

    for (i = 0; i < args[0].length; i++) {

      copy = curr.slice();
      copy.push(args[0][i]);

      if (last) {
        result.push(copy);

      } else {
        result = result.concat(addTo(copy, rest));
      }
    }

    return result;
  }


  return addTo([], Array.prototype.slice.call(arguments));
}


>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
     [1, 10, 100], [1, 10, 200], [1, 10, 300], [1, 20, 100], 
     [1, 20, 200], [1, 20, 300], [2, 10, 100], [2, 10, 200], 
     [2, 10, 300], [2, 20, 100], [2, 20, 200], [2, 20, 300]
   ]

implementasi referensi lengkap yang relatif efisien ... :-D

pada efisiensi: Anda bisa mendapatkan beberapa dengan mengeluarkan if dari loop dan memiliki 2 loop terpisah karena secara teknis konstan dan Anda akan membantu prediksi cabang dan semua kekacauan itu, tetapi poin itu semacam diperdebatkan di javascript

siapa saja, selamat menikmati -ck


1
Terima kasih @ckoz untuk jawaban rinci Anda. Mengapa Anda tidak menggunakan reducefungsi array?
viebel

1
@viebel mengapa Anda ingin menggunakan mengurangi? untuk satu, reduce memiliki dukungan yang sangat buruk untuk browser lama (lihat: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ), dan bagaimanapun juga apakah kode gila dari jawaban lain itu benar-benar terlihat dapat dibaca oleh Anda ? itu tidak bagi saya. yakin itu lebih pendek, tetapi setelah diperkecil, kode ini akan memiliki panjang yang sama, lebih mudah untuk di-debug / dioptimalkan, kedua semua solusi "kurangi" itu dipecah menjadi hal yang sama, kecuali mereka memiliki pencarian penutupan (secara teoritis lebih lambat), itu juga lebih sulit untuk merancang sehingga menangani set tak terbatas ...
ckozl

5
Saya membuat versi 2+ kali lebih cepat dan (imo) lebih bersih: pastebin.com/YbhqZuf7 Ini mencapai peningkatan kecepatan dengan tidak menggunakan result = result.concat(...)dan dengan tidak menggunakan args.slice(1). Sayangnya, saya tidak dapat menemukan cara untuk menyingkirkan curr.slice()dan rekursi tersebut.
Pauan

2
@Pauan kerja bagus, pengurangan titik panas yang bagus secara keseluruhan untuk peningkatan kinerja 10% -50% di liga berdasarkan apa yang saya lihat. Saya tidak bisa berbicara tentang "kebersihan", saya merasa versi Anda sebenarnya lebih sulit untuk diikuti karena penggunaan variabel lingkup penutupan. Tetapi secara umum, lebih banyak kode performant lebih sulit untuk diikuti. Saya menulis versi aslinya agar mudah dibaca, saya berharap saya memiliki lebih banyak waktu sehingga saya dapat melibatkan Anda dalam pertunjukan pertunjukan;) mungkin nanti ...
ckozl

ini benar-benar salah satu masalah itu
Yakobus

26

Fungsi generator efisien berikut mengembalikan produk kartesius dari semua iterabel yang diberikan :

// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
  const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));

Ini menerima array, string, set, dan semua objek lain yang mengimplementasikan protokol iterable .

Mengikuti spesifikasi produk kartesian n-ary yang dihasilkannya

  • []jika satu atau lebih iterable yang diberikan kosong, misalnya []atau''
  • [[a]]jika satu iterable yang berisi satu nilai adiberikan.

Semua kasus lainnya ditangani seperti yang diharapkan seperti yang ditunjukkan oleh kasus uji berikut:


Apakah Anda keberatan untuk menjelaskan apa yang terjadi pada yang satu ini? Terima kasih banyak!
LeandroP

Terima kasih telah mengajari kami contoh yang sangat bagus tentang penggunaan fungsi generator + rekursi ekor + loop lapisan ganda! Tetapi posisi for-loop pertama dalam kode perlu diubah untuk membuat urutan sub-array output benar. Kode tetap:function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }
ooo

@ooo Jika Anda ingin mereproduksi urutan tuple produk cartesian yang diberikan oleh komentar OP, maka modifikasi Anda sudah benar. Namun, urutan tupel dalam produk biasanya tidak relevan, misalnya secara matematis hasilnya adalah himpunan yang tidak berurutan. Saya memilih urutan ini karena memerlukan panggilan rekursif yang jauh lebih sedikit dan oleh karena itu sedikit lebih berkinerja - saya tidak menjalankan benchmark sekalipun.
le_m

Erratum: Dalam komentar saya di atas, "rekursi ekor" harus "rekursi" (bukan panggilan ekor dalam kasus ini).
ooo

21

Berikut adalah solusi rekursif langsung yang tidak mewah:

function cartesianProduct(a) { // a = array of array
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0]; // the first array of a
    a = cartesianProduct(a);
    for (i = 0, l = a1.length; i < l; i++) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j++)
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}

console.log(cartesianProduct([[1,2], [10,20], [100,200,300]]));
// [[1,10,100],[1,10,200],[1,10,300],[1,20,100],[1,20,200],[1,20,300],[2,10,100],[2,10,200],[2,10,300],[2,20,100],[2,20,200],[2,20,300]]


2
Yang ini ternyata menjadi kode JS murni paling efisien di bawah topik ini. Diperlukan waktu ~ 600msecs untuk menyelesaikan array item 3 x 100 untuk menghasilkan array dengan panjang 1M.
Redu

1
Berfungsi untuk cartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]); tanpa meratakan nilai asli
Mzn

10

Berikut adalah cara rekursif yang menggunakan fungsi generator ECMAScript 2015 sehingga Anda tidak perlu membuat semua tupel sekaligus:

function* cartesian() {
    let arrays = arguments;
    function* doCartesian(i, prod) {
        if (i == arrays.length) {
            yield prod;
        } else {
            for (let j = 0; j < arrays[i].length; j++) {
                yield* doCartesian(i + 1, prod.concat([arrays[i][j]]));
            }
        }
    }
    yield* doCartesian(0, []);
}

console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));


Ini tidak akan berfungsi ketika salah satu larik memiliki item larik seperticartesian([[1],[2]],[10,20],[100,200,300])
Redu

@Redu Jawaban telah diperbarui untuk mendukung argumen array.
heenenee

Ya, .concat()operator penyebaran bawaan terkadang mungkin menjadi licik.
Redu

10

Berikut ini satu baris yang menggunakan ES2019 asli flatMap. Tidak perlu perpustakaan, cukup browser modern (atau transpiler):

data.reduce((a, b) => a.flatMap(x => b.map(y => [...x, y])), [[]]);

Ini pada dasarnya adalah versi modern dari jawaban viebel, tanpa lodash.


Tentu tidak ada perpustakaan yang dibutuhkan. Tapi itu juga, bukan kode yang paling bisa dibaca. Ini trade-off.
Arturo Hernandez

9

Menggunakan backtracking biasa dengan generator ES6,

function cartesianProduct(...arrays) {
  let current = new Array(arrays.length);
  return (function* backtracking(index) {
    if(index == arrays.length) yield current.slice();
    else for(let num of arrays[index]) {
      current[index] = num;
      yield* backtracking(index+1);
    }
  })(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
  console.log('[' + item.join(', ') + ']');
}
div.as-console-wrapper { max-height: 100%; }

Di bawah ini ada versi serupa yang kompatibel dengan browser lama.


9

Ini adalah solusi ES6 murni menggunakan fungsi panah

function cartesianProduct(arr) {
  return arr.reduce((a, b) =>
    a.map(x => b.map(y => x.concat(y)))
    .reduce((a, b) => a.concat(b), []), [[]]);
}

var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));


7

Versi coffeescript dengan lodash:

_ = require("lodash")
cartesianProduct = ->
    return _.reduceRight(arguments, (a,b) ->
        _.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
    , [ [] ])

7

Pendekatan satu baris, untuk membaca lebih baik dengan lekukan.

result = data.reduce(
    (a, b) => a.reduce(
        (r, v) => r.concat(b.map(w => [].concat(v, w))),
        []
    )
);

Dibutuhkan satu larik dengan larik item kartesius yang diinginkan.

var data = [[1, 2], [10, 20], [100, 200, 300]],
    result = data.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));

console.log(result.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }


Saya harus menambahkan pernyataan penjaga untuk menangani dengan benar kasus di mana array memiliki satu elemen:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn

5

Ini diberi tag pemrograman fungsional jadi mari kita lihat Monad List :

Satu aplikasi untuk daftar monadik ini merepresentasikan komputasi nondeterministik. List dapat menyimpan hasil untuk semua jalur eksekusi dalam suatu algoritma ...

Nah itu terdengar seperti yang sempurna cocok untuk cartesian. JavaScript memberi kita Arraydan fungsi pengikatan monadik adalah Array.prototype.flatMap, jadi mari kita gunakan -

const cartesian = (...all) =>
{ const loop = (t, a, ...more) =>
    a === undefined
      ? [ t ]
      : a .flatMap (x => loop ([ ...t, x ], ...more))
  return loop ([], ...all)
}

console .log (cartesian ([1,2], [10,20], [100,200,300]))

Alih-alih di loopatas, tdapat ditambahkan sebagai parameter kari -

const makeCartesian = (t = []) => (a, ...more) =>
  a === undefined
    ? [ t ]
    : a .flatMap (x => makeCartesian ([ ...t, x ]) (...more))

const cartesian =
  makeCartesian ()

console .log (cartesian ([1,2], [10,20], [100,200,300]))


3

Beberapa jawaban di bawah topik ini gagal ketika salah satu larik input berisi item larik. Sebaiknya kau periksa itu.

Pokoknya tidak perlu garis bawah, lodash apa saja. Saya yakin yang satu ini harus melakukannya dengan JS ES6 murni, sefungsional yang didapatnya.

Potongan kode ini menggunakan peta yang dikurangi dan bersarang, hanya untuk mendapatkan produk kartesian dari dua larik, namun larik kedua berasal dari panggilan rekursif ke fungsi yang sama dengan satu larik yang lebih sedikit; karenanya.. a[0].cartesian(...a.slice(1))

Array.prototype.cartesian = function(...a){
  return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
                  : this;
};

var arr = ['a', 'b', 'c'],
    brr = [1,2,3],
    crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr))); 


3

Dalam pengaturan khusus saya, pendekatan "kuno" tampaknya lebih efisien daripada metode yang didasarkan pada fitur yang lebih modern. Di bawah ini adalah kode (termasuk perbandingan kecil dengan solusi lain yang diposting di utas ini oleh @rsp dan @sebnukem) seandainya itu terbukti berguna bagi orang lain juga.

Idenya mengikuti. Katakanlah kita sedang membangun produk luar dari Narray, yang a_1,...,a_Nmasing-masing memiliki m_ikomponen. Produk luar dari array ini memiliki M=m_1*m_2*...*m_Nelemen dan kita dapat mengidentifikasi masing-masing dengan N-vektor dimensi yang komponennya adalah bilangan bulat positif dan ikomponen -th dibatasi secara ketat dari atas m_i. Misalnya, vektor(0, 0, ..., 0) akan sesuai dengan kombinasi tertentu di mana seseorang mengambil elemen pertama dari setiap larik, sedangkan (m_1-1, m_2-1, ..., m_N-1)diidentifikasikan dengan kombinasi di mana seseorang mengambil elemen terakhir dari setiap larik. Jadi untuk membangun semuaM kombinasi, fungsi di bawah ini secara berurutan menyusun semua vektor tersebut dan untuk masing-masing vektor tersebut mengidentifikasi kombinasi yang sesuai dari elemen array input.

function cartesianProduct(){
    const N = arguments.length;

    var arr_lengths = Array(N);
    var digits = Array(N);
    var num_tot = 1;
    for(var i = 0; i < N; ++i){
        const len = arguments[i].length;
        if(!len){
            num_tot = 0;
            break;
        }
        digits[i] = 0;
        num_tot *= (arr_lengths[i] = len);
    }

    var ret = Array(num_tot);
    for(var num = 0; num < num_tot; ++num){

        var item = Array(N);
        for(var j = 0; j < N; ++j){ item[j] = arguments[j][digits[j]]; }
        ret[num] = item;

        for(var idx = 0; idx < N; ++idx){
            if(digits[idx] == arr_lengths[idx]-1){
                digits[idx] = 0;
            }else{
                digits[idx] += 1;
                break;
            }
        }
    }
    return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0];
    a = cartesianProduct_sebnukem(a);
    for (i = 0, l = a1.length; i < l; i++) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j++)
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];

let fns = {
    'cartesianProduct': function(args){ return cartesianProduct(...args); },
    'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
    'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};

Object.keys(fns).forEach(fname => {
    console.time(fname);
    const ret = fns[fname](args);
    console.timeEnd(fname);
});

dengan node v6.12.2, saya mendapatkan pengaturan waktu berikut:

cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms

3

Bagi mereka yang membutuhkan TypeScript (reimplemented @ Danny jawaban)

/**
 * Calculates "Cartesian Product" sets.
 * @example
 *   cartesianProduct([[1,2], [4,8], [16,32]])
 *   Returns:
 *   [
 *     [1, 4, 16],
 *     [1, 4, 32],
 *     [1, 8, 16],
 *     [1, 8, 32],
 *     [2, 4, 16],
 *     [2, 4, 32],
 *     [2, 8, 16],
 *     [2, 8, 32]
 *   ]
 * @see https://stackoverflow.com/a/36234242/1955709
 * @see https://en.wikipedia.org/wiki/Cartesian_product
 * @param arr {T[][]}
 * @returns {T[][]}
 */
function cartesianProduct<T> (arr: T[][]): T[][] {
  return arr.reduce((a, b) => {
    return a.map(x => {
      return b.map(y => {
        return x.concat(y)
      })
    }).reduce((c, d) => c.concat(d), [])
  }, [[]] as T[][])
}

2

Hanya untuk pilihan, implementasi sederhana yang nyata menggunakan array reduce:

const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");

const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);

2

JavaScript modern hanya dalam beberapa baris. Tidak ada perpustakaan atau dependensi eksternal seperti Lodash.

function cartesian(...arrays) {
  return arrays.reduce((a, b) => a.flatMap(x => b.map(y => x.concat([y]))), [ [] ]);
}

console.log(
  cartesian([1, 2], [10, 20], [100, 200, 300])
    .map(arr => JSON.stringify(arr))
    .join('\n')
);


2

Anda bisa reducemembuat array 2D. Gunakan flatMappada array akumulator untuk mendapatkan acc.length x curr.lengthjumlah kombinasi di setiap loop. [].concat(c, n)digunakan karena cadalah angka pada iterasi pertama dan array sesudahnya.

const data = [ [1, 2], [10, 20], [100, 200, 300] ];

const output = data.reduce((acc, curr) =>
  acc.flatMap(c => curr.map(n => [].concat(c, n)))
)

console.log(JSON.stringify(output))

(Ini berdasarkan jawaban Nina Scholz )


1

Pendekatan non-rekursif yang menambahkan kemampuan untuk memfilter dan memodifikasi produk sebelum benar-benar menambahkannya ke kumpulan hasil. Perhatikan penggunaan .map daripada .forEach. Di beberapa browser, .map berjalan lebih cepat.

function crossproduct(arrays,rowtest,rowaction) {
      // Calculate the number of elements needed in the result
      var result_elems = 1, row_size = arrays.length;
      arrays.map(function(array) {
            result_elems *= array.length;
      });
      var temp = new Array(result_elems), result = [];

      // Go through each array and add the appropriate element to each element of the temp
      var scale_factor = result_elems;
      arrays.map(function(array)
      {
        var set_elems = array.length;
        scale_factor /= set_elems;
        for(var i=result_elems-1;i>=0;i--) {
            temp[i] = (temp[i] ? temp[i] : []);
            var pos = i / scale_factor % set_elems;
            // deal with floating point results for indexes, this took a little experimenting
            if(pos < 1 || pos % 1 <= .5) {
                pos = Math.floor(pos);
            } else {
                pos = Math.min(array.length-1,Math.ceil(pos));
            }
            temp[i].push(array[pos]);
            if(temp[i].length===row_size) {
                var pass = (rowtest ? rowtest(temp[i]) : true);
                if(pass) {
                    if(rowaction) {
                        result.push(rowaction(temp[i]));
                    } else {
                        result.push(temp[i]);
                    }
                }
            }
        }
      });
      return result;
    }

1

Solusi sederhana "pikiran dan visual ramah".

masukkan deskripsi gambar di sini


// t = [i, length]

const moveThreadForwardAt = (t, tCursor) => {
  if (tCursor < 0)
    return true; // reached end of first array

  const newIndex = (t[tCursor][0] + 1) % t[tCursor][1];
  t[tCursor][0] = newIndex;

  if (newIndex == 0)
    return moveThreadForwardAt(t, tCursor - 1);

  return false;
}

const cartesianMult = (...args) => {
  let result = [];
  const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
  let reachedEndOfFirstArray = false;

  while (false == reachedEndOfFirstArray) {
    result.push(t.map((v, i) => args[i][v[0]]));

    reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
  }

  return result;
}

// cartesianMult(
//   ['a1', 'b1', 'c1'],
//   ['a2', 'b2'],
//   ['a3', 'b3', 'c3'],
//   ['a4', 'b4']
// );

console.log(cartesianMult(
  ['a1'],
  ['a2', 'b2'],
  ['a3', 'b3']
));

1

Versi kode @ viebel yang sederhana dan dimodifikasi dalam Javascript biasa:

function cartesianProduct(...arrays) {
  return arrays.reduce((a, b) => {
    return [].concat(...a.map(x => {
      const next = Array.isArray(x) ? x : [x];
      return [].concat(b.map(y => next.concat(...[y])));
    }));
  });
}

const product = cartesianProduct([1, 2], [10, 20], [100, 200, 300]);

console.log(product);
/*
[ [ 1, 10, 100 ],
  [ 1, 10, 200 ],
  [ 1, 10, 300 ],
  [ 1, 20, 100 ],
  [ 1, 20, 200 ],
  [ 1, 20, 300 ],
  [ 2, 10, 100 ],
  [ 2, 10, 200 ],
  [ 2, 10, 300 ],
  [ 2, 20, 100 ],
  [ 2, 20, 200 ],
  [ 2, 20, 300 ] ];
*/

1

Implementasi yang lebih mudah dibaca

function productOfTwo(one, two) {
  return one.flatMap(x => two.map(y => [].concat(x, y)));
}

function product(head = [], ...tail) {
  if (tail.length === 0) return head;
  return productOfTwo(head, product(...tail));
}

const test = product(
  [1, 2, 3],
  ['a', 'b']
);

console.log(JSON.stringify(test));


1
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))

Ini untuk 3 larik.
Beberapa jawaban memberi jalan untuk sejumlah array.
Ini dapat dengan mudah berkontraksi atau berkembang menjadi lebih sedikit atau lebih array.
Saya membutuhkan kombinasi satu set dengan pengulangan, jadi saya bisa menggunakan:

f(a,a,a)

tapi digunakan:

f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))

0

Saya perhatikan bahwa tidak ada yang memposting solusi yang memungkinkan suatu fungsi diteruskan untuk memproses setiap kombinasi, jadi inilah solusi saya:

const _ = require('lodash')

function combinations(arr, f, xArr = []) {
    return arr.length>1 
    ? _.flatMap(arr[0], x => combinations(arr.slice(1), f, xArr.concat(x)))
    : arr[0].map(x => f(...xArr.concat(x)))
}

// use case
const greetings = ["Hello", "Goodbye"]
const places = ["World", "Planet"]
const punctuationMarks = ["!", "?"]
combinations([greetings,places,punctuationMarks], (greeting, place, punctuationMark) => `${greeting} ${place}${punctuationMark}`)
  .forEach(row => console.log(row))

Keluaran:

Hello World!
Hello World?
Hello Planet!
Hello Planet?
Goodbye World!
Goodbye World?
Goodbye Planet!
Goodbye Planet?

0

Pendekatan brute force JS biasa yang menggunakan array array sebagai input.

var cartesian = function(arrays) {
    var product = [];
    var precals = [];
    var length = arrays.reduce(function(acc, curr) {
        return acc * curr.length
    }, 1);
    for (var i = 0; i < arrays.length; i++) {
        var array = arrays[i];
        var mod = array.length;
        var div = i > 0 ? precals[i - 1].div * precals[i - 1].mod : 1;
        precals.push({
            div: div,
            mod: mod
        });
    }
    for (var j = 0; j < length; j++) {
        var item = [];
        for (var i = 0; i < arrays.length; i++) {
            var array = arrays[i];
            var precal = precals[i];
            var k = (~~(j / precal.div)) % precal.mod;
            item.push(array[k]);
        }
        product.push(item);
    }
    return product;
};

cartesian([
    [1],
    [2, 3]
]);

cartesian([
    [1],
    [2, 3],
    [4, 5, 6]
]);

0

var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [
    []
  ]);
};

console.log(cartesianProduct(chars, nums))
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

Baru saja mengubah jawaban @ dummersl dari CoffeScript ke JavaScript. Ini berhasil.

var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [[]]);
};

console.log( cartesianProduct(chars, nums) )

0

Implementasi lain. Bukan yang terpendek atau mewah, tapi cepat:

function cartesianProduct() {
    var arr = [].slice.call(arguments),
        intLength = arr.length,
        arrHelper = [1],
        arrToReturn = [];

    for (var i = arr.length - 1; i >= 0; i--) {
        arrHelper.unshift(arrHelper[0] * arr[i].length);
    }

    for (var i = 0, l = arrHelper[0]; i < l; i++) {
        arrToReturn.push([]);
        for (var j = 0; j < intLength; j++) {
            arrToReturn[i].push(arr[j][(i / arrHelper[j + 1] | 0) % arr[j].length]);
        }
    }

    return arrToReturn;
}

0

Tidak perlu perpustakaan! :)

Membutuhkan fungsi panah dan mungkin tidak seefisien itu. : /

const flatten = (xs) => 
    xs.flat(Infinity)

const binaryCartesianProduct = (xs, ys) =>
    xs.map((xi) => ys.map((yi) => [xi, yi])).flat()

const cartesianProduct = (...xss) =>
    xss.reduce(binaryCartesianProduct, [[]]).map(flatten)
      
console.log(cartesianProduct([1,2,3], [1,2,3], [1,2,3]))


0

Untuk catatan

Ini dia versi saya. Saya membuatnya menggunakan iterator javascript paling sederhana "for ()", jadi kompatibel pada setiap kasus dan memiliki kinerja terbaik.

function cartesian(arrays){
    var quant = 1, counters = [], retArr = [];

    // Counts total possibilities and build the counters Array;
    for(var i=0;i<arrays.length;i++){
        counters[i] = 0;
        quant *= arrays[i].length;
    }

    // iterate all possibilities
    for(var i=0,nRow;i<quant;i++){
        nRow = [];
        for(var j=0;j<counters.length;j++){
            if(counters[j] < arrays[j].length){
                nRow.push(arrays[j][counters[j]]);
            } else { // in case there is no such an element it restarts the current counter
                counters[j] = 0;
                nRow.push(arrays[j][counters[j]]);
            }
            counters[j]++;
        }
        retArr.push(nRow);
    }
    return retArr;
}

Salam Hormat.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.