-1

What is the best way to implement a function that takes three arguments

  • smallest length of combinations
  • highest length of combinations
  • array of values

and returns all combinations of length l (arg1 <= l <= arg2). E.g.

getComb (2, 2, [1, 2, 3]) === [[1,2], [2,3], [3,1]]
getComb (0, 3, [1, 2, 3]) === [[],[1],[2],[3],[1,2],[2,3],[3,1],[1,2,3]]

(=== is defined here as deep equals without respect to order (almost set equality for both depths of the array) Also duplicate values should be ignored (e.g. getComb(a, b, [x,x,y]) === getComb(a, b, [x,y]) for all a, b, x, y)

Then a fn to get all combinations can be implemented:

getAllComb = arr => getComb (0, arr.length, arr) 

Thanks!

Dominik Teiml
  • 505
  • 1
  • 4
  • 12
  • 1
    please add your try. – Nina Scholz Sep 28 '19 at 19:34
  • What is the idea - take an array and generate all distinct combinations of any two items? Does the order of the items in the sub-groups matter - e.g., is `[2, 1]` an acceptable replacement for `[1, 2]`? – VLAZ Sep 28 '19 at 19:56
  • what should happen with more than three items in the array? – Nina Scholz Sep 28 '19 at 19:59
  • Also - what if you have duplicate items? – VLAZ Sep 28 '19 at 20:00
  • Let's have some fun. I'm editing the question to be more generalized. Re order matter - it doesn't. Any solution is accepted ;) Re duplicate items - they should not be counted. (e.g. getComb([x,x,y]) === getComb([x,y]) for all x,y – Dominik Teiml Sep 28 '19 at 20:09
  • Done generalizing – Dominik Teiml Sep 28 '19 at 20:18
  • I will also add my write my solution now – Dominik Teiml Sep 28 '19 at 20:19
  • Ok I tried a solution with a generalized aperture. one that also takes in an arbitrary distance, so the values don't have to be consecutive. After coding the whole thing I realized it still doesn't solve the problem, e.g. for array `[1,2,3,4,5]`, [1,2,4] wouldn't appear bc the values have differing differences. Bummer! Now I have a solution I'm kinda proud of, gonna post it – Dominik Teiml Sep 29 '19 at 06:42

5 Answers5

4

Here's another recursive solution, structured slightly differently from the answer by Nina Scholz. It has a function to choose exactly n elements from the list, and then uses that in the main function, which calls it for each value from min to max:

const choose = (n, xs) =>
  n < 1 || n > xs .length
    ? []
    : n == 1
      ? [...xs .map (x => [x])]
      : [
          ...choose (n - 1, xs .slice (1)) .map (ys => [xs [0], ...ys]),
          ...choose (n , xs .slice (1))
        ]

const getCombs = (min, max, xs) => 
  xs .length == 0 || min > max
    ? [] 
    : [...choose (min, xs), ...getCombs (min + 1, max, xs)]


console .log (
  getCombs (0, 3, [1, 2, 3]),
  getCombs (2, 2, [1, 2, 3])
)

Here getCombs is the main function, and should be fairly clear, just concatenating the result of choose (min, xs) with the result of the recursive call to getCombs (min + 1, max, xs). choose is a nicely reusable function which operates on a double recursion, the first one selecting all those combinations which use the initial element and the second all those that don't.

This doesn't quite match Nina's solution, as it ignores the empty list when min is zero. If you want one that includes the empty list, you could change choose to the (slightly uglier, IMHO) version:

const choose = (n, xs) =>
  n < 1 || n > xs .length
    ? [[]]
    : [
        ...choose (n - 1, xs .slice (1)) .map (ys => [xs [0], ...ys]),
        ...(n + 1 > xs .length ? [] : choose (n , xs .slice (1)))
      ]
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
1

one way to implement getComb is :

[1,2,3].reduce( (acc, v, i, original) =>
    acc.concat(original.slice(i+1).map( w => [w, v] )),
[]);
tlrmacl
  • 51
  • 1
1

You could take a recursive approach.

function getComb(min, max, array) {
    function iter(left, right = [], push = true) {
        if (push && min <= right.length && right.length <= max) result.push(right);
        if (!left.length) return;
        iter(left.slice(1), [...right, left[0]]);
        iter(left.slice(1), right, false);
    }

    var result = [];
    iter(array);
    return result;
}

console.log(getComb(2, 2, [1, 2, 3]));
console.log(getComb(0, 3, [1, 2, 3]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

Ok I have a partial solution (for a = 1, b = arr.length):

const list = R.unapply (R.identity)
const xproduct = arr => R.apply (R.liftN (arr.length) (list)) (arr)
const getPerm = arr => xproduct (R.repeat (arr) (arr.length))
const getComb = arr => R.uniq (R.map (R.uniq) (getPerm (arr)))

getComb([1,2,3]) === [[1],[2],[3],[1,2],[2,3],[3,1],[1,2,3]]

There's got to be something better ;)

Dominik Teiml
  • 505
  • 1
  • 4
  • 12
-2

Here's a solution (atleast to getAllComb) that I'm kinda proud of :) There's a lot of stuff, but most of it is boilerplate

Inspired by bitstrings

// Generic helper functions
const appendIfNotFalse = fn => (acc, val) => R.ifElse (R.equals (false)) (R.always (acc)) (R.flip (R.append) (acc)) (fn (acc, val))

const mapAndFilter = fn => arr => R.reduce (appendIfNotFalse (fn)) ([]) (arr)

// My helper fn
const has1InBitstring = n => idx => (n & 2 ** idx) > 0

// Soltuion
const indices = arr => key => mapAndFilter ((_, j) => has1InBitstring (key) (j) ? j : false) (R.range (0) (arr.length))

const getAllComb = arr => R.times (i => R.props (indices (arr) (i)) (arr)) (2 ** arr.length)

// Example
getAllComb ([1,2,3]) === [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
Dominik Teiml
  • 505
  • 1
  • 4
  • 12