我在编写代码时遇到麻烦,需要在JavaScript中生成包含m个元素的n个数组的组合。我已经在其他语言中看到了类似的问题,但答案中包含语法或库魔法,我不确定如何翻译。
考虑这些数据:
[[0,1], [0,1,2,3], [0,1,2]]
3个数组,其中包含不同数量的元素。我想要做的是通过组合每个数组中的项来获得所有组合。
例如:
0,0,0 // item 0 from array 0, item 0 from array 1, item 0 from array 2
0,0,1
0,0,2
0,1,0
0,1,1
0,1,2
0,2,0
0,2,1
0,2,2
等等。
如果数组的数量是固定的,那么很容易进行硬编码实现。但阵列的数量可能会有所不同:
[[0,1], [0,1]]
[[0,1,3,4], [0,1], [0], [0,1]]
非常感谢任何帮助。
答案 0 :(得分:81)
这是一个非常简单和简单的使用递归辅助函数:
function cartesian() {
var r = [], arg = arguments, max = arg.length-1;
function helper(arr, i) {
for (var j=0, l=arg[i].length; j<l; j++) {
var a = arr.slice(0); // clone arr
a.push(arg[i][j]);
if (i==max)
r.push(a);
else
helper(a, i+1);
}
}
helper([], 0);
return r;
}
用法:
cartesian([0,1], [0,1,2,3], [0,1,2]);
要使该函数采用数组数组,只需将签名更改为function cartesian(arg)
,以便arg
是参数而不是all arguments
。
答案 1 :(得分:9)
您可以通过构建子数组来采用迭代方法。
var parts = [[0, 1], [0, 1, 2, 3], [0, 1, 2]],
result = parts.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
console.log(result.map(a => a.join(', ')));
&#13;
.as-console-wrapper { max-height: 100% !important; top: 0; }
&#13;
答案 2 :(得分:6)
我建议使用简单的递归generator function:
// Generate all combinations of array elements:
function* cartesian(head, ...tail) {
let remainder = tail.length ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
for (let c of cartesian([0,1], [0,1,2,3], [0,1,2])) {
console.log(...c);
}
答案 3 :(得分:3)
在做了一点研究后,我发现了一个先前的相关问题: Finding All Combinations of JavaScript array values
我已经从那里调整了一些代码,以便它返回一个包含所有排列的数组数组:
function(arraysToCombine) {
var divisors = [];
for (var i = arraysToCombine.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
}
function getPermutation(n, arraysToCombine) {
var result = [],
curArray;
for (var i = 0; i < arraysToCombine.length; i++) {
curArray = arraysToCombine[i];
result.push(curArray[Math.floor(n / divisors[i]) % curArray.length]);
}
return result;
}
var numPerms = arraysToCombine[0].length;
for(var i = 1; i < arraysToCombine.length; i++) {
numPerms *= arraysToCombine[i].length;
}
var combinations = [];
for(var i = 0; i < numPerms; i++) {
combinations.push(getPermutation(i, arraysToCombine));
}
return combinations;
}
我在http://jsfiddle.net/7EakX/放了一个工作副本,它带有你之前给出的数组([[0,1],[0,1,2,3],[0,1,2]])并将结果输出到浏览器控制台。
答案 4 :(得分:3)
只是为了好玩,这是我的第一个答案中解决方案的更多功能变体:
function cartesian() {
var r = [], args = Array.from(arguments);
args.reduceRight(function(cont, factor, i) {
return function(arr) {
for (var j=0, l=factor.length; j<l; j++) {
var a = arr.slice(); // clone arr
a[i] = factor[j];
cont(a);
}
};
}, Array.prototype.push.bind(r))(new Array(args.length));
return r;
}
替代方案,为了全速运行,我们可以动态编译自己的循环:
function cartesian() {
return (cartesian.cache[arguments.length] || cartesian.compile(arguments.length)).apply(null, arguments);
}
cartesian.cache = [];
cartesian.compile = function compile(n) {
var args = [],
indent = "",
up = "",
down = "";
for (var i=0; i<n; i++) {
var arr = "$"+String.fromCharCode(97+i),
ind = String.fromCharCode(105+i);
args.push(arr);
up += indent+"for (var "+ind+"=0, l"+arr+"="+arr+".length; "+ind+"<l"+arr+"; "+ind+"++) {\n";
down = indent+"}\n"+down;
indent += " ";
up += indent+"arr["+i+"] = "+arr+"["+ind+"];\n";
}
var body = "var res=[],\n arr=[];\n"+up+indent+"res.push(arr.slice());\n"+down+"return res;";
return cartesian.cache[n] = new Function(args, body);
}
答案 5 :(得分:2)
var f = function(arr){
if(typeof arr !== 'object'){
return false;
}
arr = arr.filter(function(elem){ return (elem !== null); }); // remove empty elements - make sure length is correct
var len = arr.length;
var nextPerm = function(){ // increase the counter(s)
var i = 0;
while(i < len)
{
arr[i].counter++;
if(arr[i].counter >= arr[i].length){
arr[i].counter = 0;
i++;
}else{
return false;
}
}
return true;
};
var getPerm = function(){ // get the current permutation
var perm_arr = [];
for(var i = 0; i < len; i++)
{
perm_arr.push(arr[i][arr[i].counter]);
}
return perm_arr;
};
var new_arr = [];
for(var i = 0; i < len; i++) // set up a counter property inside the arrays
{
arr[i].counter = 0;
}
while(true)
{
new_arr.push(getPerm()); // add current permutation to the new array
if(nextPerm() === true){ // get next permutation, if returns true, we got them all
break;
}
}
return new_arr;
};
答案 6 :(得分:2)
这是另一种做法。我将所有数组的索引视为一个数字,其数字都是不同的数字(如时间和日期),使用数组的长度作为基数。
所以,使用你的第一组数据,第一个数字是基数2,第二个数字是基数4,第三个数字是基数3.计数器开始000,然后是001,002,然后是010.数字对应于数组中的索引,并且由于保留了顺序,这没有问题。
我在这里工作的小提琴:http://jsfiddle.net/Rykus0/DS9Ea/1/
这是代码:
// Arbitrary base x number class
var BaseX = function(initRadix){
this.radix = initRadix ? initRadix : 1;
this.value = 0;
this.increment = function(){
return( (this.value = (this.value + 1) % this.radix) === 0);
}
}
function combinations(input){
var output = [], // Array containing the resulting combinations
counters = [], // Array of counters corresponding to our input arrays
remainder = false, // Did adding one cause the previous digit to rollover?
temp; // Holds one combination to be pushed into the output array
// Initialize the counters
for( var i = input.length-1; i >= 0; i-- ){
counters.unshift(new BaseX(input[i].length));
}
// Get all possible combinations
// Loop through until the first counter rolls over
while( !remainder ){
temp = []; // Reset the temporary value collection array
remainder = true; // Always increment the last array counter
// Process each of the arrays
for( i = input.length-1; i >= 0; i-- ){
temp.unshift(input[i][counters[i].value]); // Add this array's value to the result
// If the counter to the right rolled over, increment this one.
if( remainder ){
remainder = counters[i].increment();
}
}
output.push(temp); // Collect the results.
}
return output;
}
// Input is an array of arrays
console.log(combinations([[0,1], [0,1,2,3], [0,1,2]]));
答案 7 :(得分:1)
使用ES6递归样式的另一种实现
Array.prototype.cartesian = function(a,...as){
return a ? this.reduce((p,c) => (p.push(...a.cartesian(...as).map(e => as.length ? [c,...e] : [c,e])),p),[])
: this;
};
console.log(JSON.stringify([0,1].cartesian([0,1,2,3], [[0],[1],[2]])));
答案 8 :(得分:1)
您可以使用递归函数获取所有组合
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '', final = []) => {
if (arr.length > 1) {
arr[0].forEach(v => loopOver(arr.slice(1), str + v, final))
} else {
arr[0].forEach(v => final.push(str + v))
}
return final
}
console.log(loopOver(charSet))
使用三进制仍可以简化此代码,但出于可读性的考虑,我更喜欢第一个版本
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
let loopOver = (arr, str = '') => arr[0].map(v => arr.length > 1 ? loopOver(arr.slice(1), str + v) : str + v).flat()
console.log(loopOver(charSet))
答案 9 :(得分:0)
const charSet = [["A", "B"],["C", "D", "E"],["F", "G", "H", "I"]];
console.log(charSet.reduce((a,b)=>a.flatMap(x=>b.map(y=>x+y)),['']))