【问题标题】:How can I create every combination possible for the contents of two arrays?如何为两个数组的内容创建每个可能的组合?
【发布时间】:2012-02-14 17:38:39
【问题描述】:

我有两个数组:

var array1 = ["A", "B", "C"];
var array2 = ["1", "2", "3"];

如何设置另一个数组来包含上述所有组合,以便:

var combos = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"];

【问题讨论】:

标签: javascript arrays function combinations


【解决方案1】:

或者,如果您想用任意数量的任意大小的数组创建组合...(我相信您可以递归地执行此操作,但由于这不是工作面试,所以我改为使用一个迭代的“里程表”......它增加一个“数字”,每个数字都是一个基于每个数组长度的“base-n”数字)......例如......

combineArrays([ ["A","B","C"],
                ["+", "-", "*", "/"],
                ["1","2"] ] )

...返回...

[
   "A+1","A+2","A-1", "A-2",
   "A*1", "A*2", "A/1", "A/2", 
   "B+1","B+2","B-1", "B-2",
   "B*1", "B*2", "B/1", "B/2", 
   "C+1","C+2","C-1", "C-2",
   "C*1", "C*2", "C/1", "C/2"
]

...每一个都对应一个“里程表”值 从每个数组中选择一个索引...

[0,0,0], [0,0,1], [0,1,0], [0,1,1]
[0,2,0], [0,2,1], [0,3,0], [0,3,1]
[1,0,0], [1,0,1], [1,1,0], [1,1,1]
[1,2,0], [1,2,1], [1,3,0], [1,3,1]
[2,0,0], [2,0,1], [2,1,0], [2,1,1]
[2,2,0], [2,2,1], [2,3,0], [2,3,1]

“里程表”方法可以让您轻松生成 您想要的输出类型,而不仅仅是连接的字符串 就像我们在这里一样。除此之外,通过避免递归 我们避免了——我敢说吗? -- 堆栈溢出...

function combineArrays( array_of_arrays ){

    // First, handle some degenerate cases...

    if( ! array_of_arrays ){
        // Or maybe we should toss an exception...?
        return [];
    }

    if( ! Array.isArray( array_of_arrays ) ){
        // Or maybe we should toss an exception...?
        return [];
    }

    if( array_of_arrays.length == 0 ){
        return [];
    }

    for( let i = 0 ; i < array_of_arrays.length; i++ ){
        if( ! Array.isArray(array_of_arrays[i]) || array_of_arrays[i].length == 0 ){
            // If any of the arrays in array_of_arrays are not arrays or zero-length, return an empty array...
            return [];
        }
    }

    // Done with degenerate cases...

    // Start "odometer" with a 0 for each array in array_of_arrays.
    let odometer = new Array( array_of_arrays.length );
    odometer.fill( 0 ); 

    let output = [];

    let newCombination = formCombination( odometer, array_of_arrays );

    output.push( newCombination );

    while ( odometer_increment( odometer, array_of_arrays ) ){
        newCombination = formCombination( odometer, array_of_arrays );
        output.push( newCombination );
    }

    return output;
}/* combineArrays() */


// Translate "odometer" to combinations from array_of_arrays
function formCombination( odometer, array_of_arrays ){
    // In Imperative Programmingese (i.e., English):
    // let s_output = "";
    // for( let i=0; i < odometer.length; i++ ){
    //    s_output += "" + array_of_arrays[i][odometer[i]]; 
    // }
    // return s_output;

    // In Functional Programmingese (Henny Youngman one-liner):
    return odometer.reduce(
      function(accumulator, odometer_value, odometer_index){
        return "" + accumulator + array_of_arrays[odometer_index][odometer_value];
      },
      ""
    );
}/* formCombination() */

function odometer_increment( odometer, array_of_arrays ){

    // Basically, work you way from the rightmost digit of the "odometer"...
    // if you're able to increment without cycling that digit back to zero,
    // you're all done, otherwise, cycle that digit to zero and go one digit to the
    // left, and begin again until you're able to increment a digit
    // without cycling it...simple, huh...?

    for( let i_odometer_digit = odometer.length-1; i_odometer_digit >=0; i_odometer_digit-- ){ 

        let maxee = array_of_arrays[i_odometer_digit].length - 1;         

        if( odometer[i_odometer_digit] + 1 <= maxee ){
            // increment, and you're done...
            odometer[i_odometer_digit]++;
            return true;
        }
        else{
            if( i_odometer_digit - 1 < 0 ){
                // No more digits left to increment, end of the line...
                return false;
            }
            else{
                // Can't increment this digit, cycle it to zero and continue
                // the loop to go over to the next digit...
                odometer[i_odometer_digit]=0;
                continue;
            }
        }
    }/* for( let odometer_digit = odometer.length-1; odometer_digit >=0; odometer_digit-- ) */

}/* odometer_increment() */

