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) => array.slice(i+1).map(v2 => 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>n)
元素的所有组合大小 n
。在您的情况下,我们有 n=2
和 m=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 & Math.pow(2, j))) temp.push(valuesArray[j]);
嘿,你能解释一下,这段代码是做什么的吗? if ((i & Math.pow(2, j)))
喜欢单行的可以试试这个Codepen:const combinations = (arr) => [...Array(2 ** arr.length - 1).keys()].map((n) => ((n + 1) >>> 0).toString(2).split("").reverse().map((n, i) => (+n ? arr[i] : false)).filter(Boolean)).sort((a, b) => (a.length > 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使用map
和flatMap
可以完成以下操作(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 - 在单个数组中生成所有元素组合(成对)的主要内容,如果未能解决你的问题,请参考以下文章
将带有坐标的单个字符串转换为 CLLocationCoordinate2D 数组,并使用该数组在 mapView 中生成多边形