ผลคูณคาร์ทีเซียนของอาร์เรย์หลายรายการใน JavaScript


112

คุณจะใช้ผลิตภัณฑ์คาร์ทีเซียนของหลายอาร์เรย์ใน JavaScript ได้อย่างไร?

ตัวอย่างเช่น,

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

ควรกลับ

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


3
สิ่งนี้นำไปใช้ในโมดูล js-combinatorics: github.com/dankogai/js-combinatorics
Erel Segal-Halevi


ฉันเห็นด้วยเกี่ยวกับ underscore.js แต่ฉันไม่แน่ใจว่าฉันเห็นว่าการลบแท็กการเขียนโปรแกรมฟังก์ชันจะช่วยได้อย่างไร @le_m
viebel

Fwiw, d3 เพิ่มd3.cross(a, b[, reducer])ในเดือนกุมภาพันธ์ github.com/d3/d3-array#cross
Toph

คำตอบ:


106

2017 Update: คำตอบ 2 บรรทัดด้วย vanilla JS

คำตอบทั้งหมดที่นี่มีความซับซ้อนมากเกินไปส่วนใหญ่ใช้รหัส 20 บรรทัดหรือมากกว่านั้น

ตัวอย่างนี้ใช้วานิลลา JavaScriptเพียงสองบรรทัดไม่มี lodash ขีดล่างหรือไลบรารีอื่น ๆ :

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;

อัปเดต:

สิ่งนี้เหมือนกับข้างต้น แต่ได้รับการปรับปรุงให้ปฏิบัติตามคู่มือสไตล์ JavaScript ของ Airbnbอย่างเคร่งครัด- ตรวจสอบโดยใช้ESLintกับ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);

ขอขอบคุณเป็นพิเศษสำหรับZuBB ที่แจ้งให้เราทราบเกี่ยวกับปัญหาเศษกระดาษกับรหัสเดิม

ตัวอย่าง

นี่คือตัวอย่างที่แน่นอนจากคำถามของคุณ:

let output = cartesian([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 ] ]

การสาธิต

ดูการสาธิตได้ที่:

ไวยากรณ์

ไวยากรณ์ที่ฉันใช้ที่นี่ไม่มีอะไรใหม่ ตัวอย่างของฉันใช้ตัวดำเนินการสเปรดและพารามิเตอร์ที่เหลือซึ่งเป็นคุณลักษณะของ JavaScript ที่กำหนดไว้ในมาตรฐาน ECMA-262 รุ่นที่ 6 ซึ่งเผยแพร่เมื่อเดือนมิถุนายน 2015 และได้รับการพัฒนาก่อนหน้านี้ซึ่งรู้จักกันดีในชื่อ ES6 หรือ ES2015 ดู:

มันทำให้โค้ดแบบนี้ง่ายมากจนเป็นบาปที่จะไม่ใช้มัน สำหรับแพลตฟอร์มเก่าที่ไม่รองรับโดยกำเนิดคุณสามารถใช้ Babel หรือเครื่องมืออื่น ๆ เพื่อถ่ายทอดไปยังไวยากรณ์ที่เก่ากว่าได้เสมอและอันที่จริงตัวอย่างของฉันที่ Babel ถ่ายทอดนั้นยังสั้นและง่ายกว่าตัวอย่างส่วนใหญ่ที่นี่ แต่ก็ไม่ได้ มีความสำคัญมากเพราะผลลัพธ์ของการถ่ายเทไม่ใช่สิ่งที่คุณต้องเข้าใจหรือรักษาไว้มันเป็นเพียงความจริงที่ฉันพบว่าน่าสนใจ

สรุป

ไม่จำเป็นต้องเขียนโค้ดหลายร้อยบรรทัดซึ่งยากต่อการดูแลรักษาและไม่จำเป็นต้องใช้ไลบรารีทั้งหมดสำหรับเรื่องง่ายๆเช่นนี้เมื่อวานิลลา JavaScript สองบรรทัดสามารถทำงานให้เสร็จได้อย่างง่ายดาย อย่างที่คุณเห็นว่าการใช้คุณสมบัติที่ทันสมัยของภาษานั้นคุ้มค่ามากและในกรณีที่คุณจำเป็นต้องสนับสนุนแพลตฟอร์มโบราณโดยไม่มีการสนับสนุนคุณลักษณะสมัยใหม่แบบดั้งเดิมคุณสามารถใช้ Babel หรือเครื่องมืออื่น ๆ เพื่อถ่ายทอดไวยากรณ์ใหม่ไปยังเวอร์ชันเก่าได้ตลอดเวลา .

