简体   繁体   English

JavaScript中多个数组的笛卡尔积

[英]Cartesian product of multiple arrays in JavaScript

How would you implement the Cartesian product of multiple arrays in JavaScript?你将如何在 JavaScript 中实现多个数组的笛卡尔积?

As an example,举个例子,

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

should return应该返回

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

2020 Update: 1-line (!) answer with vanilla JS 2020 年更新:使用香草 JS 的 1 行 (!) 答案

Original 2017 Answer: 2-line answer with vanilla JS: (see updates below) 2017 年原始答案:香草 JS 的 2 行答案:(请参阅下面的更新)

All of the answers here are overly complicated , most of them take 20 lines of code or even more.这里的所有答案都过于复杂,大部分都需要 20 行代码甚至更多。

This example uses just two lines of vanilla JavaScript , no lodash, underscore or other libraries:这个例子只使用了两行 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;

Update:更新:

This is the same as above but improved to strictly follow the Airbnb JavaScript Style Guide - validated using ESLint with eslint-config-airbnb-base :这与上述相同,但经过改进以严格遵循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);

Special thanks to ZuBB for letting me know about linter problems with the original code.特别感谢ZuBB让我知道原始代码的 linter 问题。

Update 2020: 2020 年更新:

Since I wrote this answer we got even better builtins, that can finally let us reduce (no pun intended) the code to just 1 line!由于我写了这个答案,我们得到了更好的内置函数,这最终可以让我们将代码减少(不是双关语)只有 1 行!

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

Special thanks to inker for suggesting the use of reduce.特别感谢inker建议使用 reduce。

Special thanks to Bergi for suggesting the use of the newly added flatMap.特别感谢Bergi建议使用新添加的 flatMap。

Special thanks to ECMAScript 2019 for adding flat and flatMap to the language!特别感谢ECMAScript 2019为语言添加了 flat 和 flatMap!

Example例子

This is the exact example from your question:这是您问题的确切示例:

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

Output输出

This is the output of that command:这是该命令的输出:

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

Demo演示

See demos on:请参阅以下演示:

Syntax句法

The syntax that I used here is nothing new.我在这里使用的语法并不新鲜。 My example uses the spread operator and the rest parameters - features of JavaScript defined in the 6th edition of the ECMA-262 standard published on June 2015 and developed much earlier, better known as ES6 or ES2015.我的示例使用了扩展运算符和其余参数——JavaScript 的特性在 2015 年 6 月发布的第 6 版 ECMA-262 标准中定义,并且开发得更早,更广为人知的是 ES6 或 ES2015。 See:看:

The new methods from the Update 2020 example was added in ES2019: ES2019 中添加了 Update 2020 示例中的新方法:

It makes code like this so simple that it's a sin not to use it.它使这样的代码变得如此简单,以至于不使用它是一种罪过。 For old platforms that don't support it natively you can always use Babel or other tools to transpile it to older syntax - and in fact my example transpiled by Babel is still shorter and simpler than most of the examples here, but it doesn't really matter because the output of transpilation is not something that you need to understand or maintain, it's just a fact that I found interesting.对于原生不支持它的旧平台,您始终可以使用 Babel 或其他工具将其转换为较旧的语法 - 事实上,我由 Babel 转换的示例仍然比这里的大多数示例更短更简单,但它没有真的很重要,因为转译的输出不是你需要理解或维护的东西,这只是我觉得有趣的一个事实。

Conclusion结论

There's no need to write hundred of lines of code that is hard to maintain and there is no need to use entire libraries for such a simple thing, when two lines of vanilla JavaScript can easily get the job done.不需要编写数百行难以维护的代码,也不需要为这么简单的事情使用整个库,两行原生 JavaScript 就可以轻松完成工作。 As you can see it really pays off to use modern features of the language and in cases where you need to support archaic platforms with no native support of the modern features you can always use Babel , TypeScript or other tools to transpile the new syntax to the old one.正如您所看到的,使用该语言的现代特性确实是值得的,如果您需要支持古老的平台而没有对现代特性的本机支持,您可以随时使用BabelTypeScript或其他工具将新语法转换为旧的。

Don't code like it's 1995不要像 1995 年那样编码

