Iloczyn kartezjański wielu tablic w JavaScript

112

Jak zaimplementowałbyś iloczyn kartezjański wielu tablic w JavaScript?

Jako przykład,

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

powinien wrócić

[
  [1, 10, 100],
  [1, 10, 200],
  [1, 10, 300],
  [2, 10, 100],
  [2, 10, 200]
  ...
]
viebel
źródło
3
Zaimplementowano to w module js-combinatorics: github.com/dankogai/js-combinatorics
Erel Segal-Halevi
Zgadzam się z underscore.js, ale nie jestem pewien, jak pomoże usunięcie tagu programowania funkcjonalnego @le_m
viebel
Fwiw, d3 dodane d3.cross(a, b[, reducer])w lutym. github.com/d3/d3-array#cross
Toph

Odpowiedzi:

106

Aktualizacja 2017: 2-wierszowa odpowiedź z waniliowym JS

Wszystkie odpowiedzi tutaj są zbyt skomplikowane , większość z nich zajmuje 20 linii kodu lub nawet więcej.

W tym przykładzie użyto tylko dwóch wierszy zwykłego JavaScript , żadnych bibliotek lodash, podkreślenia ani innych:

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;

Aktualizacja:

Jest to to samo, co powyżej, ale poprawione, aby ściśle przestrzegać Przewodnika po stylach JavaScript Airbnb - zweryfikowanego za pomocą ESLint z 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);

Specjalne podziękowania dla ZuBB za poinformowanie mnie o problemach lintera z oryginalnym kodem.

Przykład

Oto dokładny przykład z Twojego pytania:

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

Wynik

Oto wynik tego polecenia:

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

Próbny

Zobacz dema na:

Składnia

Składnia, której tutaj użyłem, nie jest niczym nowym. Mój przykład wykorzystuje operator spreadu i pozostałe parametry - cechy JavaScript zdefiniowane w 6. edycji standardu ECMA-262 opublikowanego w czerwcu 2015 roku i opracowanego znacznie wcześniej, lepiej znanego jako ES6 lub ES2015. Widzieć:

To sprawia, że ​​taki kod jest tak prosty, że grzechem jest go nie używać. W przypadku starych platform, które nie obsługują go natywnie, zawsze możesz użyć Babel lub innych narzędzi, aby przenieść go do starszej składni - i w rzeczywistości mój przykład transponowany przez Babel jest nadal krótszy i prostszy niż większość przykładów tutaj, ale tak nie jest naprawdę ważne, ponieważ efekt transpilacji nie jest czymś, co musisz zrozumieć lub utrzymać, to po prostu fakt, który wydałem mi się interesujący.

Wniosek

Nie ma potrzeby pisania setek linii kodu, który jest trudny w utrzymaniu i nie ma potrzeby używania całych bibliotek do tak prostej rzeczy, kiedy dwie linie zwykłego JavaScript mogą z łatwością wykonać zadanie. Jak widać, naprawdę opłaca się korzystać z nowoczesnych funkcji języka, aw przypadkach, gdy potrzebujesz obsługi archaicznych platform bez natywnego wsparcia dla nowoczesnych funkcji, zawsze możesz użyć Babel lub innych narzędzi do przeniesienia nowej składni do starej .

Nie koduj jak w 1995 roku

JavaScript ewoluuje i nie bez powodu. TC39 wykonuje niesamowitą robotę przy projektowaniu języka, dodając nowe funkcje, a dostawcy przeglądarek wykonują niesamowitą robotę, implementując te funkcje.

Aby zobaczyć aktualny stan natywnej obsługi dowolnej funkcji w przeglądarkach, zobacz:

Aby zobaczyć obsługę w wersjach Node, zobacz:

Aby używać nowoczesnej składni na platformach, które nie obsługują jej natywnie, użyj Babel:

