Prodotto cartesiano di più array in JavaScript


111

Come implementeresti il ​​prodotto cartesiano di più array in JavaScript?

Come esempio,

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

dovrebbe tornare

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


3
Questo è implementato nel modulo js-combinatorics: github.com/dankogai/js-combinatorics
Erel Segal-Halevi


Sono d'accordo su underscore.js ma non sono sicuro di vedere come la rimozione del tag di programmazione funzionale aiuterà @le_m
viebel

Fwiw, d3 aggiunto d3.cross(a, b[, reducer])a febbraio. github.com/d3/d3-array#cross
Toph

Risposte:


105

Aggiornamento 2017: risposta su 2 righe con vanilla JS

Tutte le risposte qui sono eccessivamente complicate , la maggior parte richiede 20 righe di codice o anche di più.

Questo esempio utilizza solo due righe di vanilla JavaScript , nessun lodash, trattino basso o altre librerie:

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;

Aggiornare:

È lo stesso di sopra ma migliorato per seguire rigorosamente la Guida allo stile JavaScript di Airbnb , convalidata utilizzando ESLint con 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);

Un ringraziamento speciale a ZuBB per avermi informato dei problemi di linter con il codice originale.

Esempio

Questo è l'esempio esatto della tua domanda:

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

Produzione

Questo è l'output di quel comando:

[ [ 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 ] ]

dimostrazione

Guarda le demo su:

Sintassi

La sintassi che ho usato qui non è una novità. Il mio esempio utilizza l'operatore spread ei parametri rest, funzionalità di JavaScript definite nella 6a edizione dello standard ECMA-262 pubblicato a giugno 2015 e sviluppate molto prima, meglio note come ES6 o ES2015. Vedere:

Rende un codice come questo così semplice che è un peccato non usarlo. Per le vecchie piattaforme che non lo supportano in modo nativo puoi sempre usare Babel o altri strumenti per trasferirlo alla sintassi più vecchia - e in effetti il ​​mio esempio trascritto da Babel è ancora più breve e più semplice della maggior parte degli esempi qui, ma non lo fa importa davvero perché l'output di transpilation non è qualcosa che devi capire o mantenere, è solo un fatto che ho trovato interessante.

Conclusione

Non è necessario scrivere centinaia di righe di codice difficili da mantenere e non è necessario utilizzare intere librerie per una cosa così semplice, quando due righe di JavaScript vanilla possono facilmente portare a termine il lavoro. Come puoi vedere, utilizzare le funzionalità moderne del linguaggio ripaga davvero e nei casi in cui devi supportare piattaforme arcaiche senza supporto nativo delle funzionalità moderne puoi sempre utilizzare Babel o altri strumenti per trasferire la nuova sintassi a quella vecchia .

Non programmare come se fosse il 1995

JavaScript si evolve e lo fa per un motivo. TC39 fa un lavoro straordinario nella progettazione del linguaggio con l'aggiunta di nuove funzionalità ei fornitori di browser fanno un lavoro straordinario nell'implementare tali funzionalità.

Per vedere lo stato corrente del supporto nativo di una determinata funzionalità nei browser, vedere:

Per vedere il supporto nelle versioni di Node, vedere:

Per utilizzare la sintassi moderna su piattaforme che non la supportano in modo nativo, usa Babel:


Ecco una versione dattiloscritta con una leggera modifica per tenere conto del modo in cui il dattiloscritto esegue la diffusione degli array. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe

1
@rsp grazie mille per un'ottima risposta. anche se vorrei chiederti di migliorarlo un po 'per ottenere un rig di avviso delle variabili ombreggiate (2 variabili locali ae 2 variabili locali b)
ZuBB

7
"Non scrivere codice come se fosse il 1995" - non c'è bisogno di essere spiacevole, non tutti hanno ancora raggiunto.
Godwhacker