JavaScript evolves and it does so for a reason. JavaScript 的发展是有原因的。 TC39 does an amazing job of the language design with adding new features and the browser vendors do an amazing job of implementing those features. TC39 通过添加新功能在语言设计方面做得非常出色,浏览器供应商在实现这些功能方面做得非常出色。

To see the current state of native support of any given feature in the browsers, see:要查看浏览器中任何给定功能的本机支持的当前状态,请参阅:

To see the support in Node versions, see:要查看 Node 版本中的支持,请参阅:

To use modern syntax on platforms that don't support it natively, use Babel or TypeScript:要在本机不支持现代语法的平台上使用现代语法,请使用 Babel 或 TypeScript:

Here is a functional solution to the problem (without any mutable variable !) using reduce and flatten , provided by underscore.js :下面是使用reduceflatten解决问题的功能解决方案(没有任何可变变量!),由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>

Remark: This solution was inspired by http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/备注:此解决方案的灵感来自http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/

Here's a modified version of @viebel's code in plain Javascript, without using any library:这是@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));

The following efficient generator function returns the cartesian product of all given 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]));

It accepts arrays, strings, sets and all other objects implementing the iterable protocol .它接受数组、字符串、集合和所有其他实现可迭代协议的对象。

Following the specification of the n-ary cartesian product it yields遵循n 元笛卡尔积的规范,它产生

  • [] if one or more given iterables are empty, eg [] or '' []如果一个或多个给定的迭代是空的,例如[]''
  • [[a]] if a single iterable containing a single value a is given. [[a]]如果给定包含单个值a的单个迭代。

All other cases are handled as expected as demonstrated by the following test cases:如以下测试用例所示,所有其他情况均按预期处理:

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

It seems the community thinks this to be trivial and/or easy to find a reference implementation.社区似乎认为这很简单和/或很容易找到参考实现。 However, upon brief inspection I couldn't find one, … either that or maybe it's just that I like re-inventing the wheel or solving classroom-like programming problems.然而,经过短暂的检查,我找不到一个,……要么就是我喜欢重新发明轮子或解决类似课堂的编程问题。 Either way its your lucky day:无论哪种方式,它都是你的幸运日:

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

Full reference implementation that's relatively efficient… 😁相对高效的完整参考实现......😁

On efficiency: You could gain some by taking the if out of the loop and having 2 separate loops since it is technically constant and you'd be helping with branch prediction and all that mess, but that point is kind of moot in JavaScript.关于效率:您可以通过将 if 退出循环并拥有 2 个单独的循环来获得一些收益,因为它在技术上是恒定的,并且您将帮助进行分支预测和所有这些混乱,但这一点在 JavaScript 中是没有实际意义的。

Here's a non-fancy, straightforward recursive solution:这是一个不花哨的、直接的递归解决方案:

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

Here is a one-liner using the native ES2019 flatMap .这是一个使用原生 ES2019 flatMap的单线器。 No libraries needed, just a modern browser (or transpiler):不需要库,只需一个现代浏览器(或转译器):

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

It's essentially a modern version of viebel's answer, without lodash.它本质上是 viebel 答案的现代版本,没有 lodash。

Here is a recursive way that uses an ECMAScript 2015 generator function so you don't have to create all of the tuples at once:这是一种使用 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]))));

This is a pure ES6 solution using arrow functions这是一个使用箭头函数的纯 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)));

Using a typical backtracking with ES6 generators,使用 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%; }

Below there is a similar version compatible with older browsers.下面有一个与旧浏览器兼容的类似版本。

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

A coffeescript version with lodash:带有 lodash 的咖啡脚本版本:

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

A single line approach, for better reading with indentations.单行方法,以便更好地阅读缩进。

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

It takes a single array with arrays of wanted cartesian items.它需要一个带有所需笛卡尔项目数组的数组。

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

functional programming函数式编程

This question is tagged functional-programming so let's take a look at the List monad :这个问题被标记为函数式编程,所以让我们看一下List monad

One application for this monadic list is representing nondeterministic computation.这个单子列表的一个应用是表示非确定性计算。 List can hold results for all execution paths in an algorithm... List可以保存算法中所有执行路径的结果...

Well that sounds like a perfect fit for cartesian .好吧,这听起来很适合cartesian JavaScript gives us Array and the monadic binding function is Array.prototype.flatMap , so let's put them to use - JavaScript 为我们提供了Array并且一元绑定函数是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]))

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