rsp
źródło
Oto wersja maszynopisu z niewielką zmianą uwzględniającą sposób, w jaki maszynopis rozdziela tablice. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam
1
@rsp wielkie dzięki za naprawdę dobrą odpowiedź. chociaż chciałbym prosić o trochę ulepszenie, aby uzyskać zestaw ostrzeżeń o zmiennych cieniowanych (2 zmienne lokalne ai 2 zmienne lokalne b)
ZuBB
7
„Nie programuj jak w 1995 roku” - nie musisz być nieprzyjemny, jeszcze nie wszyscy nadrobili zaległości.
Godwhacker
7
Jest to w porządku, ale zawodzi, gdy jest karmione, ['a', 'b'], [1,2], [[9], [10]]co [ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]w rezultacie daje plon . Mam na myśli, że nie zatrzymam tego typu przedmiotów [[9], [10]].
Redu
1
Skoro ...już używamy , czy nie powinno [].concat(...[array])stać się po prostu [...array]?
Lazar Ljubenović
89

Oto funkcjonalne rozwiązanie problemu (bez żadnej zmiennej zmiennej !) Przy użyciu reducei flatten, zapewniane przez 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>

Uwaga: to rozwiązanie zostało zainspirowane http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/

viebel
źródło
W tej odpowiedzi jest literówka, nie powinno być „, prawda” (może lodash zmienił się od czasu, gdy napisałeś ten post?)
Chris Jefferson
@ChrisJefferson drugim parametrem flattenjest spłaszczenie płyt . Tutaj jest to obowiązkowe!
viebel
4
Przepraszamy, to jest niezgodność lodash / podkreślenia, zamienili się flagą.
Chris Jefferson
1
Dlatego podczas spłaszczania używaj truez podkreśleniem i używaj falsez lodash, aby zapewnić płytkie spłaszczenie.
Akseli Palén
Jak zmodyfikować tę funkcję, aby akceptowała tablice tablic?
44

Oto zmodyfikowana wersja kodu @ viebel w zwykłym JavaScript, bez użycia żadnej biblioteki:

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

Danny
źródło
2
Błąd dla produktu kartezjańskiego ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', „faa”]]), ponieważ spłaszcza [„gamma”] do „gamma” i [[„alpha”]] do [„alpha”]
Mzn
ponieważ .concat(y)zamiast.concat([ y ])
Dziękuję
@ Dziękuję, możesz edytować odpowiedź bezpośrednio, zamiast komentować, po prostu zrobiłem to, więc nie ma potrzeby teraz: P
Olivier Lalonde
28

wydaje się, że społeczność uważa, że ​​jest to trywialne i / lub łatwe do znalezienia implementacji referencyjnej, po krótkiej inspekcji nie mogłem, a może po prostu lubię wymyślać na nowo koło lub rozwiązywać problemy programistyczne podobne do klasowych, tak czy inaczej to twój szczęśliwy dzień :

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

pełna implementacja referencyjna, która jest stosunkowo wydajna ... :-D

na wydajności: możesz trochę zyskać, wyjmując if z pętli i mając 2 oddzielne pętle, ponieważ jest on technicznie stały i pomagałbyś w przewidywaniu gałęzi i całym tym bałaganie, ale ten punkt jest trochę dyskusyjny w javascript

ktokolwiek, ciesz się -ck

