-
Star
(190)
You must be signed in to star a gist -
Fork
(44)
You must be signed in to fork a gist
-
-
Save axelpale/3118596 to your computer and use it in GitHub Desktop.
| /** | |
| * Copyright 2012 Akseli Palén. | |
| * Created 2012-07-15. | |
| * Licensed under the MIT license. | |
| * | |
| * <license> | |
| * Permission is hereby granted, free of charge, to any person obtaining | |
| * a copy of this software and associated documentation files | |
| * (the "Software"), to deal in the Software without restriction, | |
| * including without limitation the rights to use, copy, modify, merge, | |
| * publish, distribute, sublicense, and/or sell copies of the Software, | |
| * and to permit persons to whom the Software is furnished to do so, | |
| * subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be | |
| * included in all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| * SOFTWARE. | |
| * </lisence> | |
| * | |
| * Implements functions to calculate combinations of elements in JS Arrays. | |
| * | |
| * Functions: | |
| * k_combinations(set, k) -- Return all k-sized combinations in a set | |
| * combinations(set) -- Return all combinations of the set | |
| */ | |
| /** | |
| * K-combinations | |
| * | |
| * Get k-sized combinations of elements in a set. | |
| * | |
| * Usage: | |
| * k_combinations(set, k) | |
| * | |
| * Parameters: | |
| * set: Array of objects of any type. They are treated as unique. | |
| * k: size of combinations to search for. | |
| * | |
| * Return: | |
| * Array of found combinations, size of a combination is k. | |
| * | |
| * Examples: | |
| * | |
| * k_combinations([1, 2, 3], 1) | |
| * -> [[1], [2], [3]] | |
| * | |
| * k_combinations([1, 2, 3], 2) | |
| * -> [[1,2], [1,3], [2, 3] | |
| * | |
| * k_combinations([1, 2, 3], 3) | |
| * -> [[1, 2, 3]] | |
| * | |
| * k_combinations([1, 2, 3], 4) | |
| * -> [] | |
| * | |
| * k_combinations([1, 2, 3], 0) | |
| * -> [] | |
| * | |
| * k_combinations([1, 2, 3], -1) | |
| * -> [] | |
| * | |
| * k_combinations([], 0) | |
| * -> [] | |
| */ | |
| function k_combinations(set, k) { | |
| var i, j, combs, head, tailcombs; | |
| // There is no way to take e.g. sets of 5 elements from | |
| // a set of 4. | |
| if (k > set.length || k <= 0) { | |
| return []; | |
| } | |
| // K-sized set has only one K-sized subset. | |
| if (k == set.length) { | |
| return [set]; | |
| } | |
| // There is N 1-sized subsets in a N-sized set. | |
| if (k == 1) { | |
| combs = []; | |
| for (i = 0; i < set.length; i++) { | |
| combs.push([set[i]]); | |
| } | |
| return combs; | |
| } | |
| // Assert {1 < k < set.length} | |
| // Algorithm description: | |
| // To get k-combinations of a set, we want to join each element | |
| // with all (k-1)-combinations of the other elements. The set of | |
| // these k-sized sets would be the desired result. However, as we | |
| // represent sets with lists, we need to take duplicates into | |
| // account. To avoid producing duplicates and also unnecessary | |
| // computing, we use the following approach: each element i | |
| // divides the list into three: the preceding elements, the | |
| // current element i, and the subsequent elements. For the first | |
| // element, the list of preceding elements is empty. For element i, | |
| // we compute the (k-1)-computations of the subsequent elements, | |
| // join each with the element i, and store the joined to the set of | |
| // computed k-combinations. We do not need to take the preceding | |
| // elements into account, because they have already been the i:th | |
| // element so they are already computed and stored. When the length | |
| // of the subsequent list drops below (k-1), we cannot find any | |
| // (k-1)-combs, hence the upper limit for the iteration: | |
| combs = []; | |
| for (i = 0; i < set.length - k + 1; i++) { | |
| // head is a list that includes only our current element. | |
| head = set.slice(i, i + 1); | |
| // We take smaller combinations from the subsequent elements | |
| tailcombs = k_combinations(set.slice(i + 1), k - 1); | |
| // For each (k-1)-combination we join it with the current | |
| // and store it to the set of k-combinations. | |
| for (j = 0; j < tailcombs.length; j++) { | |
| combs.push(head.concat(tailcombs[j])); | |
| } | |
| } | |
| return combs; | |
| } | |
| /** | |
| * Combinations | |
| * | |
| * Get all possible combinations of elements in a set. | |
| * | |
| * Usage: | |
| * combinations(set) | |
| * | |
| * Examples: | |
| * | |
| * combinations([1, 2, 3]) | |
| * -> [[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]] | |
| * | |
| * combinations([1]) | |
| * -> [[1]] | |
| */ | |
| function combinations(set) { | |
| var k, i, combs, k_combs; | |
| combs = []; | |
| // Calculate all non-empty k-combinations | |
| for (k = 1; k <= set.length; k++) { | |
| k_combs = k_combinations(set, k); | |
| for (i = 0; i < k_combs.length; i++) { | |
| combs.push(k_combs[i]); | |
| } | |
| } | |
| return combs; | |
| } |
@danbars Thanks for the links. +1
Does Not work for this set "ababb" it repeats elements and also "abbba", "babab" are missing
@foluis all member elements must be considered unique in order to form a set mathematically
possible combination of 1, 2, 3 are following,may be i am wrong
[1], [2], [3], [1, 2], [1, 3], [2, 3], [2, 1], [3, 1], [3, 2], [1, 2, 3], [3, 2, 1]
Thanks a lot!
Thanks!
@imVinayPandya Looks like he is using N choose K which should only return unique combinations. Such as 1,2 and 2,1 are the same. Also, you are setting the number of members for the k combination. Thus this will only return single member combinations, or double member combinations or triple member combinations etc etc etc.
thanks!
Here is a short hand version using underscore and some es6 syntax for readability
"use strict";
//https://gist.github.com/axelpale/3118596
const _ = require('underscore');
const elemTransform = elem => [elem];
const tailcombPush = (combs, elem) => tailcomb => combs.push([elem, ...tailcomb]);
const k_combPush = combs => k_komb => combs.push(k_komb);
function k_combinations(set, k) {
const setLen = set.length;
if (k > setLen || k <= 0) {
return [];
}
if (k === setLen) {
return [set];
}
if (k === 1) {
return _.map(set, elemTransform);
}
const combs = [];
for (let i = 0; i < setLen - k + 1; i++) {
_.each(k_combinations(set.slice(i + 1), k - 1), tailcombPush(combs, set[i]));
}
return combs;
}
function combinations(set) {
const combs = [];
for (let k = 1; k <= set.length; k++) {
_.each(k_combinations(set, k), k_combPush(combs));
}
return combs;
}
module.exports = {
k_combinations,
combinations
};Here's an es6 version
const k_combinations = (set, k) => {
if (k > set.length || k <= 0) {
return []
}
if (k === set.length) {
return [set]
}
const combs = []
if (k === 1) {
for (let i = 0; i < set.length; i++) {
combs.push([set[i]])
}
return combs
}
for (let i = 0; i < set.length - k + 1; i++) {
const head = set.slice(i, i + 1)
const tailcombs = k_combinations(set.slice(i + 1), k - 1)
for (let j = 0; j < tailcombs.length; j++) {
combs.push(head.concat(tailcombs[j]))
}
}
return combs
}
const combinations = (set) => {
const combs = [];
for (let k = 1; k <= set.length; k++) {
const k_combs = k_combinations(set, k)
for (let i = 0; i < k_combs.length; i++) {
combs.push(k_combs[i])
}
}
return combs
}ES6 version:
const k_combinations = (set, k) => {
if (k > set.length || k <= 0) {
return []
}
if (k == set.length) {
return [set]
}
if (k == 1) {
return set.reduce((acc, cur) => [...acc, [cur]], [])
}
let combs = [], tail_combs = []
for (let i = 0; i <= set.length - k + 1; i++) {
tail_combs = k_combinations(set.slice(i + 1), k - 1)
for (let j = 0; j < tail_combs.length; j++) {
combs.push([set[i], ...tail_combs[j]])
}
}
return combs
}
const combinations = set => {
return set.reduce((acc, cur, idx) => [...acc, ...k_combinations(set, idx + 1)], [])
}And here is a (generic) typescript version, based on the ES6 version by @luispaulorsl:
export const k_combinations = <T>(set: T[], k: number) => {
if (k > set.length || k <= 0) {
return [];
}
if (k === set.length) {
return [set];
}
if (k === 1) {
return set.reduce((acc, cur) => [...acc, [cur]], [] as T[][]);
}
const combs = [] as T[][];
let tail_combs = [];
for (let i = 0; i <= set.length - k + 1; i++) {
tail_combs = k_combinations(set.slice(i + 1), k - 1);
for (let j = 0; j < tail_combs.length; j++) {
combs.push([set[i], ...tail_combs[j]]);
}
}
return combs;
};
export const combinations = <T>(set: T[]) => {
return set.reduce((acc, _cur, idx) => [...acc, ...k_combinations(set, idx + 1)], [] as T[][]);
};Thanks. Here is just a little addition to @luispaulorsl code to be able to limit the range of the combinations with a min and max length.
// REF: https://gist.github.com/axelpale/3118596#gistcomment-2751797
const k_combinations = (set, k) => {
if (k > set.length || k <= 0) {
return [];
}
if (k === set.length) {
return [set];
}
if (k === 1) {
return set.reduce((acc, cur) => [...acc, [cur]], []);
}
let combs = [],
tail_combs = [];
for (let i = 0; i <= set.length - k + 1; i++) {
tail_combs = k_combinations(set.slice(i + 1), k - 1);
for (let j = 0; j < tail_combs.length; j++) {
combs.push([set[i], ...tail_combs[j]]);
}
}
return combs;
};
const combinations = (set, min, max) => {
const arr = Array.from({ length: max - min + 1 }, (_, i) => i + min);
return arr.reduce((acc, cur, idx) => {
return [...acc, ...k_combinations(set, cur)];
}, []);
};typescript version
export function combinations<T>(set: Array<T>, k: number): Array<Array<T>> {
if (k > set.length || k <= 0) return [];
if (k === set.length) return [set];
return set.reduce((p, c, i) => {
combinations(set.slice(i + 1), k - 1)
.forEach(tailArray => p.push([c].concat(tailArray)));
return p;
}, []);
}I wish one of these versions could yield instead of generating and returning the whole thing.
mine was a little too short:
function combinations<T>(set: Array<T>, k: number): Array<Array<T>> {
// src: https://gist.github.com/axelpale/3118596
if (k > set.length || k <= 0) return []
if (k === set.length) return [set]
if (k === 1) return set.map(x => [x])
return set.reduce((p, c, i) => {
combinations(set.slice(i + 1), k - 1).forEach(tailArray =>
p.push([c].concat(tailArray)),
)
return p
}, [])
}@tan14142 interesting thought.. something kinda like this?
function* combinations<T>(
set: Array<T>,
k: number,
): Generator<Array<T>, Array<T>, undefined> {
if (k > set.length || k <= 0) yield []
if (k === 1) yield* set.map(x => [x])
for (const i in set) {
const x = set[i]
for (const next of combinations<T>(set.slice(+i + 1), k - 1)) {
yield [x].concat(next)
}
}
return
}that's almost not legible to me .. prettier is .. much less pretty with typescript. here's the js version:
function* combinations(set, k) {
if (k > set.length || k <= 0) yield []
if (k === 1) yield* set.map(x => [x])
for (let i in set) {
const x = set[i]
for (const next of combos(set.slice(+i + 1), k - 1)) {
yield [x].concat(next)
}
}
}producing for example:
JSON.stringify([...combinations([1,2,3,4], 2)])
'[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]'
JSON.stringify([...combinations([1,2,3,4], 3)])
'[[1,2,3],[1,2,4],[1,3,4],[2,3,4]]'
you could verify that it works like so:
function* combinations(set, k) {
console.log('foo')
if (k > set.length || k <= 0) yield []
if (k === 1) yield* set.map(x => [x])
for (let i in set) {
const x = set[i]
for (const next of combinations(set.slice(+i + 1), k - 1)) {
yield [x].concat(next)
}
}
}
function* take( num, iter ) {
let item = iter.next()
for( let index = 0; index < num && !item.done ; index++) {
yield item.value
item = iter.next()
}
}
JSON.stringify([...take(3,combinations([1,2,3,4], 2))])
VM35607:2 foo
VM35607:2 foo
VM35607:2 foo
'[[1,2],[1,3],[1,4]]'I felt clever but looking at larger examples like 10 choose 4 .. my generator is returning too long of entries sometimes
I got some help debugging that:
function* combinations(set, k) {
if (k > set.length || k <= 0) {
return
}
if (k === set.length) {
yield set
return
}
if (k === 1) {
yield* set.map(x => [x])
return
}
for (let i=0,lim=set.length; i<lim; i++) {
for (const next of combinations(set.slice(i + 1), k - 1)) {
yield [set[i]].concat(next)
}
}
return
}Bergi actually suggested a slightly tighter solution than we'd covered in this gist:
function* combinations(set, k) {
if (k >= 0 && k <= set.length) {
if (k == 0) {
yield [];
} else {
for (const [i, v] of set.entries())
for (const next of combinations(set.slice(i + 1), k - 1)) {
yield [v, ...next];
}
}
}
}
}Here's a generator version in typescript that doesn't use recursion:
function* combinations<T>(arr: T[], size: number): Generator<T[], void, unknown> {
if (size < 0 || arr.length < size)
return; // invalid parameters, no combinations possible
// generate the initial combination indices
const combIndices: number[] = Array.from(Array(size).keys());
while (true)
{
yield combIndices.map(x => arr[x]);
// find first index to update
let indexToUpdate = size - 1;
while (indexToUpdate >= 0 && combIndices[indexToUpdate] >= arr.length - size + indexToUpdate)
indexToUpdate--;
if (indexToUpdate < 0)
return;
// update combination indices
for (let combIndex = combIndices[indexToUpdate] + 1; indexToUpdate < size; indexToUpdate++, combIndex++)
combIndices[indexToUpdate] = combIndex;
}
}
I wanted a non-recursive version so I didn't have to worry about tail recursion and call stacks for potentially large power sets. However, I haven't thought about these limitations too deeply or actually tested its performance vs the solutions above.
This is based on someone's C# answer from 2015 here.
Is the above gist available on npm somewhere? I want to use it in my library, right now I've just copy & pasted it.