4

Let's say I have a set of elements S = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }

I would like to create combinations of 3 and group them in a way such that no number appears in more than one combination.

Here is an example: { {3, 7, 9}, {1, 2, 4}, {5, 6, 8} }

The order of the numbers in the groups does not matter, nor does the order of the groups in the entire example.

In short, I want every possible group combination from every possible combination in the original set, excluding the ones that have a number appearing in multiple groups.

My question: is this actually feasible in terms of run time and memory? My sample sizes could be somewhere around 30-50 numbers.

If so, what is the best way to create this algorithm? Would it be best to create all possible combinations, and choose the groups only if the number hasn't already appeared?

I'm writing this in Qt 5.6, which is a C++ based framework.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Dillydill123
  • 693
  • 7
  • 22
  • 1
    Is it normal that 4 appears in `{4, 7, 9}` AND `{1, 2, 4}` ? – L. Meyer Aug 24 '16 at 15:00
  • Wow today is not my day. It is not supposed to be. I'll fix that. – Dillydill123 Aug 24 '16 at 15:00
  • "is this actually feasible in terms of run time and memory?" We can bound the number of operations by 50 choose 3 which is 19600, so probably not so bad. Combinatorial complexity, though, is not great in general. – AndyG Aug 24 '16 at 15:40
  • does the order of the *groups* matter? would `{ {3,7,9}, {1,2,4}, {5,6,8} }` be a valid member of the same solution as `{ {1,2,4}, {3,7,9}, {5,6,8} }`? – jaggedSpire Aug 24 '16 at 15:41
  • Do you want to group the numbers 3 by 3 in _n_ groups or in 3 groups of _n_ numbers ? You inconveniently chose a set of 3² elements, so that isn't clear from your example. – Nelfeal Aug 24 '16 at 15:44
  • @jaggedSpire the order of the groups does not matter, so those solutions are the same – Dillydill123 Aug 24 '16 at 15:47
  • @Nelxiost I'm not sure what you mean. I want groups of three. so if there were 12 numbers, there would be 4 groups of numbers, each with 3 numbers in them. – Dillydill123 Aug 24 '16 at 15:49
  • For 10 groups of 3 (30 numbers), there are 1,208,883,745,669,600,000 possible partitionings, so enumerating all of them is probably impractical. – rici Aug 24 '16 at 18:06
  • @rici thank you, is there a link or a formula you used to calculate that number? I'm just wondering out of curiousity – Dillydill123 Aug 24 '16 at 18:07
  • Now I see such sequence: 1, 10, 280, 15400, 1401400.... – MBo Aug 24 '16 at 18:08
  • @rici considering the request of OP _"The order of the numbers in the groups does not matter, nor does the order of the groups in the entire example."_ and _"excluding the ones that have a number appearing in multiple groups"_ what are the solutions that I'm not counting in my answer? – Bob__ Aug 24 '16 at 18:09
  • 1
    @Dillydill123: Based on m69's correct algorithm, the formula for k groups is 3k-1*3k-2*3k-4*3k-5*...*1/2**k. In other words, the product of all the numbers from 1 to 3k which are not 0 mod 3, divided by 2 to the power of k. I computed it with python. – rici Aug 24 '16 at 18:09
  • @Bob_: since you only show 12 out of the claimed 55, how would I know which ones are missing? – rici Aug 24 '16 at 18:10
  • @rici look better. – Bob__ Aug 24 '16 at 18:12
  • @Bob__: I look just fine, thanks :) I think you meant, "please look again, I've edited my answer". None of your partitions includes, for example, the triple `{1,6,7}`, so there are 280 partitions right there. (I just took that one at random) One concrete example: `{{1,6,7},{2,3,4},{5,8,9},{10,11,12}}` – rici Aug 24 '16 at 18:16
  • 2
    Short formula: `(3k)! / (k! * 6^k)` – MBo Aug 24 '16 at 18:22
  • @rici Thanks, I see the error now (I only considered to mix two different groups, while I could take a number form three groups instead). – Bob__ Aug 24 '16 at 18:31
  • @MBo I'm indeed getting 1, 10, 280, 15400, 1401400, 190590400 solutions. – m69's been on strike for years Aug 24 '16 at 18:38
  • 1
    The size of the output is [this sequence](https://oeis.org/search?q=1%2C10%2C280%2C15400&language=english&go=Search). It is totally infeasible for 30 elements. – n. m. could be an AI Aug 25 '16 at 05:08

3 Answers3

13

You can do this recursively, and avoid duplicates, if you keep the first element fixed in each recursion, and only make groups of 3 with the values in order, eg:

{1,2,3,4,5,6,7,8,9}

Put the lowest element in the first spot (a), and keep it there:

{a,b,c} = {1, *, *}

For the second spot (b), iterate over every value from the second-lowest to the second-highest:

{a,b,c} = {1, 2~8, *}

For the third spot (c), iterate over every value higher than the second value:

{1, 2~8, b+1~9}

Then recurse with the rest of the values.

{1,2,3} {4,5,6} {7,8,9}
{1,2,3} {4,5,7} {6,8,9}
{1,2,3} {4,5,8} {6,7,9}
{1,2,3} {4,5,9} {6,7,8}
{1,2,3} {4,6,7} {5,8,9}
{1,2,3} {4,6,8} {5,7,9}
{1,2,3} {4,6,9} {5,7,8}
{1,2,3} {4,7,8} {5,6,9}
{1,2,3} {4,7,9} {5,6,8}
{1,2,3} {4,8,9} {5,6,7}
{1,2,4} {3,5,6} {7,8,9}
...
{1,8,9} {2,6,7} {3,4,5}

Wen I say "in order", that doesn't have to be any specific order (numerical, alphabetical...), it can just be the original order of the input. You can avoid having to re-sort the input of each recursion if you make sure to pass the rest of the values on to the next recursion in the order you received them.


A run-through of the recursion:

Let's say you get the input {1,2,3,4,5,6,7,8,9}. As the first element in the group, you take the first element from the input, and for the other two elements, you iterate over the other values:

{1,2,3}
{1,2,4}
{1,2,5}
{1,2,6}
{1,2,7}
{1,2,8}
{1,2,9}
{1,3,4}
{1,3,5}
{1,3,6}
...
{1,8,9}

making sure the third element always comes after the second element, to avoid duplicates like:

{1,3,5} ⇆ {1,5,3}

Now, let's say that at a certain point, you've selected this as the first group:

{1,3,7}

You then pass the rest of the values onto the next recursion:

{2,4,5,6,8,9}

In this recursion, you apply the same rules as for the first group: take the first element as the first element in the group and keep it there, and iterate over the other values for the second and third element:

{2,4,5}
{2,4,6}
{2,4,8}
{2,4,9}
{2,5,6}
{2,5,8}
{2,5,9}
{2,6,7}
...
{2,8,9}

Now, let's say that at a certain point, you've selected this as the second group:

{2,5,6}

You then pass the rest of the values onto the next recursion:

{4,8,9}

And since this is the last group, there is only one possibility, and so this particular recursion would end in the combination:

{1,3,7} {2,5,6} {4,8,9}

As you see, you don't have to sort the values at any point, as long as you pass them onto the next recursion in the order you recevied them. So if you receive e.g.:

{q,w,e,r,t,y,u,i,o}

and you select from this the group:

{q,r,u}

then you should pass on:

{w,e,t,y,i,o}


Here's a JavaScript snippet which demonstrates the method; it returns a 3D array with combinations of groups of elements.
(The filter function creates a copy of the input array, with elements 0, i and j removed.)

function clone2D(array) {
    var clone = [];
    for (var i = 0; i < array.length; i++) clone.push(array[i].slice());
    return clone;
}

function groupThree(input) {
    var result = [], combination = [];
    group(input, 0);
    return result;

    function group(input, step) {
        combination[step] = [input[0]];
        for (var i = 1; i < input.length - 1; i++) {
            combination[step][1] = input[i];
            for (var j = i + 1; j < input.length; j++) {
                combination[step][2] = input[j];
                if (input.length > 3) {
                    var rest = input.filter(function(elem, index) {
                        return index && index != i && index != j;
                    });
                    group(rest, step + 1);
                }
                else result.push(clone2D(combination));
            }
        }
    }
}

var result = groupThree([1,2,3,4,5,6,7,8,9]);
for (var r in result) document.write(JSON.stringify(result[r]) + "<br>");
  • This looks interesting, and may be just what I need. I noticed that you have an error in the second set after you say "recurse with the rest of the values." The number 7 appears in more than one group. When you talk about spots (a) (b) and (c), are these only for the first set of 3 numbers? Also, can you explain how I recurse with the rest of the values? I'm not seeing the whole picture here – Dillydill123 Aug 24 '16 at 17:35
  • @Dillydill123 I've expanded the answer a bit on a computer with a working keyboard :-) – m69's been on strike for years Aug 24 '16 at 18:08
  • 1
    Thank you for your very in-depth answer!! Unfortunately with my large sample sizes it will not be feasible to create all combinations. I will accept your answer though! – Dillydill123 Aug 24 '16 at 18:14
  • could you tell me the purpose of the array g? I'm not familiar with JavaScript and right now it doesn't look like it does anything to me. – Dillydill123 Aug 24 '16 at 19:04
  • @Dillydill123 That was a left-over from a first version of the code, and I forgot to remove it; sorry about that. I've fixed the code example a bit. – m69's been on strike for years Aug 24 '16 at 22:04
0

For n things taken 3 at a time, you could use 3 nested loops:

    for(k = 0; k < n-2; k++){
        for(j = k+1; j < n-1; j++){
            for(i = j+1; i < n  ; i++){
                ...  S[k] ... S[j] ... S[i]
            }
        }
    }

For a generic solution of n things taken k at a time, you could use an array of k counters.

rcgldr
  • 27,407
  • 3
  • 36
  • 61
0

I think You can solve it by using coin change problem with dynamic programming, just assume You are looking for change of 3 and every index in array is a coin value 1, then just output coins(values in Your array) that has been found. Link: https://www.youtube.com/watch?v=18NVyOI_690

Mateusz Wojtczak
  • 1,621
  • 1
  • 12
  • 28