【讨论】:

  • 哇,这太棒了,正是我所需要的。一个在可变数量的数组之间创建所有可能组合的函数,每个数组包含可变数量的元素。
  • 你是个天才。从字面上看,只是救了我的命。这应该是公认的答案,因为它涵盖了 OP 情况以及任何其他情况。
  • 出色地解决了,我已经绞尽脑汁好几个小时了,想不出任何与此相关的东西。我扩展了您的版本,允许通过在函数调用中添加一个新参数 array_prefixes 作为最后一个参数,然后使用 return accumulator + ' ' + array_prefixes[odometer_index] + ': ' + array_of_arrays[odometer_index][odometer_value]; 在每个值之前添加名称来为每个数组的值添加一个自定义名称的前缀。
【解决方案2】:

以防万一有人在寻找Array.map 解决方案

var array1=["A","B","C"];

var array2=["1","2","3","4"];

console.log(array1.flatMap(d => array2.map(v => d + v)))

【讨论】:

  • 需要注意的一点是 IE 不支持 flatMap,否则这看起来像一个干净的解决方案
【解决方案3】:

在所有答案中看到很多 for 循环...

这是我想出的一个递归解决方案,它将通过从每个数组中获取 1 个元素来找到 N 个数组的所有组合:

const array1=["A","B","C"]
const array2=["1","2","3"]
const array3=["red","blue","green"]

const combine = ([head, ...[headTail, ...tailTail]]) => {
  if (!headTail) return head

  const combined = headTail.reduce((acc, x) => {
    return acc.concat(head.map(h => `${h}${x}`))
  }, [])

  return combine([combined, ...tailTail])
}

console.log('With your example arrays:', combine([array1, array2]))
console.log('With N arrays:', combine([array1, array2, array3]))

//-----------UPDATE BELOW FOR COMMENT---------
// With objects
const array4=[{letter: "A"}, {letter: "B"}, {letter: "C"}]
const array5=[{number: 1}, {number: 2}, {number: 3}]
const array6=[{color: "RED"}, {color: "BLUE"}, {color: "GREEN"}]

const combineObjects = ([head, ...[headTail, ...tailTail]]) => {
  if (!headTail) return head

  const combined = headTail.reduce((acc, x) => {
    return acc.concat(head.map(h => ({...h, ...x})))
  }, [])

  return combineObjects([combined, ...tailTail])
}

console.log('With arrays of objects:', combineObjects([array4, array5, array6]))

【讨论】:

  • 非常干净的解决方案!
  • 停止搜索。这是最好的解决方案。
  • 而不是使用reduceconcat 我认为您还可以使用平面图和地图(这也将组合按OP 的请求顺序排列):const combined = head.flatMap((a) =&gt; headTail.map((b) =&gt; `${a}${b}`));
  • 你将如何输出[{ letter: A, number: 1, color: red }, { letter: A, number: 1, color: blue }]...
  • "A" 替换为{ letter: "A" },将"B" 替换为{ number: 1 } 等。然后将head.map(h =&gt; ${h}${x}) 替换为head.map(h =&gt; ({ ...h, ...x}))跨度>
【解决方案4】:

这种形式的循环

combos = [] //or combos = new Array(2);

for(var i = 0; i < array1.length; i++)
{
     for(var j = 0; j < array2.length; j++)
     {
        //you would access the element of the array as array1[i] and array2[j]
        //create and array with as many elements as the number of arrays you are to combine
        //add them in
        //you could have as many dimensions as you need
        combos.push(array1[i] + array2[j])
     }
}

【讨论】:

  • 我不能用 .map 做到这一点,知道为什么吗?
  • 不完全确定你想要做什么,但也许 flatMap 是你需要的?你能提供更多信息吗?
