Tích Descartes của nhiều mảng trong JavaScript


111

Bạn sẽ triển khai sản phẩm Descartes của nhiều mảng trong JavaScript như thế nào?

Ví dụ,

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

nên trở lại

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


3
Điều này được triển khai trong mô-đun js-combinatorics: github.com/dankogai/js-combinatorics
Erel Segal-Halevi


Tôi đồng ý về underscore.js nhưng tôi không chắc mình thấy cách xóa thẻ lập trình chức năng sẽ giúp ích như thế nào @le_m
viebel

Fwiw, d3 được thêm d3.cross(a, b[, reducer])vào tháng Hai. github.com/d3/d3-array#cross
Toph

Câu trả lời:


105

Cập nhật năm 2017: Câu trả lời 2 dòng với vani JS

Tất cả các câu trả lời ở đây đều quá phức tạp , hầu hết chúng mất 20 dòng mã hoặc thậm chí nhiều hơn.

Ví dụ này chỉ sử dụng hai dòng JavaScript vani , không có dấu gạch ngang, dấu gạch dưới hoặc các thư viện khác:

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;

Cập nhật:

Điều này tương tự như trên nhưng được cải thiện để tuân thủ nghiêm ngặt Hướng dẫn tạo kiểu JavaScript của Airbnb - được xác thực bằng ESLint với 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);

Đặc biệt cảm ơn ZuBB đã cho tôi biết về sự cố linter với mã gốc.

Thí dụ

Đây là ví dụ chính xác từ câu hỏi của bạn:

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

Đầu ra

Đây là đầu ra của lệnh đó:

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

Bản giới thiệu

Xem các bản trình diễn trên:

Cú pháp

Cú pháp mà tôi sử dụng ở đây không có gì mới. Ví dụ của tôi sử dụng toán tử spread và các tham số còn lại - các tính năng của JavaScript được định nghĩa trong ấn bản thứ 6 của tiêu chuẩn ECMA-262 được xuất bản vào tháng 6 năm 2015 và được phát triển trước đó nhiều, được biết đến nhiều hơn với tên gọi ES6 hoặc ES2015. Xem:

Nó làm cho mã như thế này đơn giản đến mức không sử dụng nó là một tội lỗi. Đối với các nền tảng cũ không hỗ trợ nó, bạn luôn có thể sử dụng Babel hoặc các công cụ khác để chuyển nó sang cú pháp cũ hơn - và trên thực tế, ví dụ của tôi được chuyển bằng Babel vẫn ngắn hơn và đơn giản hơn hầu hết các ví dụ ở đây, nhưng nó không thực sự quan trọng bởi vì đầu ra của chuyển đổi không phải là thứ mà bạn cần phải hiểu hoặc duy trì, đó chỉ là một thực tế mà tôi thấy thú vị.

Phần kết luận

Không cần phải viết hàng trăm dòng mã khó bảo trì và không cần phải sử dụng toàn bộ thư viện cho một việc đơn giản như vậy, khi hai dòng JavaScript vani có thể dễ dàng hoàn thành công việc. Như bạn có thể thấy, việc sử dụng các tính năng hiện đại của ngôn ngữ thực sự mang lại hiệu quả và trong trường hợp bạn cần hỗ trợ các nền tảng cổ xưa mà không có hỗ trợ gốc của các tính năng hiện đại, bạn luôn có thể sử dụng Babel hoặc các công cụ khác để chuyển cú pháp mới sang cú pháp cũ .

Đừng viết mã như năm 1995

JavaScript phát triển và nó làm như vậy là có lý do. TC39 thực hiện một công việc tuyệt vời của thiết kế ngôn ngữ với việc thêm các tính năng mới và các nhà cung cấp trình duyệt đã thực hiện một công việc đáng kinh ngạc trong việc triển khai các tính năng đó.

Để xem trạng thái hỗ trợ gốc hiện tại của bất kỳ tính năng cụ thể nào trong trình duyệt, hãy xem:

Để xem hỗ trợ trong các phiên bản Node, hãy xem:

Để sử dụng cú pháp hiện đại trên các nền tảng không hỗ trợ nó, hãy sử dụng Babel:


Đây là một phiên bản sắp chữ với một chút thay đổi để giải thích cho cách phân loại mảng thực hiện việc dàn trải mảng. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe

1
@rsp cảm ơn rất nhiều vì câu trả lời thực sự tốt. mặc dù tôi muốn hỏi bạn để cải thiện nó một chút để có được giàn cảnh báo của các biến shadowed (2 vars địa phương avà 2 vars địa phương b)
ZuBB

7
"Đừng code như 1995" - không cần phải khó chịu, không phải ai cũng đã bắt kịp.
Godwhacker