ckozl
źródło
1
Dzięki @ckoz za szczegółową odpowiedź. Dlaczego nie miałbyś użyć reducefunkcji tablicy?
viebel
1
@viebel, dlaczego chcesz użyć redukuj? po pierwsze ,redred ma bardzo słabe wsparcie dla starszych przeglądarek (patrz: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ), aw każdym razie czy ten szalony kod z tej drugiej odpowiedzi faktycznie wygląda dla ciebie czytelnie ? mnie to nie dotyczy. pewnie, że jest krótszy, ale po zminimalizowaniu ten kod miałby mniej więcej taką samą długość, łatwiejszy do debugowania / optymalizacji, po drugie wszystkie te rozwiązania „redukuj” rozkładają się na to samo, z wyjątkiem tego, że mają wyszukiwanie zamknięcia (teoretycznie wolniej), jest też trudniejsze zaprojektować tak, aby obsługiwał nieskończone zestawy ...
ckozl
5
Stworzyłem 2+ razy szybszą i (imo) czystszą wersję: pastebin.com/YbhqZuf7 Osiąga wzrost prędkości, nie używając result = result.concat(...)i nie używając args.slice(1). Niestety nie mogłem znaleźć sposobu na pozbycie się curr.slice()i rekursji.
Pauan
2
@Pauan dobra robota, niezła redukcja hot-spotów w sumie w lidze o 10% -50% wzrost wydajności w oparciu o to, co widzę. Nie mogę jednak mówić o „czystości”, uważam, że w rzeczywistości trudniej jest śledzić Twoją wersję ze względu na użycie zmiennych zakresu domknięcia. Ale ogólnie rzecz biorąc, bardziej wydajny kod jest trudniejszy do naśladowania. Oryginalną wersję napisałem dla czytelności, żałuję, że nie miałem więcej czasu, abym mógł cię wciągnąć w przedstawienie;) może później ...
ckozl
to naprawdę jest jeden z tych problemów
James
26

Następująca wydajna funkcja generatora zwraca iloczyn kartezjański wszystkich podanych iterable :

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

Akceptuje tablice, ciągi znaków, zestawy i wszystkie inne obiekty implementujące iterowalny protokół .

Zgodnie ze specyfikacją z kartezjańskim produktu N-ary zwracającej

  • []jeśli jedna lub więcej podanych iterable jest pustych, np. []lub''
  • [[a]]jeśli apodana jest pojedyncza iterowalna zawierająca jedną wartość .

Wszystkie inne przypadki są obsługiwane zgodnie z oczekiwaniami, co pokazują następujące przypadki testowe:

le_m
źródło
Czy możesz wyjaśnić, co się na nim dzieje? Wielkie dzięki!
LeandroP
Dzięki za nauczenie nas cudownego przykładu użycia funkcji generatora + rekurencji ogonowej + dwuwarstwowych pętli! Ale pozycja pierwszej pętli for w kodzie musi zostać zmieniona, aby kolejność wyjściowych tablic podrzędnych była poprawna. Naprawiono kod: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 Jeśli chcesz odtworzyć kolejność krotek produktów kartezjańskich podaną w komentarzu OP, to Twoja modyfikacja jest poprawna. Jednak kolejność krotek w produkcie zwykle nie ma znaczenia, np. Matematycznie wynikiem jest nieuporządkowany zbiór. Wybrałem tę kolejność, ponieważ wymaga znacznie mniej wywołań rekurencyjnych i dlatego jest nieco bardziej wydajna - jednak nie uruchomiłem testu porównawczego.
le_m
Erratum: W moim komentarzu powyżej, „rekurencja ogona” powinna być „rekurencją” (nie jest to wywołanie ogonowe w tym przypadku).
ooo
21

Oto proste, proste rozwiązanie rekurencyjne:

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

sebnukem
źródło
2
Ten okazuje się być najbardziej wydajnym czystym kodem JS w tym temacie. Wykończenie macierzy 3 x 100 elementów w celu utworzenia tablicy o długości 1M zajmuje około 600 ms.
Redu
1
Działa dla produktu kartezjańskiego ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', „faa”]]); bez spłaszczania oryginalnych wartości
Mzn
10

Oto rekurencyjny sposób korzystania z funkcji generatora ECMAScript 2015 , dzięki czemu nie musisz tworzyć wszystkich krotek naraz:

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

heenenee
źródło
To nie zadziała, gdy jedna z tablic ma elementy tablicy, takie jakcartesian([[1],[2]],[10,20],[100,200,300])
Redu
@Redu Odpowiedź została zaktualizowana, aby obsługiwać argumenty tablicowe.
heenenee
Tak, .concat()wbudowany operator rozprzestrzeniania się czasami może stać się oszustwem.
Redu
10