อย่าเขียนโค้ดเหมือนปี 1995

JavaScript พัฒนาขึ้นและเป็นเช่นนั้นด้วยเหตุผล TC39 ทำงานได้อย่างยอดเยี่ยมในการออกแบบภาษาด้วยการเพิ่มคุณสมบัติใหม่ ๆ และผู้จำหน่ายเบราว์เซอร์ทำงานได้อย่างยอดเยี่ยมในการนำคุณสมบัติเหล่านั้นไปใช้

หากต้องการดูสถานะปัจจุบันของการสนับสนุนดั้งเดิมของคุณสมบัติที่กำหนดในเบราว์เซอร์โปรดดู:

หากต้องการดูการสนับสนุนในเวอร์ชันโหนดโปรดดู:

หากต้องการใช้ไวยากรณ์สมัยใหม่บนแพลตฟอร์มที่ไม่รองรับโดยกำเนิดให้ใช้ Babel:


นี่คือเวอร์ชัน typescript ที่มีการเปลี่ยนแปลงเล็กน้อยในบัญชีสำหรับวิธีการที่ typescript แพร่กระจายอาร์เรย์ gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe

1
@rsp ขอบคุณมากสำหรับคำตอบที่ดีจริงๆ แม้ว่าฉันอยากจะขอให้คุณปรับปรุงเล็กน้อยเพื่อรับคำเตือนของตัวแปรที่เป็นเงา (2 local vars aและ 2 local vars b)
ZuBB

7
"อย่าเขียนโค้ดเหมือนปี 1995" - ไม่จำเป็นต้องเป็นที่น่ารังเกียจไม่ใช่ทุกคนที่ยังตามทัน
Godwhacker

7
สิ่งนี้ทำได้ดี แต่ล้มเหลวเมื่อเลี้ยงด้วย['a', 'b'], [1,2], [[9], [10]]ซึ่งจะให้[ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]ผล [[9], [10]]ผมหมายถึงจะไม่ให้ประเภทของรายการของ
Redu

1
เนื่องจากเราใช้...อยู่แล้วจึงไม่ควร[].concat(...[array])กลายเป็นเพียง[...array]?
Lazar Ljubenović

89

นี่คือวิธีแก้ปัญหาที่ใช้งานได้ (โดยไม่มีตัวแปรที่ไม่แน่นอน !) โดยใช้reduceและflattenจัดทำโดย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>

หมายเหตุ: โซลูชันนี้ได้รับแรงบันดาลใจจากhttp://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/


คำตอบนี้มีการพิมพ์ผิดไม่ควรมี "จริง" (อาจมีการเปลี่ยนแปลง lodash ตั้งแต่คุณโพสต์นี้)
Chris Jefferson

@ChrisJefferson พารามิเตอร์ที่สองflattenคือการทำให้แบนตื้น เป็นข้อบังคับที่นี่!
viebel

4
ขออภัยนี่เป็นความไม่เข้ากันของ lodash / ขีดล่างพวกเขาเปลี่ยนไปรอบ ๆ ธง
Chris Jefferson

1
ดังนั้นเมื่อแบนให้ใช้trueกับขีดล่างและใช้falseกับlodashเพื่อให้แน่ใจว่าแบนเรียบ
Akseli Palén

จะแก้ไขฟังก์ชันนี้อย่างไรจึงจะรับอาร์เรย์อาร์เรย์ได้

44

นี่คือโค้ดของ @ viebel เวอร์ชันแก้ไขใน Javascript ธรรมดาโดยไม่ต้องใช้ไลบรารีใด ๆ :

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
ล้มเหลวสำหรับ cartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]) เมื่อแบน ['gamma'] เป็น 'gamma' และ [['alpha']] เป็น ['alpha']
Mzn

เพราะ.concat(y)แทน.concat([ y ])
ขอบคุณ

@ ขอบคุณคุณสามารถแก้ไขคำตอบได้โดยตรงแทนการแสดงความคิดเห็นเพียงแค่ทำมันดังนั้นตอนนี้ไม่จำเป็น: P
Olivier Lalonde

28