7
Điều này là tốt nhưng không thành công khi được cho ăn ['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 ] ]kết quả là sẽ mang lại kết quả. Ý tôi là sẽ không giữ loại mặt hàng của [[9], [10]].
Redu

1
Vì chúng tôi đang sử dụng ...rồi, không [].concat(...[array])nên trở nên đơn giản [...array]?
Lazar Ljubenović

88

Đây là một giải pháp chức năng cho vấn đề (không có bất kỳ biến có thể thay đổi nào !) Bằng cách sử dụng reduceflatten, được cung cấp bởi 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>

Ghi chú: Giải pháp này được lấy cảm hứng từ http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/


Có một lỗi đánh máy trong câu trả lời này, có không phải là một "true" (có thể lodash đã thay đổi kể từ khi bạn thực hiện bài viết này?)
Chris Jefferson

@ChrisJefferson tham số thứ hai flattenlà làm cho việc làm phẳng nông hơn. Nó là bắt buộc ở đây!
viebel

4
Xin lỗi, đây là sự không tương thích của dấu gạch ngang / dấu gạch dưới, họ đã đổi chỗ cho lá cờ.
Chris Jefferson

1
Vì vậy, khi làm phẳng, sử dụng truevới gạch dưới và sử dụng falsevới gạch để đảm bảo làm phẳng nông.
Akseli Palén

Làm cách nào để sửa đổi hàm này để nó chấp nhận mảng mảng?

44

Đây là phiên bản sửa đổi của mã @ viebel bằng Javascript thuần túy mà không sử dụng bất kỳ thư viện nào:

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
Lỗi đối với cartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]) khi nó làm phẳng ['gamma'] thành 'gamma' và [['alpha']] thành ['alpha']
Mzn 15/11/17

bởi vì .concat(y)thay vì.concat([ y ])
Cảm ơn bạn

@Thankyou bạn có thể chỉnh sửa câu trả lời trực tiếp thay vì bình luận, chỉ cần làm điều đó nên không cần bây giờ: P
Olivier Lalonde

28

Có vẻ như cộng đồng nghĩ rằng điều này là tầm thường và hoặc dễ dàng để tìm thấy một triển khai tham chiếu, sau khi kiểm tra ngắn gọn, tôi không thể hoặc có thể chỉ là tôi thích phát minh lại bánh xe hoặc giải quyết các vấn đề lập trình giống như trong lớp học theo cách của ngày may mắn của bạn :

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

triển khai tham chiếu đầy đủ tương đối hiệu quả ... :-D

về hiệu quả: bạn có thể đạt được một số bằng cách lấy if ra khỏi vòng lặp và có 2 vòng lặp riêng biệt vì nó không đổi về mặt kỹ thuật và bạn sẽ giúp dự đoán nhánh và tất cả những thứ lộn xộn đó, nhưng điểm đó là một cuộc tranh luận trong javascript

bất kỳ ai, thưởng thức -ck


1
Cảm ơn @ckoz vì câu trả lời chi tiết của bạn. Tại sao bạn không sử dụng reducehàm của mảng?
viebel

1
@viebel tại sao bạn muốn sử dụng giảm? đối với một, giảm có hỗ trợ rất kém cho các trình duyệt cũ hơn (xem: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ) và trong mọi trường hợp, mã điên rồ từ câu trả lời khác đó thực sự trông có thể đọc được đối với bạn ? nó không với tôi. chắc chắn rằng nó ngắn hơn, nhưng sau khi rút gọn mã này sẽ có cùng độ dài, dễ gỡ lỗi / tối ưu hóa hơn, thứ hai là tất cả các giải pháp "giảm thiểu" đó chia thành cùng một thứ, ngoại trừ chúng có tra cứu đóng (chậm hơn về mặt lý thuyết), nó cũng khó hơn để thiết kế nên nó xử lý tập hợp vô hạn ...
ckozl

5
Tôi đã tạo phiên bản sạch hơn và nhanh hơn 2 lần và (imo): pastebin.com/YbhqZuf7 Nó đạt được tốc độ tăng khi không sử dụng result = result.concat(...)và không sử dụng args.slice(1). Thật không may, tôi không thể tìm ra cách để loại bỏ curr.slice()và đệ quy.
Pauan

2
@Pauan làm tốt lắm, giảm toàn bộ các điểm nóng trong giải đấu để tăng hiệu suất 10% -50% dựa trên những gì tôi đang thấy. Tuy nhiên, tôi không thể nói về "độ sạch", tôi cảm thấy phiên bản của bạn thực sự khó theo dõi hơn do sử dụng các biến phạm vi đóng. Nhưng nói chung, mã hiệu suất cao hơn sẽ khó theo dõi hơn. Tôi đã viết phiên bản gốc để có thể đọc, tôi ước gì có nhiều thời gian để tôi có thể tham gia bạn trong một buổi biểu diễn ném xuống;) có lẽ sau ...
ckozl

