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

Posted

技术标签:

【中文标题】Javascript - 在单个数组中生成所有元素组合(成对)【英文标题】:Javascript - Generating all combinations of elements in a single array (in pairs) 【发布时间】:2017-08-31 15:46:35 【问题描述】:

我见过几个类似的问题,关于如何在数组中生成所有可能的元素组合。但是我很难弄清楚如何编写一个只输出组合pairs的算法。任何建议都将非常感激!

从以下数组开始(有 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);
    

【问题讨论】:

【参考方案1】:

这里有一些函数式编程解决方案:

使用 EcmaScript2019 的 flatMap:

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

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

console.log(result);

在引入 flatMap(我在 2017 年的回答)之前,您会选择 reduce[].concat(...) 以展平数组:

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);

或者:

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

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

console.log(result);

【讨论】:

添加一些扩展运算符以获得更多美感,而不是 concat()。 :D 您的第二个答案最好使用flatMap() 编写,以免嵌套子集数组:result = [...array.flatMap((v1,i) =&gt; array.slice(i+1).map(v2 =&gt; v1+' '+v1))] 感谢您的评论,@Phrogz。我更新了我的答案。当我在 2017 年写下这个答案时,flatMap 还没有被广泛使用。注意:这里不需要传播flatMap的结果。【参考方案2】:

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

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);

用 ES5 重写:

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);

【讨论】:

【参考方案3】:

虽然已经找到了解决方案,但我在这里发布了一个用于一般情况的算法,以查找 m (m&gt;n) 元素的所有组合大小 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 (let 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);

【讨论】:

能说说这个算法的时间复杂度吗? result.length = 2 对眼睛来说太刺眼了。也很遗憾该功能不是“自包含”(纯功能......)。【参考方案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']);

控制台输出:

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

如果 ((i & Math.pow(2, j)))

第一个值的每一位都与第二个值进行比较,如果匹配则认为有效,否则返回零,不满足条件。

【讨论】:

请考虑添加一些关于您的代码如何实现结果的 cmets,以便其他人更容易理解 谢谢!为您的建议 你能解释一下这部分代码在做什么吗? if ((i &amp; Math.pow(2, j))) temp.push(valuesArray[j]); 嘿,你能解释一下,这段代码是做什么的吗? if ((i &amp; Math.pow(2, j))) 喜欢单行的可以试试这个Codepen:const combinations = (arr) =&gt; [...Array(2 ** arr.length - 1).keys()].map((n) =&gt; ((n + 1) &gt;&gt;&gt; 0).toString(2).split("").reverse().map((n, i) =&gt; (+n ? arr[i] : false)).filter(Boolean)).sort((a, b) =&gt; (a.length &gt; b.length ? 1 : -1)); console.log(combinations(["apple", "banana", "lemon", "mango"]));【参考方案5】:

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


/**
 * 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;


【讨论】:

工作算法得到了广泛的解释,并具有非常明确的命名约定。【参考方案6】:

只是为下一个搜索它的人提供一个选项

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' ] ]

【讨论】:

受函数式编程启发。【参考方案7】:

我找到的最佳解决方案 - 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]

【讨论】:

唯一的问题我可以说它只以一种方式生成组合,我正在寻找组合([1,2,3], 2)来给出 [1,2][2,1][ 1,3][3,1][2,3][3,2] 没有问题只是做了 var combination=function*(e,i)for(let l=0;l 这些是排列,而不是组合【参考方案8】:

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

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

【讨论】:

如何将相同的代码更改为 4 位数字的组合并仅过滤得出总和为 9 的 4 位数字? var array = [0,1, 2, 3, 4,5,6,7,8,9] array.flatMap(x => array.map(y => x !== y ? x + ' ' + y : null)).filter(x => x) asw 例如,7227,7776....【参考方案9】:

在数组中生成元素组合很像在数字系统中计数, 其中基数是数组中元素的数量(如果您考虑了将丢失的前导零)。

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

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】:

我认为这是对所有这些问题的答案。

/**
 * 
 * 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 count = isArray ? item.length : item;

  const pow = (x, n, m = []) => 
    if (n > 0) 
      for (var i = 0; i < count; 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)); // gives array with index number

【讨论】:

很好,但这也给出了重复元素的组合,例如“AA”,这不是 OP 所要求的。 是的,它给了。我想,一个简单的 if 语句将是解决方案。 这实际上给出了重复排列【参考方案11】:

这是一种结合事物 (TS) 的非变异 ES6 方法:

function combine (tail: any[], length: number, head: any[][] = [[]]): any[][] 
  return tail.reduce((acc, tailElement) => 
    const tailHeadVariants = head.reduce((acc, headElement: any[]) => 
      const combination = [...headElement, tailElement]
      return [...acc, combination]
    , [])
    if (length === 1) return [...acc, tailHeadVariants]
    const subCombinations = combine(tail.filter(t => t !== tailElement), length - 1, tailHeadVariants)
    return [...acc, ...subCombinations]
  , [])

【讨论】:

【参考方案12】:

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

// 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 的 100 万次调用需要 3.9 秒,甚至更短(并且快两倍,我的 MacBook 需要 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)];


【讨论】:

以上是关于Javascript - 在单个数组中生成所有元素组合(成对)的主要内容,如果未能解决你的问题,请参考以下文章

PHP算法从单个集合中生成特定大小的所有组合

从数组 Javascript 生成随机元素组

jquery 从返回的数组中生成单个变量

如何在 PHP 中生成多个数组中的所有项目组合

将带有坐标的单个字符串转换为 CLLocationCoordinate2D 数组,并使用该数组在 mapView 中生成多边形

如何在python中生成数组的排列?