more recursion更多递归

Other recursive implementations include -其他递归实现包括 -

 const cartesian = (a, ...more) => a == null ? [[]] : cartesian(...more).flatMap(c => a.map(v => [v,...c])) for (const p of cartesian([1,2], [10,20], [100,200,300])) console.log(JSON.stringify(p))
 .as-console-wrapper { min-height: 100%; top: 0; }

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

Note the different order above.请注意上面的不同顺序。 You can get lexicographic order by inverting the two loops.您可以通过反转两个循环来获得字典顺序 Be careful not avoid duplicating work by calling cartesian inside the loop like Nick's answer -小心不要通过像尼克的回答那样在循环内调用cartesian来避免重复工作 -

 const bind = (x, f) => f(x) const cartesian = (a, ...more) => a == null ? [[]] : bind(cartesian(...more), r => a.flatMap(v => r.map(c => [v,...c]))) for (const p of cartesian([1,2], [10,20], [100,200,300])) console.log(JSON.stringify(p))
 .as-console-wrapper { min-height: 100%; top: 0; }

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

generators发电机

Another option is to use generators.另一种选择是使用生成器。 A generator is a good fit for combinatorics because the solution space can become very large.生成器非常适合组合数学,因为解空间可以变得非常大。 Generators offer lazy evaluation so they can be paused/resumed/canceled at any time -生成器提供惰性评估,因此它们可以随时暂停/恢复/取消 -

 function* cartesian(a, ...more) { if (a == null) return yield [] for (const v of a) for (const c of cartesian(...more)) // ⚠️ yield [v, ...c] } for (const p of cartesian([1,2], [10,20], [100,200,300])) console.log(JSON.stringify(p))
 .as-console-wrapper { min-height: 100%; top: 0; }

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

Maybe you saw that we called cartesian in a loop in the generator.也许你看到我们在生成器的循环中调用了cartesian If you suspect that can be optimized, it can!如果你怀疑可以优化,它可以! Here we use a generic tee function that forks any iterator n times -在这里,我们使用一个通用的tee函数,它将任何迭代器分叉n次 -

function* cartesian(a, ...more) {
  if (a == null) return yield []
  for (const t of tee(cartesian(...more), a.length)) // ✅
    for (const v of a)
      for (const c of t) // ✅
        yield [v, ...c]
}

Where tee is implemented as -其中tee实现为 -

function tee(g, n = 2) {
  const memo = []
  function* iter(i) {
    while (true) {
      if (i >= memo.length) {
        const w = g.next()
        if (w.done) return
        memo.push(w.value)
      }
      else yield memo[i++]
    }
  }
  return Array.from(Array(n), _ => iter(0))
}

Even in small tests cartesian generator implemented with tee performs twice as fast.即使在小型测试中,使用tee实现的cartesian生成器也能以两倍的速度执行。

For those who needs TypeScript (reimplemented @Danny's answer)对于那些需要 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[][])
}

You could reduce the 2D array.您可以reduce二维数组。 Use flatMap on the accumulator array to get acc.length x curr.length number of combinations in each loop.在累加器数组上使用flatMap以获取每个循环中的acc.length x curr.length组合数。 [].concat(c, n) is used because c is a number in the first iteration and an array afterwards.使用[].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))

(This is based on Nina Scholz's answer ) (这是基于Nina Scholz 的回答

Here's a recursive one-liner that works using only flatMap and map :这是一个仅使用flatMapmap工作的递归单线:

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

A few of the answers under this topic fail when any of the input arrays contains an array item.当任何输入数组包含数组项时,本主题下的一些答案会失败。 You you better check that.你最好检查一下。

Anyways no need for underscore, lodash whatsoever.无论如何都不需要下划线,lodash。 I believe this one should do it with pure JS ES6, as functional as it gets.我相信这个应该用纯 JS ES6 来做,尽可能地实用。

This piece of code uses a reduce and a nested map, simply to get the cartesian product of two arrays however the second array comes from a recursive call to the same function with one less array;这段代码使用reduce和嵌套映射,只是为了获得两个数组的笛卡尔积,但是第二个数组来自对具有较少数组的同一函数的递归调用; hence.. a[0].cartesian(...a.slice(1))因此.. 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)));

