Kartesisches Produkt mehrerer Arrays in JavaScript

Lesezeit: 7 Minuten

Kartesisches Produkt mehrerer Arrays in JavaScript
viebel

Wie würden Sie das kartesische Produkt mehrerer Arrays in JavaScript implementieren?

Als Beispiel,

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

sollte zurückkehren

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

  • mögliches Duplikat von Finden Sie alle Kombinationen von Optionen in einer Schleife

    – Bergi

    2. Februar 2013 um 15:34 Uhr

  • Dies ist im Modul js-combinatorics implementiert: github.com/dankogai/js-combinatorics

    – Erel Segal-Halevi

    26. März 2014 um 9:17 Uhr

  • mögliches Duplikat von Generieren von Kombinationen aus n Arrays mit m Elementen

    – Bergi

    5. Juli 2015 um 21:22 Uhr

  • Ich stimme underscore.js zu, aber ich bin mir nicht sicher, ob ich sehe, wie das Entfernen des Functional-Programming-Tags @le_m helfen wird

    – viebel

    17. Mai 2017 um 13:39 Uhr

  • Fwiw, d3 hinzugefügt d3.cross(a, b[, reducer]) im Februar. github.com/d3/d3-array#cross

    – Toph

    5. Oktober 2017 um 17:38 Uhr

Kartesisches Produkt mehrerer Arrays in JavaScript
viebel

Hier ist eine funktionale Lösung des Problems (ohne veränderliche Variable!) verwenden reduce und flattenzur Verfügung gestellt von 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>

Anmerkung: Diese Lösung wurde inspiriert von http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/

  • In dieser Antwort ist ein Tippfehler, es sollte kein “, true” geben (vielleicht hat sich lodash geändert, seit Sie diesen Beitrag erstellt haben?)

    – Chris Jefferson

    29. Mai 2015 um 14:36 ​​Uhr

  • @ChrisJefferson der zweite Param zu flatten ist es, die Abflachung flach zu machen. Hier ist es Pflicht!

    – viebel

    31. Mai 2015 um 7:39 Uhr

  • Tut mir leid, das ist eine Lodash/Unterstrich-Inkompatibilität, sie haben die Flagge vertauscht.

    – Chris Jefferson

    31. Mai 2015 um 14:17 Uhr

  • Verwenden Sie also beim Glätten true mit unterstreichen und verwenden false mit Lodash um eine flache Abflachung zu gewährleisten.

    – Akseli Palén

    16. August 2015 um 12:58 Uhr


  • Wie ändern Sie diese Funktion so, dass sie ein Array von Arrays akzeptiert?

    Benutzer4338202

    21. Oktober 2015 um 20:41 Uhr

  • Fehler bei cartesianProduct([[[1],[2],[3]], [‘a’, ‘b’], [[‘gamma’], [[‘alpha’]]], [‘zii’, ‘faa’]]), wenn es abflacht [‘gamma’] zu ‘gamma’ und [[‘alpha’]]zu [‘alpha’]

    – Mzn

    15. November 2017 um 19:14 Uhr

  • da .concat(y) anstatt .concat([ y ])

    – Mulan

    4. November 2018 um 13:11 Uhr

  • @Danke, du kannst die Antwort direkt bearbeiten, anstatt sie zu kommentieren, habe es gerade getan, also ist es jetzt nicht nötig: P

    – Olivier Lalonde

    30. Januar 2020 um 2:41 Uhr

1646765114 772 Kartesisches Produkt mehrerer Arrays in JavaScript
le_m

Die folgenden effizient Generatorfunktion gibt das kartesische Produkt aller gegebenen zurück Iterables:

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

Es akzeptiert Arrays, Strings, Sets und alle anderen Objekte, die das implementieren Iterierbares Protokoll.

Nach der Spezifikation der n-äres kartesisches Produkt es ergibt

  • [] wenn ein oder mehrere angegebene Iterables leer sind, z [] oder ''
  • [[a]] wenn ein einzelnes Iterable einen einzelnen Wert enthält a gegeben ist.

Alle anderen Fälle werden wie erwartet behandelt, wie die folgenden Testfälle zeigen:

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

// Test cases:
console.log([...cartesian([])]);              // []
console.log([...cartesian([1])]);             // [[1]]
console.log([...cartesian([1, 2])]);          // [[1], [2]]

console.log([...cartesian([1], [])]);         // []
console.log([...cartesian([1, 2], [])]);      // []

console.log([...cartesian([1], [2])]);        // [[1, 2]]
console.log([...cartesian([1], [2], [3])]);   // [[1, 2, 3]]
console.log([...cartesian([1, 2], [3, 4])]);  // [[1, 3], [2, 3], [1, 4], [2, 4]]

console.log([...cartesian('')]);              // []
console.log([...cartesian('ab', 'c')]);       // [['a','c'], ['b', 'c']]
console.log([...cartesian([1, 2], 'ab')]);    // [[1, 'a'], [2, 'a'], [1, 'b'], [2, 'b']]

console.log([...cartesian(new Set())]);       // []
console.log([...cartesian(new Set([1]))]);    // [[1]]
console.log([...cartesian(new Set([1, 1]))]); // [[1]]

  • Haben Sie etwas dagegen zu erklären, was hier passiert? Vielen Dank!

    – LeandroP

    22. Februar 2019 um 19:45 Uhr

  • Danke, dass Sie uns ein ziemlich wunderbares Beispiel für die Verwendung von Generatorfunktion + Schwanzrekursion + Double-Layer-Schleifen beigebracht haben! Aber die Position der ersten for-Schleife im Code muss geändert werden, damit die Reihenfolge der ausgegebenen Unterarrays korrekt ist. Festcode: function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }

    – ooh

    17. März 2019 um 14:00 Uhr

  • @ooo Wenn Sie die Reihenfolge der kartesischen Produkttupel reproduzieren möchten, die im Kommentar von OP angegeben ist, ist Ihre Änderung korrekt. Die Reihenfolge der Tupel innerhalb des Produkts ist jedoch normalerweise nicht relevant, zB ist das Ergebnis mathematisch eine ungeordnete Menge. Ich habe diese Reihenfolge gewählt, weil sie viel weniger rekursive Aufrufe erfordert und daher etwas performanter ist – einen Benchmark habe ich allerdings nicht durchgeführt.

    – le_m

    21. März 2019 um 17:16 Uhr

  • Erratum: In meinem obigen Kommentar sollte “Tail Recursion” “Recursion” sein (in diesem Fall kein Tail Call).

    – ooh

    1. April 2019 um 4:31 Uhr

  • Ich erhalte falsche Ergebnisse, wenn ich eine Map übergebe, es sei denn, ich klone die Iterable vorher mit Array.from oder [...arg]. Vielleicht liegt das Problem aber bei mir.

    – Ninjagecko

    19. Februar 2021 um 0:42 Uhr


Es scheint, dass die Community dies für trivial hält und/oder eine Referenzimplementierung leicht zu finden ist. Bei kurzem Hinsehen konnte ich jedoch keine finden, … entweder das, oder vielleicht liegt es einfach daran, dass ich gerne das Rad neu erfinde oder klassenzimmerähnliche Programmierprobleme löse. So oder so ist es Ihr Glückstag:

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

Vollständige Referenzimplementierung, die relativ effizient ist… 😁

Zur Effizienz: Sie könnten etwas gewinnen, indem Sie das if aus der Schleife nehmen und 2 separate Schleifen haben, da es technisch konstant ist und Sie bei der Verzweigungsvorhersage und all dem Durcheinander helfen würden, aber dieser Punkt ist in JavaScript irgendwie strittig.

  • Haben Sie etwas dagegen zu erklären, was hier passiert? Vielen Dank!

    – LeandroP

    22. Februar 2019 um 19:45 Uhr

  • Danke, dass Sie uns ein ziemlich wunderbares Beispiel für die Verwendung von Generatorfunktion + Schwanzrekursion + Double-Layer-Schleifen beigebracht haben! Aber die Position der ersten for-Schleife im Code muss geändert werden, damit die Reihenfolge der ausgegebenen Unterarrays korrekt ist. Festcode: function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }

    – ooh

    17. März 2019 um 14:00 Uhr

  • @ooo Wenn Sie die Reihenfolge der kartesischen Produkttupel reproduzieren möchten, die im Kommentar von OP angegeben ist, ist Ihre Änderung korrekt. Die Reihenfolge der Tupel innerhalb des Produkts ist jedoch normalerweise nicht relevant, zB ist das Ergebnis mathematisch eine ungeordnete Menge. Ich habe diese Reihenfolge gewählt, weil sie viel weniger rekursive Aufrufe erfordert und daher etwas performanter ist – einen Benchmark habe ich allerdings nicht durchgeführt.

    – le_m

    21. März 2019 um 17:16 Uhr

  • Erratum: In meinem obigen Kommentar sollte “Tail Recursion” “Recursion” sein (in diesem Fall kein Tail Call).

    – ooh

    1. April 2019 um 4:31 Uhr

  • Ich erhalte falsche Ergebnisse, wenn ich eine Map übergebe, es sei denn, ich klone die Iterable vorher mit Array.from oder [...arg]. Vielleicht liegt das Problem aber bei mir.

    – Ninjagecko

    19. Februar 2021 um 0:42 Uhr


Hier ist eine nicht ausgefallene, einfache rekursive Lösung:

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

  • Dieser stellt sich als der effizienteste reine JS-Code unter diesem Thema heraus. Es dauert ungefähr 600 ms, um Arrays mit 3 x 100 Elementen fertigzustellen, um ein Array mit einer Länge von 1 MB zu erzeugen.

    – Redu

    2. Oktober 2016 um 23:50 Uhr

  • Funktioniert für cartesianProduct ([[[1],[2],[3]], [‘a’, ‘b’], [[‘gamma’], [[‘alpha’]]], [‘zii’, ‘faa’]]); ohne die ursprünglichen Werte zu glätten

    – Mzn

    15. November 2017 um 19:18 Uhr

977040cookie-checkKartesisches Produkt mehrerer Arrays in JavaScript

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy