5

How would you get every possible combination of 2 elements in an array?

For example:

[
    1, 
    2, 
    3, 
    4 
]

becomes

[
    [1, 2], 
    [1, 3], 
    [1, 4], 
    [2, 1], 
    [2, 3], 
    [2, 4],
    [3, 1],
    [3, 2],
    [3, 4],
    [4, 1],
    [4, 2],
    [4, 3] 
]

This answer uses brute force but is there a functional way with Ramda and or currying? Derive every possible combination of elements in array

Community
  • 1
  • 1
sa555
  • 320
  • 1
  • 4
  • 12

5 Answers5

8

Here's an elegant solution:

//    permutations :: Number -> [a] -> [[a]]
const permutations = R.compose(R.sequence(R.of), R.flip(R.repeat));

Usage examples:

permutations(2, [1, 2, 3, 4]);
// => [[1, 1], [1, 2], ..., [4, 3], [4, 4]]

permutations(3, [1, 2, 3, 4]);
// => [[1, 1, 1], [1, 1, 2], ..., [4, 4, 3], [4, 4, 4]]
davidchambers
  • 23,918
  • 16
  • 76
  • 105
  • 5
    It is elegant, but it is returning something different from what was requested, as it repeats the elements. This returns the n-element sequences drawn from a list. And it's as elegant at that as I've seen. – Scott Sauyet Feb 29 '16 at 19:21
  • 1
    You're right, Scott. This answer is wrong, but interesting nonetheless. :) – davidchambers Feb 29 '16 at 21:14
4

Borrowing from Haskell:

as = [1, 2, 3]

f xs = do
  a <- xs
  b <- xs
  return $ if a == b then [] else [a, b]

main = print $ filter (not . null) . f $ as

This is my Ramda version:

var as = [1, 2, 3, 4]

var f = xs => 
  R.pipe(
      R.chain(a => R.map(b => a == b ? [] : [a, b])(xs))
    , R.filter(R.pipe(R.isEmpty, R.not))
  )(xs)

console.log(f(as))

PS. LiveScript has a nice syntax for this: http://homam.github.io/try-livescript/#welcome/lists

For choosing a subset of ant size: Ramda code

var g = (xs, n) =>
  n == 0 ? [[]] : 
    R.isEmpty(xs) ? [] : 
      R.concat(
          R.map(R.prepend(R.head(xs)), g(R.tail(xs), n - 1))
        , g(R.tail(xs), n)
      )

g(as, 3)
homam
  • 1,945
  • 1
  • 19
  • 26
  • 1
    You can also skip the explicit filter by using `chain` instead of `map` (and dropping the `return` in the haskell example) e.g. `chain(a => chain(b => a == b ? [] : [[a, b]], xs), xs)` – Scott Christopher Feb 29 '16 at 09:41
  • This updated version answers perhaps a more interesting question than the OP asked, finding, say, all the 2-element combinations from a set of four distinct elements. But the question distinguished between `[1, 2]` and `[2, 1]`, which this doesn't do. – Scott Sauyet Mar 01 '16 at 15:07
3

If you just want two elements, the answer from Oriol should do you fine. But if you want something that extends to any size subgroup, something like this might do:

const permutations = (n, tokens, subperms = [[]]) =>
  n < 1 || n > tokens.length ?
    subperms        :
    R.addIndex(R.chain)((token, idx) => permutations(
      n - 1,
      R.remove(idx, 1, tokens), 
      R.compose(R.map, R.append)(token)(subperms)
    ), tokens);


permutations(2, [1, 2, 3, 4]);
//=> [[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], 
//    [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]

permutations(3, [1, 2, 3, 4]);
//=> [[1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 4], [1, 4, 2], [1, 4, 3],
//    [2, 1, 3], [2, 1, 4], [2, 3, 1], [2, 3, 4], [2, 4, 1], [2, 4, 3],
//    [3, 1, 2], [3, 1, 4], [3, 2, 1], [3, 2, 4], [3, 4, 1], [3, 4, 2],
//    [4, 1, 2], [4, 1, 3], [4, 2, 1], [4, 2, 3], [4, 3, 1], [4, 3, 2]]

This version was slightly adapted from one I presented in Ramda's Gitter room. There I suggested it was overwrought, but that was for full permutations. It seems appropriate for n-combinations.

You can see it in action on the Ramda REPL.

Community
  • 1
  • 1
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
2

You don't need any library, you can do it trivially in vanilla-js using a nested loop:

var arr = [1, 2, 3, 4],
    result = [];
for(var i=0; i<arr.length; ++i)
  for(var j=0; j<arr.length; ++j)
    if(i !== j)
      result.push([arr[i], arr[j]]);
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 2
    But does this extend straightforwardly to any-sized subgroups? – Scott Sauyet Feb 29 '16 at 00:54
  • @ScottSauyet No, but the question is about 2-conbinations, not n-combinations. – Oriol Feb 29 '16 at 01:02
  • You're right. I guess it was in a follow-up comment to another answer that the OP made it clear that the goal was probably more general. Yours is clearly the best answer if the goal is simply 2-combinators. – Scott Sauyet Feb 29 '16 at 01:06
0

This will work for any length of permutations just adjust it to cut off at 2.

function permutate(input, output) {
  if (input.length === 0) {
    document.body.innerHTML += "<div>" + output + "</div>";
  }

  for (var i = 0; i < input.length; i++) {
    output.push(input[i]);
    permutate(input.slice(0, i).concat(input.slice(i + 1)), output);
    output.pop();
  }
}

permutate([1, 2, 3, 4], []);
highstakes
  • 1,499
  • 9
  • 14