147

你将如何在 JavaScript 中实现多个数组的笛卡尔积?

举个例子,

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

应该返回

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

35 回答 35

175

2020 年更新:使用香草 JS 的 1 行 (!) 答案

2017 年原始答案:香草 JS 的 2 行答案:( 请参阅下面的更新)

这里的所有答案都过于复杂,大部分都需要 20 行代码甚至更多。

这个例子只使用了两行 vanilla JavaScript,没有 lodash、underscore 或其他库:

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;

更新:

这与上述相同,但经过改进以严格遵循Airbnb JavaScript 样式指南- 使用ESLinteslint-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让我知道原始代码的 linter 问题。

2020 年更新:

由于我写了这个答案,我们得到了更好的内置函数,这最终可以让我们将代码减少(不是双关语)只有 1 行!

const cartesian =
  (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));

特别感谢inker建议使用 reduce。

特别感谢Bergi建议使用新添加的 flatMap。

特别感谢ECMAScript 2019为语言添加了 flat 和 flatMap!

例子

这是您问题的确切示例:

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 的特性在 2015 年 6 月发布的第 6 版 ECMA-262 标准中定义,并且开发得更早,更广为人知的是 ES6 或 ES2015。看:

ES2019 中添加了 Update 2020 示例中的新方法:

它使这样的代码变得如此简单,以至于不使用它是一种罪过。对于原生不支持它的旧平台,您始终可以使用 Babel 或其他工具将其转换为较旧的语法 - 事实上,我由 Babel 转换的示例仍然比这里的大多数示例更短更简单,但它没有真的很重要,因为转译的输出不是你需要理解或维护的东西,这只是我觉得有趣的一个事实。

结论

不需要编写数百行难以维护的代码,也不需要为这么简单的事情使用整个库,两行原生 JavaScript 就可以轻松完成工作。正如您所看到的,使用该语言的现代特性确实是值得的,如果您需要支持古老的平台而没有对现代特性的本机支持,您可以随时使用BabelTypeScript或其他工具将新语法转换为旧的。

不要像 1995 年那样编码

JavaScript 的发展是有原因的。TC39 通过添加新功能在语言设计方面做得非常出色,而浏览器供应商在实现这些功能方面做得非常出色。

要查看浏览器中任何给定功能的本机支持的当前状态,请参阅:

要查看 Node 版本中的支持,请参阅:

要在本机不支持现代语法的平台上使用现代语法,请使用 Babel 或 TypeScript:

于 2017-03-27T18:23:58.123 回答
90

这是使用and提供的问题的功能解决方案(没有任何可变变量!),由 提供:reduceflattenunderscore.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/

于 2012-09-27T19:33:36.693 回答
55

这是@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));

于 2016-03-26T10:31:02.897 回答
33

以下高效的生成器函数返回所有给定迭代的笛卡尔积:

// 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 元笛卡尔积的规范,它产生

  • []如果一个或多个给定的可迭代对象为空,例如[]''
  • [[a]]如果给出了包含单个值的单个迭代a

如以下测试用例所示,所有其他情况均按预期处理:

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

于 2017-05-16T21:49:42.280 回答
30

社区似乎认为这很简单和/或很容易找到参考实现。然而,经过短暂的检查,我找不到一个,……要么就是我喜欢重新发明轮子或解决类似课堂的编程问题。无论哪种方式,它都是你的幸运日:

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

相对高效的完整参考实现……

关于效率:您可以通过将 if 退出循环并拥有 2 个单独的循环来获得一些收益,因为它在技术上是恒定的,并且您将帮助进行分支预测和所有这些混乱,但这一点在 JavaScript 中是没有实际意义的。

于 2012-09-06T17:16:36.807 回答
22

这是一个不花哨的、直接的递归解决方案:

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

于 2015-04-12T03:40:34.920 回答
17

这是使用本机 ES2019 的单行代码flatMap。不需要库,只需一个现代浏览器(或转译器):

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

它本质上是 viebel 答案的现代版本,没有 lodash。

于 2020-01-01T20:24:32.837 回答
13

这是一种使用 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]))));

于 2016-08-24T00:22:50.637 回答
11

这是一个使用箭头函数的纯 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)));

于 2017-03-18T10:26:06.143 回答
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%; }

下面有一个与旧浏览器兼容的类似版本。

function cartesianProduct(arrays) {
  var result = [],
      current = new Array(arrays.length);
  (function backtracking(index) {
    if(index == arrays.length) return result.push(current.slice());
    for(var i=0; i<arrays[index].length; ++i) {
      current[index] = arrays[index][i];
      backtracking(index+1);
    }
  })(0);
  return result;
}
cartesianProduct([[1,2],[10,20],[100,200,300]]).forEach(function(item) {
  console.log('[' + item.join(', ') + ']');
});
div.as-console-wrapper { max-height: 100%; }

于 2015-04-12T03:50:32.250 回答
7

带有 lodash 的咖啡脚本版本:

_ = require("lodash")
cartesianProduct = ->
    return _.reduceRight(arguments, (a,b) ->
        _.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
    , [ [] ])
于 2014-03-02T23:38:50.317 回答
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; }

于 2018-05-31T19:27:49.820 回答
6

这是标记为函数式编程的,所以让我们看一下List monad

这个单子列表的一个应用是表示非确定性计算。List 可以保存算法中所有执行路径的结果...

好吧,这听起来很适合cartesianJavaScript 给了我们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可以添加为咖喱参数 -

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