【解决方案5】:

假设您使用的是支持Array.forEach 的最新网络浏览器:

var combos = [];
array1.forEach(function(a1){
  array2.forEach(function(a2){
    combos.push(a1 + a2);
  });
});

如果你没有forEach,那么在没有它的情况下重写它是一个非常简单的练习。正如其他人之前已经证明的那样,不使用...也有一些性能优势(尽管我认为不久之后,常见的 JavaScript 运行时将优化掉当前这样做的任何优势。)

【讨论】:

  • 也可以用 for 循环替换 forEach。
  • 没有浏览器...我打算用phonegap把它变成一个原生的iphone应用。那还能用吗?
  • @Dingredient - 并不依赖于浏览器,而是支持的 JavaScript 版本。如果forEach 不起作用,请改用rubixibuc 的答案。
【解决方案6】:

第二部分:在我 2018 年 7 月复杂的迭代“里程表”解决方案之后,这里有一个更简单的 combineArraysRecursively() 递归版本...

function combineArraysRecursively( array_of_arrays ){

        // First, handle some degenerate cases...

        if( ! array_of_arrays ){
            // Or maybe we should toss an exception...?
            return [];
        }

        if( ! Array.isArray( array_of_arrays ) ){
            // Or maybe we should toss an exception...?
            return [];
        }

        if( array_of_arrays.length == 0 ){
            return [];
        }

        for( let i = 0 ; i < array_of_arrays.length; i++ ){
            if( ! Array.isArray(array_of_arrays[i]) || array_of_arrays[i].length == 0 ){
                // If any of the arrays in array_of_arrays are not arrays or are zero-length array, return an empty array...
                return [];
            }
        }

        // Done with degenerate cases...
        let outputs = [];

        function permute(arrayOfArrays, whichArray=0, output=""){

            arrayOfArrays[whichArray].forEach((array_element)=>{
                if( whichArray == array_of_arrays.length - 1 ){            
                    // Base case...
                    outputs.push( output + array_element );
                }
                else{
                    // Recursive case...
                    permute(arrayOfArrays, whichArray+1, output + array_element );
                }
            });/*  forEach() */
        }

        permute(array_of_arrays);

        return outputs;
        

}/* function combineArraysRecursively() */

const array1 = ["A","B","C"];
const array2 = ["+", "-", "*", "/"];
const array3 = ["1","2"];

console.log("combineArraysRecursively(array1, array2, array3) = ", combineArraysRecursively([array1, array2, array3]) );

【讨论】:

  • 如果array1 为空怎么办?然后我需要array2array3的所有组合。
【解决方案7】:

这里是函数式编程 ES6 解决方案:

var array1=["A","B","C"];
var array2=["1","2","3"];

var result = array1.reduce( (a, v) =>
    [...a, ...array2.map(x=>v+x)],
[]);
/*---------OR--------------*/
var result1 = array1.reduce( (a, v, i) =>
    a.concat(array2.map( w => v + w )),
[]);

/*-------------OR(without arrow function)---------------*/
var result2 = array1.reduce(function(a, v, i) {
    a = a.concat(array2.map(function(w){
      return v + w
    }));
    return a;
    },[]
);

console.log(result);
console.log(result1);
console.log(result2)

【讨论】:

    【解决方案8】:

    @Nitish Narang's answer的解决方案增强。

    reduceflatMap 组合使用以支持N 数组组合。

    const combo = [
      ["A", "B", "C"],
      ["1", "2", "3", "4"]
    ];
    
    console.log(combo.reduce((a, b) => a.flatMap(x => b.map(y => x + y)), ['']))

    【讨论】:

      【解决方案9】:

      这是另一个镜头。只有一个函数,没有递归。

      function allCombinations(arrays) {
        const numberOfCombinations = arrays.reduce(
          (res, array) => res * array.length,
          1
        )
      
        const result = Array(numberOfCombinations)
          .fill(0)
          .map(() => [])
      
        let repeatEachElement
      
        for (let i = 0; i < arrays.length; i++) {
          const array = arrays[i]
      
          repeatEachElement = repeatEachElement ?
            repeatEachElement / array.length :
            numberOfCombinations / array.length
      
          const everyElementRepeatedLength = repeatEachElement * array.length
      
          for (let j = 0; j < numberOfCombinations; j++) {
            const index = Math.floor(
              (j % everyElementRepeatedLength) / repeatEachElement
            )
            result[j][i] = array[index]
          }
        }
      
        return result
      }
      
      const result = allCombinations([
        ['a', 'b', 'c', 'd'],
        [1, 2, 3],
        [true, false],
      ])
      console.log(result.join('\n'))

      【讨论】:

        【解决方案10】:

        我有类似的要求,但我需要获取对象的所有键组合,以便可以将其拆分为多个对象。例如,我需要转换以下内容;

        { key1: [value1, value2], key2: [value3, value4] }
        

        分为以下4个对象

        { key1: value1, key2: value3 }
        { key1: value1, key2: value4 }
        { key1: value2, key2: value3 }
        { key1: value2, key2: value4 }
        

        我用入口函数splitToMultipleKeys和递归函数spreadKeys解决了这个问题;

        function spreadKeys(master, objects) {
          const masterKeys = Object.keys(master);
          const nextKey = masterKeys.pop();
          const nextValue = master[nextKey];
          const newObjects = [];
          for (const value of nextValue) {
            for (const ob of objects) {
              const newObject = Object.assign({ [nextKey]: value }, ob);
              newObjects.push(newObject);
            }
          }
        
          if (masterKeys.length === 0) {
            return newObjects;
          }
        
          const masterClone = Object.assign({}, master);
          delete masterClone[nextKey];
          return spreadKeys(masterClone, newObjects);
        }
        
        export function splitToMultipleKeys(key) {
          const objects = [{}];
          return spreadKeys(key, objects);
        }
        

        【讨论】:

          【解决方案11】:

          还有一个:

          const buildCombinations = (allGroups: string[][]) => {
            const indexInArray = new Array(allGroups.length);
            indexInArray.fill(0);
            let arrayIndex = 0;
            const resultArray: string[] = [];
            while (allGroups[arrayIndex]) {
              let str = "";
              allGroups.forEach((g, index) => {
                str += g[indexInArray[index]];
              });
              resultArray.push(str);
              // if not last item in array already, switch index to next item in array
              if (indexInArray[arrayIndex] < allGroups[arrayIndex].length - 1) {
                indexInArray[arrayIndex] += 1;
              } else {
                // set item index for the next array
                indexInArray[arrayIndex] = 0;
                arrayIndex += 1;
                // exclude arrays with 1 element
                while (allGroups[arrayIndex] && allGroups[arrayIndex].length === 1) {
                  arrayIndex += 1;
                }
                indexInArray[arrayIndex] = 1;
              }
            }
            return resultArray;
          };
          

          一个例子:

          const testArrays = [["a","b"],["c"],["d","e","f"]]
          const result = buildCombinations(testArrays)
          // -> ["acd","bcd","ace","acf"]
          

          【讨论】:

            【解决方案12】:

            John D. Aynedjian 的我的解决方案版本,我为了自己的理解重写了它。

            console.log(getPermutations([["A","B","C"],["1","2","3"]]));
            function getPermutations(arrayOfArrays)
            {
             let permutations=[];
             let remainder,permutation;
             let permutationCount=1;
             let placeValue=1;
             let placeValues=new Array(arrayOfArrays.length);
             for(let i=arrayOfArrays.length-1;i>=0;i--)
             {
              placeValues[i]=placeValue;   
              placeValue*=arrayOfArrays[i].length;
             }
             permutationCount=placeValue;
             for(let i=0;i<permutationCount;i++)
             {
              remainder=i;
              permutation=[];
              for(let j=0;j<arrayOfArrays.length;j++)
              {
               permutation[j]=arrayOfArrays[j][Math.floor(remainder/placeValues[j])];
               remainder=remainder%placeValues[j];
              }
              permutations.push(permutation.reduce((prev,curr)=>prev+curr,""));  }
             return permutations;
            }

            首先将数组表示为数组数组:

            arrayOfArrays=[["A","B","C"],["a","b","c","d"],["1","2"]];
            

            接下来通过将每个数组中的元素数相乘来计算解决方案中的排列数:

            //["A","B","C"].length*["a","b","c","d"].length*["1","2"].length //24 permuations
            

            然后给每个数组一个位置值,从最后一个开始:

            //["1","2"] place value 1
            //["a","b","c","d"] place value 2 (each one of these letters has 2 possibilities to the right i.e. 1 and 2)
            //["A","B","C"] place value 8 (each one of these letters has 8 possibilities to the right i.e. a1,a2,b1,b2,c1,c2,d1,d2 
            placeValues=[8,2,1]
            

            这允许每个元素用一个数字表示:

            arrayOfArrays[0][2]+arrayOfArrays[1][3]+arrayOfArrays[2][0] //"Cc1"
            

            ...应该是:

            2*placeValues[2]+3*placesValues[1]+0*placeValues[2] //2*8+3*2+0*1=22
            

            我们实际上需要做相反的事情,因此使用排列数的商和余数将数字 0 转换为排列数到每个数组的索引。 像这样:

            //0 = [0,0,0], 1 = [0,0,1], 2 = [0,1,0], 3 = [0,1,1]
            for(let i=0;i<permutationCount;i++)
            {
             remainder=i;
             permutation=[];
             for(let j=0;j<arrayOfArrays.length;j++)
             {
              permutation[j]=arrayOfArrays[j][Math.floor(remainder/placeValues[j])];
              remainder=remainder%placeValues[j];
             }
             permutations.push(permutation.join(""));
            }
            

            根据要求,最后一位将排列转换为字符串。

            【讨论】:

              【解决方案13】:

              这是一个包含 N 个数组的简短递归。

              function permuteArrays(first, next, ...rest) {
                if (rest.length) next = permuteArrays(next, ...rest);
                return first.flatMap(a => next.map(b => [a, b].flat()));
              }
              

              可运行示例:

              function permuteArrays(first, next, ...rest) {
                if (rest.length) next = permuteArrays(next, ...rest);
                return first.flatMap(a => next.map(b => [a, b].flat()));
              }
              
              const squish = arr => arr.join('');
              console.log(
                permuteArrays(['A', 'B', 'C'], ['+', '-', '×', '÷'], [1, 2]).map(squish),
                permuteArrays(['a', 'b', 'c'], [1, 2, 3]).map(squish),
                permuteArrays([['a', 'foo'], 'b'], [1, 2]).map(squish),
                permuteArrays(['a', 'b', 'c'], [1, 2, 3], ['foo', 'bar', 'baz']).map(squish),
              )

              【讨论】:

                【解决方案14】:

                任意数量的数组,任意数量的元素。

                我猜想使用数基理论 - 每当 j-1 个数组的组合数用尽时,第 j 个数组就会更改为下一个元素。在这里将这些数组称为“向量”。

                let vectorsInstance = [
                  [1, 2],
                  [6, 7, 9],
                  [10, 11],
                  [1, 5, 8, 17]]
                
                function getCombos(vectors) {
                  function countComb(vectors) {
                    let numComb = 1
                    for (vector of vectors) {
                      numComb *= vector.length
                    }
                    return numComb
                  }
                  let allComb = countComb(vectors)
                  
                  let combos = []
                  for (let i = 0; i < allComb; i++) {
                    let thisCombo = []
                    for (j = 0; j < vectors.length; j++) {
                      let vector = vectors[j]
                      let prevComb = countComb(vectors.slice(0, j))
                      thisCombo.push(vector[Math.floor(i / prevComb) % vector.length])
                    }
                    combos.push(thisCombo)
                  }
                  return combos
                }
                
                console.log(getCombos(vectorsInstance))
                

                【讨论】:

                  【解决方案15】:

                  做一个这样的循环 ->

                  let numbers = [1,2,3,4,5];
                  let letters = ["A","B","C","D","E"];
                  let combos = [];
                  
                  for(let i = 0; i < numbers.length; i++) {
                  
                  combos.push(letters[i] + numbers[i]);
                  
                  };
                  

                  但是你应该让“数字”和“字母”的数组长度相同!

                  【讨论】:

                  • 您的代码有两个问题。首先,它会通过错误Uncaught ReferenceError: A is not defined。 IE;数组letters 的值必须是字符串。二、不会生成所有可能的组合,而是给出如下结果["A1", "B2", "C3", "D4", "E5"]
                  • 是的,我没看到我没有把报价放在那里
                  猜你喜欢
                  • 2021-03-16
                  • 2020-02-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-11-16
                  • 2022-01-28
                  相关资源
                  最近更新 更多