'Given an array, how to generate all combinations of subset size k?

So given input = [1, 2, 3] and k=2 this would return:

1 2
1 3
2 1
2 3
3 1
3 2

This is the closest to what I am looking for, but not quite: http://algorithms.tutorialhorizon.com/print-all-combinations-of-subset-of-size-k-from-given-array/

function subsetsOfSize(a, used, startIndex, currentSize, k) {
  if (currentSize === k) {
    for (var i = 0; i < a.length; i++) {
      if (used[i])
        console.log(a[i]);
    }
    console.log('-');
    return;
  }
    
  if (startIndex === a.length)
    return;
    
  used[startIndex] = true;
  subsetsOfSize(a, used, startIndex+1, currentSize+1, k);

  used[startIndex] = false;
  subsetsOfSize(a, used, startIndex+1, currentSize, k);
}

var input = [1,2,3];
subsetsOfSize(input, Array(input.length).fill(false), 0, 0, 2);

^ Missing results such as 2 1, 3 1, 3 2, etc.

Secondly, I am not sure if I am naming this problem correctly because solutions to "all combinations of subset of size k" do not give the expected answer.



Solution 1:[1]

Instead of combinations, try permutations.

Try generating permutations, then resizing the array.

Here's it implemented, modified from here

var permArr = [],
  usedChars = [];

function permute(input, k) {
  var i, ch;
  for (i = 0; i < input.length; i++) {
    ch = input.splice(i, 1)[0];
    usedChars.push(ch);
    if (input.length == 0) {
      var toadd = usedChars.slice(0,k);
      if(!permArr.includes(toadd)) permArr.push(toadd); // resizing the returned array to size k
    }
    permute(input, k);
    input.splice(i, 0, ch);
    usedChars.pop();
  }
  return permArr
};
console.log(JSON.stringify(permute([1, 2, 3], 2)));

Solution 2:[2]

You could take a generator function.

function* permutation(array, k, head = []) {
    if (!k) {
        yield head;
        return;
    };
    for (let i = 0; i < array.length; i++) {
        yield* permutation(array.filter((_, j) => j !== i), k - 1, [...head, array[i]]);
    }
}

// example 1
const p = permutation([1, 2, 3, 4, 5, 6], 4);
console.log(...p.next().value);
console.log(...p.next().value);
console.log(...p.next().value);
console.log(...p.next().value);
console.log(...p.next().value);
console.log(...p.next().value);
console.log(...p.next().value);
console.log(...p.next().value);

// example 2
[...permutation([1, 2, 3, 4], 3)].forEach(a => console.log(...a));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Solution 3:[3]

Here's a version which doesn't try to be too clever, and doesn't exercise any "modern" features of JavaScript other than generator functions (which are not that modern). Unlike most of the solutions here, this one works even if the input has duplicates. (It only produces each unique permutation once.)

It avoids the need for associative datatypes like Sets and Maps by simply never generating the same permutation twice. That, plus the fact that it avoids unnecessary copies of internal structures, does make it reasonably fast; at least, it seems to be measurably faster than any of the other answers to this question. (By fast, I mean "per permutation". JSBench clocked it at 4.3 million 3-permutations per second and about three million 6-permutations per second, running under Chrome on my consumer-level laptop.)

Generators are the most natural way to implement combinatoric enumeration. Trying to accumulate millions of alternatives (or more) in an array is a recipe for memory exhaustion; the size of the search space rapidly gets out of hand. Based on the above numbers, it's reasonable to attempt a search through hundreds of millions of permutations. (That will take a few minutes, maybe more, depending on how fast you can check each permutation. Still, a few minutes is OK for research purposes.) But constructing an array of hundreds of millions of permutations is going to slow things down a lot, if it is even possible on the machine you're using. In the vast majority of combinatoric searches, there is no need for such an array. You can process each candidate as it is generated, or at least filter the generated candidates in order to accumulate a (much) smaller list of feasible candidates for further processing.

If generators make you nervous for some reason, you could use an additional argument specifying a function to be called with each candidate. Or you could use a hybrid, using a check function to decide whether or not to yield the candidate. That might be a better architecture if you can rapidly discard most of the possibilities, since unwinding the yield* through several layers is quite a bit slower than just calling a function.

Parts of the following snippet were borrowed from @NinaScholz. Thanks!

function* kperm(arr, k) {
    let data = arr.slice().sort();
    k = Math.min(k, data.length);

    function* helper(i) {
        if (i == k) 
            yield data.slice(0, k);
        else {
            for (let j = i+1; j < data.length; j++) {
                if (data[j] != data[i]) {
                    yield* helper(i+1);
                    const tmp = data[i];
                    data[i] = data[j];
                    data[j] = tmp;
                }
            }
            yield* helper(i+1);
            const tmp = data[i];
            for (let j = i+1; j < data.length; j++)
                data[j-1] = data[j];
            data[data.length - 1] = tmp;
        }
    }
    yield* helper(0);
}
// example 1
console.log("Example 1, first 8 permutations");
const p = kperm([1, 2, 3, 4, 1, 2, 3, 4], 4);
for (let i = 1; i < 8; ++i) 
    console.log(...p.next().value);

console.log("Example 2");
[...kperm([1, 2, 1, 2, 2], 4)].forEach(a => console.log(...a));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Solution 4:[4]

I don't really see how the newer Set and Map can really help here. But here is a fairly simple recursive version:

const nPermutations = (xs, n) =>
  xs .length < 1 || n < 1
    ? [[]]
    : xs .flatMap (
        (x, i) => nPermutations(
          [...xs .slice (0, i), ...xs .slice (i + 1)],
          n - 1
        ). map (p => [x, ...p])
      )

console .log (nPermutations ([1, 2, 3], 2))
.as-console-wrapper {max-height: 100% !important; top: 0}

In practice, I would probably extract a function that creates a copy of an array excluding one index, like this:

const excluding = (i, xs) =>
  [...xs .slice (0, i), ...xs .slice (i + 1)]

const nPermutations = (xs, n) =>
  xs .length < 1 || n < 1
    ? [[]]
    : xs .flatMap (
        (x, i) => nPermutations (excluding (i, xs), n - 1). map (p => [x, ...p])
      )

Solution 5:[5]

I'm a simple person:

make an array M with size k

fill M with zeroes

loop this:

M[0] += 1

loop through M: *if (M[i] >= size of N) then set M[i]=0 and increase M[i+1] += 1

if M has only different numbers then you've find yourself the indices of a subset of n

loop ends when the last element of M reaches size of n - minus one a.k.a. when the * condition would cause an error

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Nina Scholz
Solution 3
Solution 4 Scott Sauyet
Solution 5 Steve