ดูเหมือนว่าชุมชนจะคิดว่าสิ่งนี้เป็นเรื่องเล็กน้อยและหรือง่ายต่อการค้นหาการนำไปใช้อ้างอิงเมื่อตรวจสอบสั้น ๆ ฉันทำไม่ได้หรืออาจเป็นเพียงการที่ฉันชอบประดิษฐ์วงล้อขึ้นมาใหม่หรือแก้ปัญหาการเขียนโปรแกรมแบบห้องเรียนไม่ว่าจะเป็นวันที่โชคดีของคุณ :

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

การใช้งานอ้างอิงแบบเต็มซึ่งค่อนข้างมีประสิทธิภาพ ... :-D

เกี่ยวกับประสิทธิภาพ: คุณสามารถได้รับบางส่วนโดยการเอา if ออกจากลูปและมี 2 ลูปแยกกันเนื่องจากมันคงที่ในทางเทคนิคและคุณจะช่วยในการทำนายสาขาและความยุ่งเหยิงทั้งหมดนั้น แต่ประเด็นนั้นก็คือการสงสัยในจาวาสคริปต์

ใครก็ตามที่สนุก -ck


1
ขอบคุณ @ckoz สำหรับคำตอบโดยละเอียดของคุณ ทำไมคุณไม่ใช้reduceฟังก์ชันของอาร์เรย์
viebel

1
@viebel ทำไมถึงอยากใช้ลด? ประการหนึ่งการลดมีการสนับสนุนที่ไม่ดีมากสำหรับเบราว์เซอร์รุ่นเก่า (ดู: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ) และไม่ว่าในกรณีใดรหัสที่บ้าคลั่งจากคำตอบอื่น ๆ นั้นจะดูอ่านได้สำหรับคุณ เหรอ? มันไม่เหมาะกับฉัน แน่ใจว่ามันสั้นลง แต่เมื่อย่อขนาดแล้วโค้ดนี้จะมีความยาวเท่ากันง่ายต่อการดีบัก / ปรับให้เหมาะสมประการที่สองโซลูชัน "ลด" ทั้งหมดจะแบ่งเป็นสิ่งเดียวกันยกเว้นว่าจะมีการค้นหาการปิด (ช้ากว่าในทางทฤษฎี) ก็ยากขึ้นเช่นกัน ในการออกแบบเพื่อให้จัดการกับชุดที่ไม่มีที่สิ้นสุด ...
ckozl

5
ฉันสร้าง 2+ ครั้งได้เร็วขึ้นและ (IMO) เวอร์ชันทำความสะอาด: pastebin.com/YbhqZuf7มันประสบความสำเร็จเพิ่มความเร็วโดยไม่ได้ใช้และไม่ได้ใช้result = result.concat(...) args.slice(1)น่าเสียดายที่ฉันไม่สามารถหาวิธีกำจัดcurr.slice()และการเกิดซ้ำได้
Pauan

2
@Pauan เป็นงานที่ดีการลดจุดร้อนที่ดีโดยรวมสำหรับลีกเพิ่มประสิทธิภาพ 10% -50% ตามสิ่งที่ฉันเห็น ฉันไม่สามารถพูดถึง "ความสะอาด" ได้ แต่ฉันรู้สึกว่าเวอร์ชันของคุณทำตามได้ยากกว่าเนื่องจากการใช้ตัวแปรขอบเขตการปิด แต่โดยทั่วไปแล้วโค้ดที่มีประสิทธิภาพมากกว่านั้นยากที่จะปฏิบัติตาม ฉันเขียนฉบับดั้งเดิมเพื่อให้อ่านง่ายฉันหวังว่าฉันจะมีเวลามากขึ้นเพื่อที่จะได้มีส่วนร่วมกับคุณในการแสดง;) อาจจะหลังจากนั้น ...
ckozl

นี่เป็นหนึ่งในปัญหาเหล่านั้นจริงๆ
เจมส์

26

ฟังก์ชันตัวสร้างที่มีประสิทธิภาพต่อไปนี้จะส่งคืนผลิตภัณฑ์คาร์ทีเซียนของการทำซ้ำทั้งหมดที่กำหนด:

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

ยอมรับอาร์เรย์สตริงชุดและวัตถุอื่น ๆ ทั้งหมดที่ใช้โปรโตคอลที่ทำซ้ำได้