7
Questo va bene, ma fallisce quando viene alimentato con il ['a', 'b'], [1,2], [[9], [10]]quale produrrà [ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]di conseguenza. Voglio dire, non manterrò il tipo di oggetti di [[9], [10]].
Redu

1
Dato che lo stiamo ...già utilizzando , non dovrebbe [].concat(...[array])diventare semplicemente [...array]?
Lazar Ljubenović

88

Ecco una soluzione funzionale al problema (senza alcuna variabile mutabile !) Utilizzando reducee flatten, fornita da 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>

Nota: questa soluzione è stata ispirata da http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/


C'è un errore di battitura in questa risposta, non dovrebbe esserci un ", vero" (forse lodash è cambiato da quando hai scritto questo post?)
Chris Jefferson,

@ChrisJefferson il secondo parametro flattenè rendere l'appiattimento superficiale. È obbligatorio qui!
viebel

4
Spiacenti, questa è un'incompatibilità lodash / underscore, hanno scambiato la bandiera.
Chris Jefferson

1
Quindi quando si appiattisce, utilizzare truecon trattino basso e utilizzare falsecon lodash per garantire un appiattimento superficiale.
Akseli Palén

Come si modifica questa funzione in modo che accetti array di array?

44

Ecco una versione modificata del codice di @ viebel in semplice Javascript, senza utilizzare alcuna libreria:

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
Non riesce per cartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]) mentre appiattisce ['gamma'] a 'gamma' e [['alpha']] a ['alpha']
Mzn

perché .concat(y)invece di.concat([ y ])
Grazie

@Grazie puoi modificare la risposta direttamente invece di commentare, l'ho appena fatto, quindi non è necessario ora: P
Olivier Lalonde

28

sembra che la comunità pensi che questo sia banale eo facile trovare un'implementazione di riferimento, a una breve ispezione non potrei o forse è solo che mi piace reinventare la ruota o risolvere problemi di programmazione simili a quelli di una classe in ogni caso è il tuo giorno fortunato :

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]
   ]

implementazione di riferimento completo che è relativamente efficiente ... MrGreen

sull'efficienza: potresti guadagnarne un po 'togliendo il se dal ciclo e avendo 2 cicli separati poiché è tecnicamente costante e saresti d'aiuto con la previsione dei rami e tutto quel casino, ma quel punto è un po' discutibile in javascript

anywho, divertiti -ck


1
Grazie @ckoz per la tua risposta dettagliata. Perché non dovresti usare la reducefunzione di array?
viebel

1
@viebel perché vuoi usare riduci? per uno, reduce ha un supporto molto scarso per i browser più vecchi (vedi: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ), e in ogni caso quel codice pazzo di quell'altra risposta ti sembra effettivamente leggibile ? non per me. certo è più corto, ma una volta minimizzato questo codice sarebbe all'incirca della stessa lunghezza, più facile da eseguire il debug / ottimizzare, in secondo luogo tutte quelle soluzioni "ridotte" si rompono alla stessa cosa, tranne che hanno una ricerca di chiusura (teoricamente più lenta), è anche più difficile progettare in modo che gestisca set infiniti ...
ckozl

5
Ho creato una versione 2+ volte più veloce e (imo) più pulita: pastebin.com/YbhqZuf7 Raggiunge l'aumento di velocità non usando result = result.concat(...)e non usando args.slice(1). Sfortunatamente, non sono riuscito a trovare un modo per sbarazzarmi di curr.slice()e la ricorsione.
Pauan

2
@Pauan bel lavoro, bella riduzione degli hot-spot nel complesso per un aumento delle prestazioni del 10% -50% in base a quello che vedo. Non posso parlare della "pulizia", ​​però, credo che la tua versione sia effettivamente più difficile da seguire a causa dell'uso delle variabili dell'ambito di chiusura. Ma in generale, un codice più performante è più difficile da seguire. Ho scritto la versione originale per leggibilità, vorrei avere più tempo per coinvolgerti in una performance
buttata

questo è davvero uno di quei problemi
James