điều này thực sự là một trong những vấn đề
James

26

Hàm trình tạo hiệu quả sau đây trả về tích cacte của tất cả các vòng lặp đã cho :

// 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ó chấp nhận mảng, chuỗi, tập hợp và tất cả các đối tượng khác thực hiện giao thức có thể lặp lại .

Theo đặc điểm kỹ thuật của sản phẩm cacte n-ary, nó tạo ra

  • []nếu một hoặc nhiều vòng lặp đã cho trống, ví dụ: []hoặc''
  • [[a]]nếu một tệp có thể lặp lại chứa một giá trị duy nhất ađược đưa ra.

Tất cả các trường hợp khác được xử lý như mong đợi như được chứng minh bằng các trường hợp thử nghiệm sau:


Bạn có vui lòng giải thích những gì đang xảy ra trên cái này không? Cảm ơn rất nhiều!
LeandroP

Cảm ơn bạn đã dạy chúng tôi một ví dụ khá tuyệt vời về việc sử dụng hàm trình tạo + đệ quy đuôi + vòng lặp hai lớp! Nhưng vị trí của vòng lặp for đầu tiên trong mã cần được thay đổi để làm cho thứ tự của các mảng con đầu ra chính xác. Mã cố định: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 Nếu bạn muốn sao chép thứ tự của các bộ sản phẩm cacte theo nhận xét của OP, thì sửa đổi của bạn là đúng. Tuy nhiên, thứ tự của các bộ giá trị trong sản phẩm thường không liên quan, ví dụ về mặt toán học, kết quả là một bộ không có thứ tự. Tôi chọn thứ tự này vì nó yêu cầu ít cuộc gọi đệ quy hơn nhiều và do đó hiệu suất cao hơn một chút - mặc dù vậy, tôi đã không chạy điểm chuẩn.
le_m

Erratum: Trong nhận xét của tôi ở trên, "đệ quy đuôi" nên là "đệ quy" (không phải là lệnh gọi đuôi trong trường hợp này).
ooo

20

Đây là một giải pháp đệ quy đơn giản, đơn giản:

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
Đây hóa ra là mã JS thuần hiệu quả nhất theo chủ đề này. Phải mất khoảng ~ 600msec để hoàn thành trên mảng 3 x 100 mục để tạo ra một mảng có độ dài 1M.
Redu

1
Hoạt động cho CartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]); mà không làm phẳng giá trị ban đầu
MZN

10

Đây là một cách đệ quy sử dụng hàm trình tạo ECMAScript 2015 để bạn không phải tạo tất cả các bộ giá trị cùng một lúc:

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


Điều này sẽ không hoạt động khi một trong những mảng có các mục mảng nhưcartesian([[1],[2]],[10,20],[100,200,300])
Redu

@Redu Answer đã được cập nhật để hỗ trợ các đối số mảng.
heenenee

.concat()xây dựng trong toán tử lây lan đôi khi có thể trở nên gian dối.
Redu

10

Đây là một lớp lót sử dụng ES2019 gốc flatMap. Không cần thư viện, chỉ cần một trình duyệt hiện đại (hoặc trình chuyển tiếp):

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

Về cơ bản, nó là một phiên bản hiện đại của câu trả lời của viebel, không có lodash.


9

Sử dụng một backtracking điển hình với máy phát 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%; }

Bên dưới có một phiên bản tương tự tương thích với các trình duyệt cũ hơn.


9

Đây là một giải pháp ES6 thuần túy sử dụng các hàm mũi tên

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

Một phiên bản coffeescript với lodash:

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

7

Phương pháp tiếp cận một dòng, để đọc tốt hơn với các thụt lề.

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

Nó có một mảng duy nhất với các mảng các mặt hàng cacte muốn.

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


Tôi đã có thêm một tuyên bố bảo vệ để xử lý một cách chính xác các trường hợp mảng có một yếu tố duy nhất:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn

5

Đây là lập trình chức năng được gắn thẻ vì vậy chúng ta hãy xem xét đơn nguyên Danh sách :

Một ứng dụng cho danh sách đơn nguyên này là đại diện cho tính toán không xác định. List có thể giữ kết quả cho tất cả các đường dẫn thực thi trong một thuật toán ...

Điều đó nghe có vẻ như là một sự phù hợp hoàn hảo cho cartesian. JavaScript cung cấp cho chúng ta Arrayvà chức năng ràng buộc đơn nguyên là Array.prototype.flatMapvậy, vì vậy hãy sử dụng chúng -

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

Thay vì loopở trên, tcó thể được thêm vào dưới dạng tham số cà ri -

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

Một số câu trả lời trong chủ đề này không thành công khi bất kỳ mảng đầu vào nào chứa một mục mảng. Bạn nên kiểm tra điều đó.

