13

利用可能なすべてのオプションを確実にヒットするために、一連のオプションをループするための最良の方法を探しています。

私は、クライアントが基本的に他の画像を重ね合わせた画像を作成できるようにする機能を作成しました。これらの他の画像は、異なるグループに分割されます。画像の側面にリンクがあり、クリックしてさまざまな画像をすべてスクロールして表示できます。

今、私は、ユーザーがリンクの1つをクリックしたときに画像を変更する関数を実行する自動化されたプロセスを作成しています。このプロセス中に、さまざまな画像のすべての可能な組み合わせがヒットすることを確認する必要があります。

3種類の帽子、4種類のシャツ、5種類のズボン、6種類の靴があるとします。これは、各グループのオプションの数を含む配列として表すことができます。現在の配列は[3, 4, 5, 6]です。

この配列をループして、可能なすべてのオプションが表示されるようにするための最良の方法は何ですか?

4

3 に答える 3

16

すべてのアレイのデカルト積が必要です。

私のサイトには、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);        
      }
    }
  }
}

…しかし、実行時に定義された任意の数の配列をサポートします。(そして、私のサイトから最初の「怠惰な」実装を使用している場合は、アイテムをランダムに選択したり、逆に繰り返したり、いつでも簡単に繰り返しを停止したりできます。)

于 2012-08-28T04:19:00.523 に答える
2

編集:ここでjsperfを使用してさまざまな方法を比較しましたが、Phrogzの方法は、ここの3番目の方法の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]

その後、選択したとおりに処理できます。

于 2012-08-28T04:09:55.653 に答える
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}

于 2020-05-07T08:26:50.630 に答える