26

La seguente funzione di generatore efficiente restituisce il prodotto cartesiano di tutti gli iterabili dati :

// 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]));

Accetta array, stringhe, set e tutti gli altri oggetti che implementano il protocollo iterabile .

Seguendo la specifica del prodotto cartesiano n-ario si ottiene

  • []se uno o più iterabili dati sono vuoti, ad esempio []o''
  • [[a]]se aviene fornito un singolo iterabile contenente un singolo valore .

Tutti gli altri casi vengono gestiti come previsto, come dimostrato dai seguenti casi di test:


Ti dispiace spiegare cosa sta succedendo su questo? Molte grazie!
LeandroP

Grazie per averci insegnato un meraviglioso esempio di utilizzo della funzione generatore + ricorsione della coda + loop a doppio strato! Ma la posizione del primo ciclo for nel codice deve essere modificata per rendere corretto l'ordine dei sotto-array di output. Codice fisso: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 Se vuoi riprodurre l'ordine delle tuple del prodotto cartesiano fornito dal commento di OP, la tua modifica è corretta. Tuttavia, l'ordine delle tuple all'interno del prodotto di solito non è rilevante, ad esempio matematicamente il risultato è un insieme non ordinato. Ho scelto questo ordine perché richiede chiamate molto meno ricorsive ed è quindi un po 'più performante - però non ho eseguito un benchmark.
le_m

Errata corrige: Nel mio commento sopra, "ricorsione di coda" dovrebbe essere "ricorsione" (non una chiamata di coda in questo caso).
ooo

20

Ecco una soluzione ricorsiva semplice e non elaborata:

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
Questo risulta essere il codice JS puro più efficiente in questo argomento. Ci vogliono circa 600 msec per finire su array di elementi 3 x 100 per produrre un array di lunghezza 1M.
Redu

1
Funziona per cartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]); senza appiattire i valori originali
Mzn

10

Ecco un modo ricorsivo che utilizza una funzione di generatore ECMAScript 2015 in modo da non dover creare tutte le tuple contemporaneamente:

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]))));


Questo non funzionerà quando uno degli array ha elementi di array comecartesian([[1],[2]],[10,20],[100,200,300])
Redu

La risposta @Redu è stata aggiornata per supportare gli argomenti dell'array.
heenenee

Sì, l' .concat()operatore di diffusione integrato a volte potrebbe diventare ingannevole.
Redu

10

Ecco un one-liner che utilizza l'ES2019 nativo flatMap. Non sono necessarie librerie, solo un browser moderno (o transpiler):

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

È essenzialmente una versione moderna della risposta di Viebel, senza lodash.


9

Utilizzando un tipico backtracking con i generatori 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 seguito è disponibile una versione simile compatibile con i browser meno recenti.


9

Questa è una soluzione ES6 pura che utilizza le funzioni delle frecce

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

Una versione coffeescript con lodash:

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

7

Un approccio a riga singola, per una migliore lettura con rientranze.

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

Richiede un singolo array con array di elementi cartesiani desiderati.

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; }


Ho dovuto aggiungere un'istruzione guard per gestire correttamente il caso in cui l'array ha un singolo elemento:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn

5

Questo è etichettato come programmazione funzionale, quindi diamo un'occhiata alla monade List :

Un'applicazione per questo elenco monadico rappresenta il calcolo non deterministico. List può contenere risultati per tutti i percorsi di esecuzione in un algoritmo ...

Bene, suona come una misura perfetta per cartesian. JavaScript ci fornisce Arraye la funzione di associazione monadica è Array.prototype.flatMap, quindi mettiamoli in uso:

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]))

Invece di quanto loopsopra, tpuò essere aggiunto come parametro al curry -

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

Alcune delle risposte in questo argomento non riescono quando uno qualsiasi degli array di input contiene un elemento dell'array. Faresti meglio a controllarlo.