Dù sao thì không cần gạch dưới, gạch dưới bất cứ điều gì. Tôi tin rằng điều này nên làm điều đó với JS ES6 thuần túy, chức năng như nó có.

Đoạn mã này sử dụng một bản đồ rút gọn và lồng nhau, chỉ đơn giản để lấy tích các-ten của hai mảng tuy nhiên mảng thứ hai đến từ một lời gọi đệ quy đến cùng một hàm với một mảng nhỏ hơn; vì thế.. 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

Trong bối cảnh cụ thể của tôi, cách tiếp cận "kiểu cũ" dường như hiệu quả hơn các phương pháp dựa trên các tính năng hiện đại hơn. Dưới đây là mã (bao gồm một so sánh nhỏ với các giải pháp khác được đăng trong chủ đề này bởi @rsp và @sebnukem) nếu nó cũng hữu ích cho người khác.

Ý tưởng là sau đây. Giả sử chúng ta đang xây dựng sản phẩm bên ngoài của Ncác mảng, a_1,...,a_Nmỗi mảng đều có m_icác thành phần. Tích ngoài của các mảng này có M=m_1*m_2*...*m_Ncác phần tử và chúng ta có thể xác định từng phần tử trong số chúng bằng N-vectơ chiều, các thành phần của chúng là các số nguyên dương và ithành phần -th được giới hạn chặt chẽ từ phía trên bởi m_i. Ví dụ: vectơ (0, 0, ..., 0)sẽ tương ứng với tổ hợp cụ thể mà trong đó một tổ hợp lấy phần tử đầu tiên từ mỗi mảng, trong khi (m_1-1, m_2-1, ..., m_N-1)được xác định với tổ hợp trong đó một tổ hợp lấy phần tử cuối cùng từ mỗi mảng. Do đó, để xây dựng tất cảM kết hợp, hàm dưới đây tạo liên tiếp tất cả các vectơ như vậy và đối với mỗi vectơ đó xác định tổ hợp tương ứng của các phần tử của mảng đầu vào.

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

với node v6.12.2, tôi nhận được thời gian sau:

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

3

Đối với những người cần TypeScript (câu trả lời của @ Danny được thực hiện lại)

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

Chỉ cho một lựa chọn, một triển khai đơn giản thực sự bằng cách sử dụng mảng 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 hiện đại chỉ trong một vài dòng. Không có thư viện bên ngoài hoặc phụ thuộc như 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

Bạn có thể reducemảng 2D. Sử dụng flatMaptrên mảng tích lũy để nhận acc.length x curr.lengthsố lượng kết hợp trong mỗi vòng lặp. [].concat(c, n)được sử dụng vì clà một số trong lần lặp đầu tiên và một mảng sau đó.

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

(Điều này dựa trên câu trả lời của Nina Scholz )


1

Phương pháp tiếp cận không đệ quy bổ sung khả năng lọc và sửa đổi các sản phẩm trước khi thực sự thêm chúng vào tập kết quả. Lưu ý việc sử dụng .map thay vì .forEach. Trong một số trình duyệt, .map chạy nhanh hơn.

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

Một giải pháp đơn giản "thân thiện với thị giác và trí óc".

nhập mô tả hình ảnh ở đây


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

Một phiên bản đơn giản, được sửa đổi của mã @ viebel bằng Javascript thuần túy:

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

Cách triển khai dễ đọc hơn

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

Đây là cho 3 mảng.
Một số câu trả lời đưa ra một cách cho bất kỳ số mảng nào.
Điều này có thể dễ dàng thu hẹp hoặc mở rộng đến ít hoặc nhiều mảng.
Tôi cần các kết hợp của một tập hợp với các lần lặp lại, vì vậy tôi có thể đã sử dụng:

f(a,a,a)

nhưng đã sử dụng:

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

0

Tôi nhận thấy rằng không ai đăng giải pháp cho phép truyền một hàm để xử lý từng tổ hợp, vì vậy đây là giải pháp của tôi:

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

Đầu ra:

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

0

Phương pháp tiếp cận brute force JS thuần túy lấy một mảng mảng làm đầu vào.

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>

Chỉ cần chuyển đổi câu trả lời của @ dummersl từ CoffeScript sang JavaScript. Nó chỉ hoạt động.

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

Tuy nhiên, một triển khai khác. Không phải ngắn nhất hoặc lạ mắt, nhưng nhanh chóng:

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

Không cần thư viện! :)

Cần các chức năng mũi tên và có lẽ không hiệu quả. : /

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

Đối với hồ sơ

Đây là phiên bản của tôi về nó. Tôi đã tạo nó bằng cách sử dụng trình lặp javascript đơn giản nhất "for ()", vì vậy nó tương thích với mọi trường hợp và có hiệu suất tốt nhất.

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

Trân trọng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.