我正在寻找循环使用一系列选项的最佳方法,以确保我点击所有可用选项。
我创建了一个功能,允许客户端构建基本上是彼此叠加的其他图像的图像。这些其他图像被分成不同的组。它们在图像的一侧有链接,可以单击它们滚动浏览所有不同的图像以查看它们。
现在我正在制作一个自动化流程,该流程将在用户点击其中一个链接时运行更改图像的功能。我需要确保在此过程中击中不同图像的每个可能组合。
假设有3种不同的帽子,4种不同的衬衫,5种不同的裤子和6种不同的鞋子。我可以将其表示为一个数组,其中包含每个组的选项数。当前数组为[3, 4, 5, 6]
。
循环使用此数组以确保显示所有可能选项的最佳方法是什么?
答案 0 :(得分:12)
您需要所有阵列的Cartesian Product。
我在我的网站上有一个页面讨论这个,包括JavaScript中的实现:
http://phrogz.net/lazy-cartesian-product
例如,要以“前进”顺序快速迭代它们,您可以使用:
hats = ['fez','fedora']
shirts = ['t-shirt','long']
pants = ['shorts','jeans']
shoes = ['sneaker','loafer']
lazyProduct( [hats,shirts,pants,shoes], function(hat,shirt,pant,shoe){
// Your function is yielded unique combinations of values from the arrays
console.log(hat,shirt,pant,shoe);
});
function lazyProduct(sets,f,context){
if (!context) context=this;
var p=[],max=sets.length-1,lens=[];
for (var i=sets.length;i--;) lens[i]=sets[i].length;
function dive(d){
var a=sets[d], len=lens[d];
if (d==max) for (var i=0;i<len;++i) p[d]=a[i], f.apply(context,p);
else for (var i=0;i<len;++i) p[d]=a[i], dive(d+1);
p.pop();
}
dive(0);
}
输出:
fez t-shirt shorts sneaker
fez t-shirt shorts loafer
fez t-shirt jeans sneaker
fez t-shirt jeans loafer
fez long shorts sneaker
fez long shorts loafer
fez long jeans sneaker
fez long jeans loafer
fedora t-shirt shorts sneaker
fedora t-shirt shorts loafer
fedora t-shirt jeans sneaker
fedora t-shirt jeans loafer
fedora long shorts sneaker
fedora long shorts loafer
fedora long jeans sneaker
fedora long jeans loafer
fez t-shirt shorts sneaker
fez t-shirt shorts loafer
这与以下结果相同:
hats.forEach(function(hat){
shirts.forEach(function(shirt){
pants.forEach(function(pant){
shoes.forEach(function(shoe){
console.log(hat,shirt,pant,shoe);
});
});
});
});
或(对于旧浏览器):
for (var h=0;h<hats.length;h++){
var hat = hats[h];
for (var s=0;s<shirts.length;s++){
var shirt = shirts[s];
for (var p=0;p<pants.length;p++){
var pant = pants[p];
for (var e=0;e<shoes.length;e++){
var shoe = shoes[e];
console.log(hat,shirt,pant,shoe);
}
}
}
}
...但它支持在运行时定义的任意数量的数组。 (如果你在我的网站上使用第一个“懒惰”实现,你可以随机挑选项目,反向迭代,或者在任何时候轻松停止迭代。)
答案 1 :(得分:2)
如果我理解正确,你会问到计算每列数字的位置是不同的。你可以递归地做到这一点。
function options(opArr, fullArray){
var i = 0, j = opArr.length;
if(j < fullArray.length){ // if opArr doesn't have item from each group, add new group
while(i < fullArray[j]){ // count up for this group
newArr = opArr.slice(0); // clone opArr so we don't run into shared reference troubles, not sure if necessary
newArr[j] = i;
i++;
options(newArr, fullArray); // recurse
}
}else{ // opArr is now a unique array of your items
// console.log(opArr);
}
}
options([], [3, 9, 3, 3]);
注意:此(示例)将导致生成3 * 9 * 3 * 3 = 243
数组。你最终可能会以这种方式吃掉大量的记忆。
另一种方法是从整数转换为数组,这可以节省内存使用,因为您可以忘记所有先前计算的数组
function countUp(arrayOfBases, callback, self){
var arr = arrayOfBases.reverse(), x = 1, i = arr.length,
me = (self===undefined?this:self),
makeArr = function(arr, x, fn, me){
var a = arr.slice(0), n = [], i = x, j = 0, k = 0;
while(a.length > 0){
k = a[0];
if(k !== 0) j = i % k, i = (i - j) / k;
else j = 0;
n.unshift(j);
a.shift();
}
fn.call(me,n);
};
while (i-->0) if(arr[i] !== 0) x = x * arr[i];
i = 0;
while(i < x){
makeArr(arr, i, callback, me);
i++;
}
}
countUp([3,9,3,3], function(a){console.log(a);});
另一种方法,类似于前一种方法,保留了上次生成的数组,因此循环中的计算次数较少,而初始化时则更多。
function countUp2(arrayOfBases, callback, self){
var arr = arrayOfBases.reverse(), x = 1, i = arr.length, last = [],
me = (self===undefined?this:self),
addOne = function(arr, n, fn, me){
var j = n.length, i = j - 1;
n[i]++;
while(j = i, i-- > 0 && n[j] >= arr[j]){
if(arr[j] === 0) n[i] += n[j], n[j] = 0;
else n[i]++, n[j] -= arr[j];
}
return fn.call(me,n.slice(0)), n;
};
while (i-->0){
if(arr[i] !== 0) x = x * arr[i];
last[i] = 0;
}
i = 0;
last[last.length-1] = -1;
while(i < x){
last = addOne(arr, last, callback, me);
i++;
}
}
countUp2([3,9,3,3], function(a){console.log(a);});
所有这些方法都将输出
[0,0,0,0]
[0,0,0,1]
...
[0,8,1,2]
[0,8,2,0]
...
[2,8,2,1]
[2,8,2,2]
然后您可以选择处理。
答案 2 :(得分:0)
也许答案太晚了。但是我在geeksforgeeks找到了另一种解决方案。所以任何人都可以尝试。
const hats = ['fez','fedora'];
const shirts = ['t-shirt','long'];
const pants = ['shorts','jeans'];
const shoes = ['sneaker','loafer'];
const data = [hats, shirts, pants, shoes];
const keys = ['hats', 'shirts', 'pants', 'shoes'];
function combination(data, keys) {
const { length: numberOfArrays } = data;
/**
* Initialize a variable called indices
* with the length of data array and
* fill with zeros.
*/
const indices = Array(numberOfArrays).fill(0);
const result = [];
while (true) {
let obj = {};
/**
* Get the values from the inner arrays
* with help of `indices` and make an object
*/
for (let i = 0; i < numberOfArrays; i++) {
obj[keys[i]] = data[i][indices[i]];
}
// Push the object into the result array
result.push(obj);
// Get the last array
let pick = numberOfArrays - 1;
/**
* find the rightmost array that has more
* elements left after the current element
* in that array
*/
while (pick >= 0 && (indices[pick] + 1 >= data[pick].length)) {
pick--;
}
/**
* No such array is found so no more
* combinations left
*/
if (pick < 0) break;
/**
* If found move to next element in that array
*/
indices[pick]++;
/**
* for all arrays to the right of this
* array current index again points to
* first element
*/
for (let i = pick + 1; i < numberOfArrays; i++) {
indices[i] = 0;
}
}
return result;
}
console.log(combination(data, keys));
.as-console-wrapper {min-height: 100%!important; top: 0}