In my particular setting, the "old-fashioned" approach seemed to be more efficient than the methods based on more modern features.在我的特定环境中,“老式”方法似乎比基于更现代特征的方法更有效。 Below is the code (including a small comparison with other solutions posted in this thread by @rsp and @sebnukem) should it prove useful to someone else as well.下面是代码(包括与@rsp 和@sebnukem 在此线程中发布的其他解决方案的小比较),如果它也证明对其他人有用。

The idea is following.思路如下。 Let's say we are constructing the outer product of N arrays, a_1,...,a_N each of which has m_i components.假设我们正在构建N个数组的外积a_1,...,a_N每个数组都有m_i个分量。 The outer product of these arrays has M=m_1*m_2*...*m_N elements and we can identify each of them with a N- dimensional vector the components of which are positive integers and i -th component is strictly bounded from above by m_i .这些数组的外积有M=m_1*m_2*...*m_N元素,我们可以用一个N-维向量来识别它们中的每一个,该向量的分量是正整数,并且第i个分量从上面严格限定为m_i For example, the vector (0, 0, ..., 0) would correspond to the particular combination within which one takes the first element from each array, while (m_1-1, m_2-1, ..., m_N-1) is identified with the combination where one takes the last element from each array.例如,向量(0, 0, ..., 0)将对应于从每个数组中获取第一个元素的特定组合,而(m_1-1, m_2-1, ..., m_N-1)被标识为从每个数组中获取最后一个元素的组合。 Thus in order to construct all M combinations, the function below consecutively constructs all such vectors and for each of them identifies the corresponding combination of the elements of the input arrays.因此,为了构造所有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);
});

with node v6.12.2 , I get following timings:使用node v6.12.2 ,我得到以下时间:

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

Modern JavaScript in just a few lines.现代 JavaScript 只需几行代码。 No external libraries or dependencies like Lodash.没有像 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') );

Another, even more simplified, 2021-style answer using only reduce, map, and concat methods:另一个更简化的 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]));

A more readable implementation更易读的实现

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

For those happy with a ramda solution:对于那些对 ramda 解决方案感到满意的人:

import { xprod, flatten } from 'ramda';

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

Or the same without dependencies and two lego blocks for free ( xprod and 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);

A non-recursive approach that adds the ability to filter and modify the products before actually adding them to the result set.一种非递归方法,可在实际将产品添加到结果集之前添加过滤和修改产品的功能。

Note: the use of .map rather than .forEach .注意:使用.map而不是.forEach In some browsers, .map runs faster.在某些浏览器中, .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;
}

Just for a choice a real simple implementation using array's reduce :只是为了选择一个使用数组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))), []);

A simple "mind and visually friendly" solution.一个简单的“思想和视觉友好”的解决方案。

在此处输入图像描述


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

Yet another implementation.又一个实现。 Not the shortest or fancy, but fast:不是最短或花哨的,但很快:

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

A simple, modified version of @viebel's code in plain Javascript: @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 ] ];
*/

No libraries needed!不需要图书馆! :) :)

Needs arrow functions though and probably not that efficient.虽然需要箭头功能,但可能效率不高。 :/ :/

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

f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))

This is for 3 arrays.这适用于 3 个阵列。
Some answers gave a way for any number of arrays.一些答案为任意数量的数组提供了一种方式。
This can easily contract or expand to less or more arrays.这可以很容易地收缩或扩展为更少或更多的阵列。
I needed combinations of one set with repetitions, so I could have used:我需要一组与重复的组合,所以我可以使用:

f(a,a,a)

but used:但使用:

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

I noticed that nobody posted a solution that allows a function to be passed to process each combination, so here is my solution:我注意到没有人发布允许传递函数来处理每个组合的解决方案,所以这是我的解决方案:

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

Output:输出:

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

How would you implement the Cartesian product of multiple arrays in JavaScript?您将如何在JavaScript中实现多个数组的笛卡尔积?

As an example,举个例子,

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

should return应该回来

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

How would you implement the Cartesian product of multiple arrays in JavaScript?你将如何在 JavaScript 中实现多个数组的笛卡尔积?

As an example,举个例子,

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

should return应该回来

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

For the record 作为记录

Here it goes my version of it. 这是我的版本。 I made it using the simplest javascript iterator "for()", so it's compatible on every case and has the best performance. 我使用最简单的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;
}

Best regards. 最好的祝福。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM