Javascript - 在单个数组中生成元素的所有组合(成对)

时间:2017-04-05 20:37:57

标签: javascript arrays algorithm combinations

我已经看到了几个关于如何在数组中生成所有可能的元素组合的类似问题。但是我很难搞清楚如何编写一个只能输出组合的算法。任何建议都会受到超级赞赏!

从以下数组开始(包含N个元素):

var array = ["apple", "banana", "lemon", "mango"];

得到以下结果:

var result = [
   "apple banana"
   "apple lemon"
   "apple mango"
   "banana lemon"
   "banana mango"
   "lemon mango"
];

我正在尝试以下方法,但这导致所有可能的组合,而不是组合对。

var letters = splSentences;
var combi = [];
var temp= "";
var letLen = Math.pow(2, letters.length);

for (var i = 0; i < letLen ; i++){
    temp= "";
    for (var j=0;j<letters.length;j++) {
        if ((i & Math.pow(2,j))){ 
            temp += letters[j]+ " "
        }
    }
    if (temp !== "") {
        combi.push(temp);
    }
}

12 个答案:

答案 0 :(得分:16)

一种简单的方法是在数组中执行double for循环,跳过第二个循环中的第一个i元素。

&#13;
&#13;
let array = ["apple", "banana", "lemon", "mango"];
let results = [];

// Since you only want pairs, there's no reason
// to iterate over the last element directly
for (let i = 0; i < array.length - 1; i++) {
  // This is where you'll capture that last value
  for (let j = i + 1; j < array.length; j++) {
    results.push(`${array[i]} ${array[j]}`);
  }
}

console.log(results);
&#13;
&#13;
&#13;

用ES5重写:

&#13;
&#13;
var array = ["apple", "banana", "lemon", "mango"];
var results = [];

// Since you only want pairs, there's no reason
// to iterate over the last element directly
for (var i = 0; i < array.length - 1; i++) {
  // This is where you'll capture that last value
  for (var j = i + 1; j < array.length; j++) {
    results.push(array[i] + ' ' + array[j]);
  }
}

console.log(results);
&#13;
&#13;
&#13;

答案 1 :(得分:12)

以下是两个函数式编程 ES6解决方案:

&#13;
&#13;
var array = ["apple", "banana", "lemon", "mango"];

var result = array.reduce( (acc, v, i) =>
    acc.concat(array.slice(i+1).map( w => v + ' ' + w )),
[]);

console.log(result);
&#13;
&#13;
&#13;

或者:

&#13;
&#13;
var array = ["apple", "banana", "lemon", "mango"];

var result = [].concat(...array.map( 
    (v, i) => array.slice(i+1).map( w => v + ' ' + w ))
);

console.log(result);
&#13;
&#13;
&#13;

答案 2 :(得分:9)

虽然已找到解决方案,但我在此处发布了一般算法算法,以查找n个元素的所有组合大小m (m>n)。在您的情况下,我们有n=2m=4

const result = [];
result.length = 2; //n=2

function combine(input, len, start) {
  if(len === 0) {
    console.log( result.join(" ") ); //process here the result
    return;
  }
  for (var i = start; i <= input.length - len; i++) {
    result[result.length - len] = input[i];
    combine(input, len-1, i+1 );
  }
}

const array = ["apple", "banana", "lemon", "mango"];    
combine( array, result.length, 0);

答案 3 :(得分:7)

我最终为这个问题写了一个通用的解决方案,在功能上等效于nhnghia's答案,但是我在这里分享了它,因为我认为它更易于阅读/关注,并且也充满了描述该问题的评论。算法。


/**
 * Generate all combinations of an array.
 * @param {Array} sourceArray - Array of input elements.
 * @param {number} comboLength - Desired length of combinations.
 * @return {Array} Array of combination arrays.
 */
function generateCombinations(sourceArray, comboLength) {
  const sourceLength = sourceArray.length;
  if (comboLength > sourceLength) return [];

  const combos = []; // Stores valid combinations as they are generated.

  // Accepts a partial combination, an index into sourceArray, 
  // and the number of elements required to be added to create a full-length combination.
  // Called recursively to build combinations, adding subsequent elements at each call depth.
  const makeNextCombos = (workingCombo, currentIndex, remainingCount) => {
    const oneAwayFromComboLength = remainingCount == 1;

    // For each element that remaines to be added to the working combination.
    for (let sourceIndex = currentIndex; sourceIndex < sourceLength; sourceIndex++) {
      // Get next (possibly partial) combination.
      const next = [ ...workingCombo, sourceArray[sourceIndex] ];

      if (oneAwayFromComboLength) {
        // Combo of right length found, save it.
        combos.push(next);
      }
      else {
        // Otherwise go deeper to add more elements to the current partial combination.
        makeNextCombos(next, sourceIndex + 1, remainingCount - 1);
      }
        }
  }

  makeNextCombos([], 0, comboLength);
  return combos;
}

答案 4 :(得分:4)

对于我来说,我想根据数组的大小范围获得以下组合:

function getCombinations(valuesArray: String[])
{

var combi = [];
var temp = [];
var slent = Math.pow(2, valuesArray.length);

for (var i = 0; i < slent; i++)
{
    temp = [];
    for (var j = 0; j < valuesArray.length; j++)
    {
        if ((i & Math.pow(2, j)))
        {
            temp.push(valuesArray[j]);
        }
    }
    if (temp.length > 0)
    {
        combi.push(temp);
    }
}

combi.sort((a, b) => a.length - b.length);
console.log(combi.join("\n"));
return combi;
}

示例:

// variable "results" stores an array with arrays string type
let results = getCombinations(['apple', 'banana', 'lemon', ',mango']);

控制台中的输出:

enter image description here

该功能基于以下文档的逻辑,有关更多信息,请参见以下参考: https://www.w3resource.com/javascript-exercises/javascript-function-exercise-3.php

答案 5 :(得分:3)

只需给下一个将要搜索的人一个选项

const arr = ['a', 'b', 'c']
const combinations = ([head, ...tail]) => tail.length > 0 ? [...tail.map(tailValue => [head, tailValue]), ...combinations(tail)] : []
console.log(combinations(arr)) //[ [ 'a', 'b' ], [ 'a', 'c' ], [ 'b', 'c' ] ]

答案 6 :(得分:3)

我找到的最佳解决方案 - https://lowrey.me/es6-javascript-combination-generator/ 使用 ES6 生成器功能,我适配了 TS。大多数情况下,您不需要同时使用所有组合。我对编写 for (let i=0; ... for let (j=i+1; ... for (let k=j+1... 之类的循环感到恼火,只是为了一个一个地组合组合来测试我是否需要终止循环..

export function* combinations<T>(array: T[], length: number): IterableIterator<T[]> {
    for (let i = 0; i < array.length; i++) {
        if (length === 1) {
            yield [array[i]];
        } else {
            const remaining = combinations(array.slice(i + 1, array.length), length - 1);
            for (let next of remaining) {
                yield [array[i], ...next];
            }
        }
    }
}

用法:

for (const combo of combinations([1,2,3], 2)) {
    console.log(combo)
}

输出:

> (2) [1, 2]
> (2) [1, 3]
> (2) [2, 3]

答案 7 :(得分:0)

试试这个: https://jsfiddle.net/e2dLa9v6/

var array = ["apple", "banana", "lemon", "mango"];
var result = [];

for(var i=0;i<array.length-1;i++){
    for(var j=i+1;j<array.length;j++){
    result.push(array[i]+" "+array[j]);
  }
}
for(var i=0;i<result.length;i++){
    alert(result[i]);
}

答案 8 :(得分:0)

使用mapflatMap可以完成以下操作(chrome and firefox仅支持flatMap

var array = ["apple", "banana", "lemon", "mango"]
array.flatMap(x => array.map(y => x !== y ? x + ' ' + y : null)).filter(x => x)

答案 9 :(得分:0)

生成数组中元素的组合非常类似于在数字系统中进行计数, 基数是数组中元素的数量(如果您考虑将丢失的前导零)。

这将为您提供数组的所有索引(连接的):

arr = ["apple", "banana", "lemon", "mango"]
base = arr.length

idx = [...Array(Math.pow(base, base)).keys()].map(x => x.toString(base))

您只对两个配对感兴趣,因此请限制范围:

range = (from, to) = [...Array(to).keys()].map(el => el + from)
indices = range => range.map(x => x.toString(base).padStart(2,"0"))

indices( range( 0, Math.pow(base, 2))) // range starts at 0, single digits are zero-padded.

现在剩下要做的就是将索引映射到值。

由于您不希望元素与自身配对并且顺序无关紧要, 在映射到最终结果之前,需要删除这些内容。

const range = (from, to) => [...Array(to).keys()].map(el => el + from)
const combinations = arr => {
  const base = arr.length
  return range(0, Math.pow(base, 2))
    .map(x => x.toString(base).padStart(2, "0"))
    .filter(i => !i.match(/(\d)\1/) && i === i.split('').sort().join(''))
    .map(i => arr[i[0]] + " " + arr[i[1]])
}

console.log(combinations(["apple", "banana", "lemon", "mango"]))

toString()包含十个以上的元素,将为索引返回字母;而且,这最多只能使用36个元素。

答案 10 :(得分:0)

我认为这是所有此类问题的答案。

/**
 * 
 * Generates all combination of given Array or number
 * 
 * @param {Array | number} item  - Item accepts array or number. If it is array exports all combination of items. If it is a number export all combination of the number
 * @param {number} n - pow of the item, if given value is `n` it will be export max `n` item combination
 * @param {boolean} filter - if it is true it will just export items which have got n items length. Otherwise export all posible length.
 * @return {Array} Array of combination arrays.
 * 
 * Usage Example:
 * 
 * console.log(combination(['A', 'B', 'C', 'D'], 2, true)); // [[ 'A','A' ], [ 'A', 'B' ]...] (16 items)
 * console.log(combination(['A', 'B', 'C', 'D'])); // [['A', 'A', 'A', 'B' ],.....,['A'],] (340 items)
 * console.log(comination(4, 2)); // all posible values [[ 0 ], [ 1 ], [ 2 ], [ 3 ], [ 0, 0 ], [ 0, 1 ], [ 0, 2 ]...] (20 items)
 */
function combination(item, n) {
  const filter = typeof n !=='undefined';
  n = n ? n : item.length;
  const result = [];
  const isArray = item.constructor.name === 'Array';

  const pow = (x, n, m = []) => {
    if (n > 0) {
      for (var i = 0; i < 4; i++) {
        const value = pow(x, n - 1, [...m, isArray ? item[i] : i]);
        result.push(value);
      }
    }
    return m;
  }
  pow(isArray ? item.length : item, n);

  return filter ? result.filter(item => item.length == n) : result;
}

console.log("#####first sample: ", combination(['A', 'B', 'C', 'D'], 2)); // with filter
console.log("#####second sample: ", combination(['A', 'B', 'C', 'D'])); // without filter
console.log("#####third sample: ", combination(4, 2)); // just number

答案 11 :(得分:0)

稍微击败死马,但对于递归限制和性能不是问题的较小集合,可以通过“包含给定数组中第一个元素的递归组合”加上“不包含的递归组合”递归地完成一般组合生成第一个元素”。作为生成器,它提供了非常紧凑的实现:

// Generator yielding k-item combinations of array a
function* choose(a, k) {
  if(a.length == k) yield a;
  else if(k == 0) yield [];
  else {
      for(let rest of choose(a.slice(1), k-1)) yield [a[0], ...rest];
      for(let rest of choose(a.slice(1), k)) yield rest;
  }
}

甚至更短(速度快两倍,使用我的 MacBook 进行 7 次选择 5 次的 1 M 次调用需要 3.9 秒),带有函数返回和组合数组:

// Return an array of combinations
function comb(a, k) {
  if(a.length === k) return [a];
  else if(k === 0) return [[]];
  else return [...comb(a.slice(1), k-1).map(c => [a[0], ...c]),
      ...comb(a.slice(1), k)];
}