Comunque non c'è bisogno di sottolineatura, lodash qualunque. Credo che questo dovrebbe farlo con JS ES6 puro, per quanto funzionale sia.

Questo pezzo di codice utilizza una mappa ridotta e una mappa annidata, semplicemente per ottenere il prodotto cartesiano di due array, tuttavia il secondo array proviene da una chiamata ricorsiva alla stessa funzione con un array in meno; quindi.. 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

Nel mio contesto particolare, l'approccio "vecchio stile" sembrava essere più efficiente dei metodi basati su caratteristiche più moderne. Di seguito è riportato il codice (incluso un piccolo confronto con altre soluzioni pubblicate in questo thread da @rsp e @sebnukem) se dovesse risultare utile anche a qualcun altro.

L'idea sta seguendo. Diciamo che stiamo costruendo il prodotto esterno di Narray, a_1,...,a_Nognuno dei quali ha m_icomponenti. Il prodotto esterno di questi array ha M=m_1*m_2*...*m_Nelementi e possiamo identificare ciascuno di essi con un N-vettore dimensionale i cui componenti sono numeri interi positivi e i-th componente è strettamente delimitato dall'alto da m_i. Ad esempio, per le combinazioni di vettori , la funzione sottostante costruisce consecutivamente tutti questi vettori e per ciascuno di essi identifica la corrispondente combinazione degli elementi delle matrici di input.(0, 0, ..., 0) corrisponderebbe alla particolare combinazione all'interno della quale si prende il primo elemento da ciascun array, mentre (m_1-1, m_2-1, ..., m_N-1)è identificato con la combinazione in cui si prende l'ultimo elemento da ogni array. Così per costruire tuttoM

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);
});

con node v6.12.2, ottengo i seguenti tempi:

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

3

Per coloro che hanno bisogno di TypeScript (risposta di @ Danny reimplementata)

/**
 * 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

Solo per una scelta, un'implementazione davvero semplice utilizzando 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 moderno in poche righe. Nessuna libreria o dipendenza esterna come 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

Potresti reducel'array 2D. Utilizzare flatMapsull'array dell'accumulatore per ottenere il acc.length x curr.lengthnumero di combinazioni in ogni ciclo. [].concat(c, n)viene utilizzato perché cè un numero nella prima iterazione e successivamente un array.

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))

(Questo è basato sulla risposta di Nina Scholz )


1

Un approccio non ricorsivo che aggiunge la possibilità di filtrare e modificare i prodotti prima di aggiungerli effettivamente al set di risultati. Nota l'uso di .map invece di .forEach. In alcuni browser, .map viene eseguito più velocemente.

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

Una semplice soluzione "mentale e visivamente amichevole".

inserisci qui la descrizione dell'immagine


// 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

Una semplice versione modificata del codice di @ viebel in semplice Javascript:

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

Un'implementazione più leggibile

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])))

Questo è per 3 array.
Alcune risposte hanno fornito un modo per un numero qualsiasi di array.
Questo può facilmente contrarsi o espandersi a meno o più array.
Avevo bisogno di combinazioni di un set con ripetizioni, quindi avrei potuto usare:

f(a,a,a)

ma usato:

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

0

Ho notato che nessuno ha pubblicato una soluzione che consente di passare una funzione per elaborare ogni combinazione, quindi ecco la mia soluzione:

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))

Produzione:

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

0

Semplice approccio a forza bruta JS che accetta un array di array come 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>

Ho appena convertito la risposta di @ dummersl da CoffeScript a JavaScript. Funziona e basta.

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

Ancora un'altra implementazione. Non il più corto o stravagante, ma veloce:

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

Nessuna libreria necessaria! :)

Ha bisogno di funzioni freccia però e probabilmente non così efficiente. : /

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

Per il record

Ecco la mia versione di esso. L'ho realizzato utilizzando il più semplice iteratore javascript "for ()", quindi è compatibile in ogni caso e ha le migliori prestazioni.

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;
}

I migliori saluti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.