你将如何在 JavaScript 中实现多个数组的笛卡尔积?
举个例子,
cartesian([1, 2], [10, 20], [100, 200, 300])
应该回来
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
你将如何在 JavaScript 中实现多个数组的笛卡尔积?
举个例子,
cartesian([1, 2], [10, 20], [100, 200, 300])
应该回来
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
原始 2017 答案:带有 vanilla JS 的 2 行答案:( 请参阅下面的更新)
这里所有的答案都过于复杂,大部分都需要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;
这与上面相同,但经过改进以严格遵循Airbnb JavaScript 风格指南- 使用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让我知道原始代码的linter问题。
由于我写了这个答案,我们得到了更好的内置函数,这最终可以让我们将代码减少(没有双关语)只有 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 月发布的 ECMA-262 标准第 6 版中定义并且开发得更早,更广为人知的是 ES6 或 ES2015。看:
更新 2020 示例中的新方法已在 ES2019 中添加:
它使这样的代码变得如此简单,以至于不使用它是一种罪过。对于原生不支持它的旧平台,您始终可以使用 Babel 或其他工具将其转译为旧语法——事实上,我的由 Babel 转译的示例仍然比这里的大多数示例更短更简单,但它没有真的很重要,因为转译的输出不是您需要理解或维护的东西,这只是我发现有趣的一个事实。
没有必要编写数百行难以维护的代码,也没有必要为这么简单的事情使用整个库,两行普通的 JavaScript 就可以轻松完成工作。如您所见,使用该语言的现代功能确实值得,如果您需要支持没有现代功能本机支持的古老平台,您可以随时使用Babel、TypeScript或其他工具将新语法转换为旧的。
JavaScript 的发展是有原因的。TC39 通过添加新功能在语言设计方面做得非常出色,而浏览器供应商在实现这些功能方面做得非常出色。
要查看浏览器中任何给定功能的本机支持的当前状态,请参阅:
要查看 Node 版本的支持,请参阅:
要在原生不支持现代语法的平台上使用现代语法,请使用 Babel 或 TypeScript:
这是使用and提供的问题的功能解决方案(没有任何可变变量!):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/
这是@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));
// 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
。所有其他案例都按预期处理,如以下测试案例所示:
社区似乎认为这是微不足道的和/或很容易找到参考实现。然而,经过简短的检查,我找不到一个,......要么是这样,要么只是我喜欢重新发明轮子或解决课堂式的编程问题。无论哪种方式,这是您的幸运日:
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 中没有实际意义。