ตามข้อกำหนดของผลิตภัณฑ์คาร์ทีเซียน n-aryจะให้ผลตอบแทน

  • []หากการวนซ้ำที่กำหนดอย่างน้อยหนึ่งรายการว่างเปล่าเช่น[]หรือ''
  • [[a]]หากมีการaกำหนดค่าที่สามารถทำซ้ำได้รายการเดียวที่มีค่าเดียว

กรณีอื่น ๆ ทั้งหมดได้รับการจัดการตามที่คาดไว้ดังที่แสดงโดยกรณีทดสอบต่อไปนี้:


คุณคิดที่จะอธิบายว่าเกิดอะไรขึ้นกับสิ่งนี้หรือไม่? ขอบคุณมาก!
LeandroP

ขอบคุณที่สอนเราถึงตัวอย่างที่ยอดเยี่ยมในการใช้ฟังก์ชันเครื่องกำเนิดไฟฟ้า + การเรียกซ้ำหาง + ลูปสองชั้น แต่ตำแหน่งของ for-loop แรกในโค้ดจำเป็นต้องเปลี่ยนเพื่อให้ลำดับของอาร์เรย์ย่อยของเอาต์พุตถูกต้อง รหัสคงที่: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 หากคุณต้องการทำซ้ำลำดับของรายการสินค้าคาร์ทีเซียนที่ได้รับจากความคิดเห็นของ OP การดัดแปลงของคุณนั้นถูกต้อง อย่างไรก็ตามลำดับของสิ่งที่เพิ่มขึ้นภายในผลิตภัณฑ์มักจะไม่เกี่ยวข้องเช่นในทางคณิตศาสตร์ผลลัพธ์จะเป็นเซตที่ไม่เรียงลำดับ ฉันเลือกคำสั่งนี้เพราะต้องใช้การโทรซ้ำน้อยกว่ามากดังนั้นจึงมีประสิทธิภาพมากกว่าเล็กน้อย - ฉันไม่ได้ใช้เกณฑ์มาตรฐาน
le_m

Erratum: ในความคิดเห็นของฉันด้านบน "การเรียกซ้ำหาง" ควรเป็น "การเรียกซ้ำ" (ไม่ใช่การเรียกหางในกรณีนี้)
ooo

21

นี่เป็นวิธีแก้ปัญหาแบบวนซ้ำที่ไม่หรูหราและตรงไปตรงมา:

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
อันนี้กลายเป็นโค้ด JS บริสุทธิ์ที่มีประสิทธิภาพที่สุดในหัวข้อนี้ ใช้เวลาประมาณ 600 มิลลิวินาทีในการเสร็จสิ้นในอาร์เรย์รายการ 3 x 100 เพื่อสร้างอาร์เรย์ที่มีความยาว 1M
Redu

1
ใช้ได้กับ cartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]); โดยไม่ทำให้ค่าดั้งเดิมแบนราบ
Mzn

10

นี่คือวิธีเรียกซ้ำที่ใช้ฟังก์ชันตัวสร้าง ECMAScript 2015 ดังนั้นคุณจึงไม่จำเป็นต้องสร้างสิ่งเหล่านี้ทั้งหมดพร้อมกัน:

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


สิ่งนี้จะไม่ทำงานเมื่ออาร์เรย์หนึ่งมีรายการอาร์เรย์เช่นcartesian([[1],[2]],[10,20],[100,200,300])
Redu

@Redu Answer ได้รับการอัปเดตเพื่อรองรับอาร์กิวเมนต์อาร์เรย์
heenenee

ใช่.concat()ในตัวดำเนินการแพร่กระจายบางครั้งอาจกลายเป็นการหลอกลวง
Redu

10

นี่คือหนึ่งซับใช้ flatMapES2019 ไม่จำเป็นต้องมีไลบรารีเพียงเบราว์เซอร์สมัยใหม่ (หรือตัวส่งสัญญาณ):

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

โดยพื้นฐานแล้วเป็นคำตอบของ viebel รุ่นที่ทันสมัยโดยไม่ต้องใช้ lodash


แน่นอนว่าไม่จำเป็นต้องมีห้องสมุด แต่ก็ไม่ใช่รหัสที่อ่านได้มากที่สุดเท่าที่เคยมีมา มันเป็นการแลกเปลี่ยน
Arturo Hernandez

ความสามารถในการอ่านมีสิ่งที่ต้องทำมากกว่าในกรณีนี้เมื่อฉันเลือกใช้ตัวดำเนินการการแพร่กระจายที่ฉันคิดและไม่มากนักกับการเลือกไม่ใช้ไลบรารี ฉันไม่คิดว่า lodash จะนำไปสู่รหัสที่อ่านได้มากขึ้นเลย
Fred Kleuver

9

การใช้การย้อนกลับโดยทั่วไปกับเครื่องกำเนิดไฟฟ้า 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%; }

ด้านล่างมีเวอร์ชันที่คล้ายกันซึ่งเข้ากันได้กับเบราว์เซอร์รุ่นเก่า


9

นี่คือโซลูชัน ES6 บริสุทธิ์โดยใช้ฟังก์ชันลูกศร

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

เวอร์ชันกาแฟพร้อม lodash:

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

7

แนวทางบรรทัดเดียวเพื่อการอ่านที่ดีขึ้นด้วยการเยื้อง

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

ใช้อาร์เรย์เดียวที่มีอาร์เรย์ของรายการคาร์ทีเซียนที่ต้องการ

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


1
ฉันต้องเพิ่มคำสั่งยามเพื่อจัดการกรณีที่อาร์เรย์มีองค์ประกอบเดียวอย่างถูกต้อง:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn

5

นี่คือแท็กการเขียนโปรแกรมเชิงฟังก์ชันดังนั้นมาดูรายการ monad :

แอปพลิเคชั่นหนึ่งสำหรับรายการ monadic นี้แสดงถึงการคำนวณแบบไม่ระบุตัวตน List สามารถเก็บผลลัพธ์สำหรับเส้นทางการดำเนินการทั้งหมดในอัลกอริทึม ...

ดีว่าเสียงเหมือนที่สมบูรณ์แบบcartesianเหมาะสำหรับ JavaScript ให้เราArrayและฟังก์ชั่นการผูกแบบ monadic Array.prototype.flatMapดังนั้นให้นำไปใช้ -

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

แทนที่จะเป็นloopข้างบนtสามารถเพิ่มเป็นพารามิเตอร์ 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]))


3

คำตอบสองสามคำในหัวข้อนี้ล้มเหลวเมื่ออาร์เรย์อินพุตใด ๆ มีรายการอาร์เรย์ คุณควรตรวจสอบสิ่งนั้นดีกว่า

อย่างไรก็ตามไม่จำเป็นต้องมีเครื่องหมายขีดล่างไม่ว่าอย่างไรก็ตาม ฉันเชื่อว่าสิ่งนี้ควรทำด้วย JS ES6 บริสุทธิ์ซึ่งใช้งานได้ดีเท่าที่จะทำได้

โค้ดชิ้นนี้ใช้การลดและแมปที่ซ้อนกันเพียงเพื่อรับผลคูณคาร์ทีเซียนของอาร์เรย์สองอาร์เรย์ แต่อาร์เรย์ที่สองมาจากการเรียกซ้ำไปยังฟังก์ชันเดียวกันโดยมีอาร์เรย์น้อยกว่าหนึ่งอาร์เรย์ ด้วยเหตุนี้ .. 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

ในสภาพแวดล้อมเฉพาะของฉันแนวทาง "ล้าสมัย" ดูเหมือนจะมีประสิทธิภาพมากกว่าวิธีการที่ใช้คุณสมบัติที่ทันสมัยกว่า ด้านล่างนี้คือรหัส (รวมถึงการเปรียบเทียบเล็กน้อยกับโซลูชันอื่น ๆ ที่โพสต์ในชุดข้อความนี้โดย @rsp และ @sebnukem) หากพิสูจน์ได้ว่ามีประโยชน์กับคนอื่นเช่นกัน

ความคิดกำลังตามมา สมมติว่าเรากำลังสร้างผลิตภัณฑ์ด้านนอกของNอาร์เรย์ซึ่งa_1,...,a_Nแต่ละm_iส่วนมีส่วนประกอบ ผลิตภัณฑ์ด้านนอกของอาร์เรย์เหล่านี้มีM=m_1*m_2*...*m_Nองค์ประกอบและเราสามารถระบุแต่ละของพวกเขาที่มีN-มิติเวกเตอร์องค์ประกอบของการที่เป็นจำนวนเต็มบวกและiส่วนประกอบ -th m_iมีขอบเขตอย่างเคร่งครัดจากข้างต้นโดย ตัวอย่างเช่นเวกเตอร์(0, 0, ..., 0)จะสอดคล้องกับชุดค่าผสมเฉพาะที่หนึ่งรับองค์ประกอบแรกจากแต่ละอาร์เรย์ในขณะที่(m_1-1, m_2-1, ..., m_N-1)ระบุด้วยชุดค่าผสมที่หนึ่งนำองค์ประกอบสุดท้ายจากแต่ละอาร์เรย์ ดังนั้นในการสร้างทั้งหมดM ชุดค่าผสมฟังก์ชันด้านล่างจะสร้างเวกเตอร์ดังกล่าวทั้งหมดตามลำดับและสำหรับแต่ละชุดจะระบุการรวมกันขององค์ประกอบของอาร์เรย์อินพุตที่สอดคล้องกัน

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

ด้วยnode v6.12.2ฉันได้รับการกำหนดเวลาดังต่อไปนี้:

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

3

สำหรับผู้ที่ต้องการ TypeScript (คำตอบของ Danny ที่นำมาใช้ใหม่)

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

สำหรับตัวเลือกการใช้งานง่ายจริงโดยใช้อาร์เรย์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 สมัยใหม่เพียงไม่กี่บรรทัด ไม่มีไลบรารีภายนอกหรือการอ้างอิงเช่น 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

คุณสามารถreduceอาร์เรย์ 2D ใช้flatMapกับอาร์เรย์ตัวสะสมเพื่อรับacc.length x curr.lengthจำนวนชุดค่าผสมในแต่ละลูป [].concat(c, n)ถูกใช้เนื่องจากcเป็นตัวเลขในการทำซ้ำครั้งแรกและอาร์เรย์ในภายหลัง

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

(ขึ้นอยู่กับคำตอบของ Nina Scholz )


1

วิธีการแบบไม่วนซ้ำที่เพิ่มความสามารถในการกรองและแก้ไขผลิตภัณฑ์ก่อนที่จะเพิ่มลงในชุดผลลัพธ์จริงๆ สังเกตการใช้. map แทนที่จะเป็น. forEach ในบางเบราว์เซอร์. map ทำงานได้เร็วขึ้น

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

วิธีแก้ปัญหาง่ายๆ "ใจและเป็นมิตรกับสายตา"

ใส่คำอธิบายภาพที่นี่


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

โค้ดของ @ viebel เวอร์ชันแก้ไขอย่างง่ายใน 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

การใช้งานที่อ่านได้มากขึ้น

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

นี่คือ 3 อาร์เรย์
คำตอบบางคำให้วิธีสำหรับอาร์เรย์จำนวนเท่าใดก็ได้
สิ่งนี้สามารถหดหรือขยายไปยังอาร์เรย์น้อยหรือมากกว่าได้อย่างง่ายดาย
ฉันต้องการการผสมผสานของชุดเดียวกับการทำซ้ำดังนั้นฉันจึงสามารถใช้:

f(a,a,a)

แต่ใช้:

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

0

ฉันสังเกตเห็นว่าไม่มีใครโพสต์โซลูชันที่อนุญาตให้ส่งผ่านฟังก์ชันเพื่อประมวลผลแต่ละชุดค่าผสมดังนั้นนี่คือวิธีแก้ปัญหาของฉัน:

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

เอาท์พุต:

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

0

วิธีการบังคับแบบเดรัจฉาน JS ธรรมดาที่ใช้อาร์เรย์ของอาร์เรย์เป็นอินพุต

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>

เพิ่งแปลงคำตอบของ @ dummerslจาก CoffeScript เป็น JavaScript มันใช้งานได้

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

ยังใช้งานได้อีก ไม่ใช่สั้นที่สุดหรือแฟนซี แต่เร็ว:

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

ไม่จำเป็นต้องมีห้องสมุด! :)

ต้องการฟังก์ชั่นลูกศรแม้ว่าและอาจไม่มีประสิทธิภาพ : /

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

สำหรับบันทึก

นี่คือเวอร์ชันของฉัน ฉันสร้างมันขึ้นมาโดยใช้ตัววนซ้ำจาวาสคริปต์ "for ()" ที่ง่ายที่สุดดังนั้นมันจึงเข้ากันได้กับทุกกรณีและมีประสิทธิภาพดีที่สุด

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

ขอแสดงความนับถืออย่างสูง.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.