Oto jedna linijka korzystająca z natywnego ES2019 flatMap. Żadne biblioteki nie są potrzebne, tylko nowoczesna przeglądarka (lub transpiler):

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

Zasadniczo jest to nowoczesna wersja odpowiedzi Viebel, bez lodash.

Fred Kleuver
źródło
Z pewnością żadna biblioteka nie była potrzebna. Ale to też nie jest najbardziej czytelny kod w historii. To kompromis.
Arturo Hernandez
Czytelność ma w tym przypadku więcej wspólnego z moim wyborem użycia operatora spreadu, jak sądzę, a nie z wyborem nieużywania biblioteki. Nie sądzę, aby lodash w ogóle prowadził do bardziej czytelnego kodu.
Fred Kleuver
9

Używając typowego backtrackingu z generatorami 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%; }

Poniżej podobna wersja kompatybilna ze starszymi przeglądarkami.

Oriol
źródło
9

To jest czyste rozwiązanie ES6 wykorzystujące funkcje strzałek

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

Christopher Moore
źródło
7

Wersja Coffeescript z lodash:

_ = require("lodash")
cartesianProduct = ->
    return _.reduceRight(arguments, (a,b) ->
        _.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
    , [ [] ])
dsummersl
źródło
7

Podejście jednoliniowe dla lepszego czytania z wcięciami.

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

Zajmuje pojedynczą tablicę z tablicami poszukiwanych elementów kartezjańskich.

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

Nina Scholz
źródło
1
Musiałem dodać instrukcję guard, aby poprawnie obsłużyć przypadek, w którym tablica ma pojedynczy element:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn
5

To jest oznaczone jako programowanie funkcjonalne, więc spójrzmy na monadę List :

Jedna aplikacja dla tej monadycznej listy reprezentuje obliczenia niedeterministyczne. List może przechowywać wyniki dla wszystkich ścieżek wykonywania w algorytmie ...

Cóż, to brzmi jak idealne dopasowanie cartesian. JavaScript daje nam Arrayi monadyczną funkcją wiążącą jest Array.prototype.flatMap, więc użyjmy ich -

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

Zamiast looppowyższego tmożna dodać jako parametr curried -

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

Dziękuję Ci
źródło
3

Niektóre odpowiedzi w tym temacie kończą się niepowodzeniem, gdy którakolwiek z tablic wejściowych zawiera element tablicy. Lepiej to sprawdź.

W każdym razie nie ma potrzeby podkreślania, chleba w ogóle. Uważam, że ten powinien zrobić to z czystym JS ES6, tak funkcjonalnym, jak to tylko możliwe.

Ten fragment kodu używa mapy redukującej i zagnieżdżonej, aby po prostu uzyskać iloczyn kartezjański dwóch tablic, jednak druga tablica pochodzi z rekurencyjnego wywołania tej samej funkcji z jedną tablicą mniej; W związku z tym.. 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))); 

Redu
źródło
3

W moim konkretnym miejscu podejście „staromodne” wydawało się skuteczniejsze niż metody oparte na bardziej nowoczesnych funkcjach. Poniżej znajduje się kod (w tym małe porównanie z innymi rozwiązaniami zamieszczonymi w tym wątku przez @rsp i @sebnukem), jeśli okaże się przydatny także komuś innemu.

Pomysł jest następujący. Powiedzmy, że tworzymy iloczyn zewnętrzny Ntablic, a_1,...,a_Nz których każda ma m_ikomponenty. Iloczyn zewnętrzny tych tablic ma M=m_1*m_2*...*m_Nelementy i każdy z nich możemy zidentyfikować za pomocą N-wektora wymiarowego, którego składowymi są dodatnie liczby całkowite, a i-ty składnik jest ściśle ograniczony od góry przez m_i. Na przykład wektor (0, 0, ..., 0)odpowiadałby określonej kombinacji, w której bierze się pierwszy element z każdej tablicy, podczas gdy (m_1-1, m_2-1, ..., m_N-1)jest identyfikowany za pomocą kombinacji, w której pobiera się ostatni element z każdej tablicy. Tak więc, aby zbudować wszystkoM kombinacje, poniższa funkcja konstruuje kolejno wszystkie takie wektory i dla każdego z nich identyfikuje odpowiednią kombinację elementów tablic wejściowych.

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

z node v6.12.2, otrzymuję następujące czasy:

cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms
ewcz
źródło
3

Dla tych, którzy potrzebują TypeScript (reimplemented @ Danny's answer)

/**
 * 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[][])
}
artnikpro
źródło
2

Dla wyboru prawdziwa prosta implementacja przy użyciu tablic 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))), []);
Simple.Js
źródło
2

Nowoczesny JavaScript w zaledwie kilku wierszach. Żadnych bibliotek zewnętrznych ani zależności, takich jak 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')
);

Miles Elam
źródło
2

Możesz mieć reducetablicę 2D. Użyj flatMapna tablicy akumulatorów, aby uzyskać acc.length x curr.lengthliczbę kombinacji w każdej pętli. [].concat(c, n)jest używany, ponieważ cjest liczbą w pierwszej iteracji, a następnie tablicą.

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

(To jest oparte na odpowiedzi Niny Scholz )

adiga
źródło
1

Podejście nierekurencyjne, które dodaje możliwość filtrowania i modyfikowania produktów przed faktycznym dodaniem ich do zestawu wyników. Zwróć uwagę na użycie .map zamiast .forEach. W niektórych przeglądarkach .map działa szybciej.

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;
    }
W jakikolwiek sposób
źródło
1

Proste rozwiązanie „przyjazne dla umysłu i wizualne”.

wprowadź opis obrazu tutaj


// 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']
));
zero.zero.seven
źródło
1

Prosta, zmodyfikowana wersja kodu @ viebel w prostym języku 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 ] ];
*/
Juan Gaitán
źródło
1

Bardziej czytelna implementacja

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

Andrei
źródło
1
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))

Dotyczy to 3 tablic.
Niektóre odpowiedzi ustąpiły miejsca dowolnej liczbie tablic.
Może to łatwo skurczyć się lub rozszerzyć do mniejszej lub większej liczby tablic.
Potrzebowałem kombinacji jednego zestawu z powtórzeniami, więc mogłem użyć:

f(a,a,a)

ale używany:

f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))
iAmOren
źródło
0

Zauważyłem, że nikt nie opublikował rozwiązania, które umożliwia przekazanie funkcji do przetwarzania każdej kombinacji, więc oto moje rozwiązanie:

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

Wynik:

Hello World!
Hello World?
Hello Planet!
Hello Planet?
Goodbye World!
Goodbye World?
Goodbye Planet!
Goodbye Planet?
Lezorte
źródło
0

Zwykłe podejście brutalnej siły w JS, które przyjmuje tablicę tablic jako dane wejściowe.

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]
]);
Samuel Ventura
źródło
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>

Właśnie przekonwertowałem odpowiedź @ dummersl z CoffeScript na JavaScript. Po prostu działa.

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) )
guneysus
źródło
0

Jeszcze inna realizacja. Nie najkrótszy ani wyszukany, ale szybki:

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;
}
flara256
źródło
0

Żadne biblioteki nie są potrzebne! :)

Potrzebuje jednak funkcji strzałek i prawdopodobnie nie jest tak wydajna. : /

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

Chris Vouga
źródło
0

Tak dla porządku

Oto moja wersja. Zrobiłem to przy użyciu najprostszego iteratora javascript „for ()”, więc jest kompatybilny w każdym przypadku i ma najlepszą wydajność.

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

Z poważaniem.

LeandroP
źródło