于 2019-04-16T15:09:24.423 回答
4

你可以reduce二维数组。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 的回答

于 2019-08-21T18:40:50.103 回答
3

当任何输入数组包含数组项时,本主题下的一些答案会失败。你最好检查一下。

无论如何都不需要下划线,lodash。我相信这个应该用纯 JS ES6 来做,尽可能地实用。

这段代码使用reduce和嵌套映射,只是为了获得两个数组的笛卡尔积,但是第二个数组来自对具有较少数组的同一函数的递归调用;因此.. 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))); 

于 2016-10-02T16:10:03.040 回答
3

在我的特定环境中,“老式”方法似乎比基于更现代特征的方法更有效。下面是代码(包括与@rsp 和@sebnukem 在此线程中发布的其他解决方案的小比较),它是否也对其他人有用。

思路如下。假设我们正在构建数组的外积N,每个数组a_1,...,a_N都有m_i组件。这些数组的外积有M=m_1*m_2*...*m_N元素,我们可以用一个维向量来识别它们中的每一个,N-维向量的分量是正整数,i第一个分量从上面严格限定为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
于 2017-12-12T14:12:53.753 回答
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[][])
}
于 2019-03-05T11:04:41.217 回答
3

现代 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')
);

于 2019-08-12T17:38:18.367 回答
3

另一个更简化的 2021 式答案,仅使用 reduce、map 和 concat 方法:

const cartesian = (...arr) => arr.reduce((a,c) => a.map(e => c.map(f => e.concat([f]))).reduce((a,c) => a.concat(c), []), [[]]);

console.log(cartesian([1, 2], [10, 20], [100, 200, 300]));

于 2021-04-07T02:58:42.390 回答
2

对于那些对 ramda 解决方案感到满意的人:

import { xprod, flatten } from 'ramda';

const cartessian = (...xs) => xs.reduce(xprod).map(flatten)

或者没有依赖关系和两个免费的乐高积木(xprodflatten)相同:

const flatten = xs => xs.flat();

const xprod = (xs, ys) => xs.flatMap(x => ys.map(y => [x, y]));

const cartessian = (...xs) => xs.reduce(xprod).map(flatten);

于 2020-12-07T13:55:11.783 回答
2

flatMap这是一个仅使用and的递归单行代码map

const inp = [
  [1, 2],
  [10, 20],
  [100, 200, 300]
];

const cartesian = (first, ...rest) => 
  rest.length ? first.flatMap(v => cartesian(...rest).map(c => [v].concat(c))) 
              : first;

console.log(cartesian(...inp));

于 2021-04-07T04:48:12.940 回答
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;
}
于 2016-03-17T09:13:51.127 回答
1

只是为了选择一个使用数组的真正简单的实现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))), []);
于 2018-01-31T14:21:28.223 回答
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']
));
于 2018-03-17T12:07:52.127 回答
1

又一个实现。不是最短或花哨的,但很快:

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;
}
于 2019-01-05T18:44:38.017 回答
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 ] ];
*/
于 2019-02-10T20:56:12.290 回答
1

不需要图书馆!:)

虽然需要箭头功能,但可能效率不高。:/

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

于 2019-04-09T07:47:25.093 回答
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));

于 2019-10-16T12:49:01.820 回答
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])))
于 2020-03-25T13:01:03.890 回答
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?
于 2017-09-20T22:19:05.680 回答
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) )
于 2018-03-26T08:15:17.247 回答
0

作为记录

这是我的版本。我使用最简单的 javascript 迭代器“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;
}

最好的祝福。

于 2019-04-15T18:17:53.337 回答
0

2021 年版 David Tang 的精彩回答
也受到了 Neil Mountford 的回答的启发

  • 快(比接受的答案快 95%)
  • 适用于任何数据类型

const getAllCombinations = (arraysToCombine) => {
  const divisors = [];
  let combinationsCount = 1;
  for (let i = arraysToCombine.length - 1; i >= 0; i--) {
      divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
      combinationsCount *= (arraysToCombine[i].length || 1);
  }

  const getCombination = (n, arrays, divisors) => arrays.reduce((acc, arr, i) => {
      acc.push(arr[Math.floor(n / divisors[i]) % arr.length]);
      return acc;
  }, []);

  const combinations = [];
  for (let i = 0; i < combinationsCount; i++) {
      combinations.push(getCombination(i, arraysToCombine, divisors));
  }
  return combinations;
};

console.log(getAllCombinations([['a', 'b'], ['c', 'z'], ['d', 'e', 'f']]));

基准测试:https ://jsbench.me/gdkmxhm36d/1

于 2021-03-31T14:14:27.870 回答
-1

将数组作为输入的普通 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]
]);
于 2018-02-22T05:05:48.620 回答
-1

实现这一点最简单的方法是使用 ES6 方式。所有人都必须了解维恩图的工作原理。因此,类似地,ES6 为我们提供了一种对任何类型的数据结构执行所有集合操作的方法。

让我们看看下面的例子

var arr1 = [1,2,2,3,5] var arr2 = [2,3,5,6,7]

arr1.filter(x => arr2.includes(x))

结果 => [2,2,3,5]

我们可以看到结果中重复了“2”,这可以通过使用上述答案中提到的任何一个数组上提供的 Set Class 来避免

[... new Set(arr1)].filter(x => arr2.includes(x))

结果 => [2,3,5]

于 2021-06-01T14:10:08.017 回答