1

There are lots of questions and answers for combination algorithms but in all my searching I haven't seen this problem anywhere before. This is as much a mathematical challenge as a coding challenge.

The Problem:

If I have a set of distinguished items and a set of undistinguished boxes, how do I find all the combinations of items in boxes?
Bear in mind, it's not just the number of combinations that we want, the program must output, all the possible combinations.

Rules:

  • All the objects must be used
  • The order the objects are placed in the box doesn't matter
  • The order the boxes are positioned in doesn't matter
  • Boxes have no limit on the number of items they can contain
  • The only thing that distinguishes a box is the items it contains

Examples of equivalency:

  • [ab][cd][ ] is equivalent to [ab][ ][cd]
  • [abcd][ ][ ] is equivelent to [bdac][ ][ ]
  • [ab][c][d] is equivalent to [ab][d][c]

I can sit down with a pen and paper to draw out all the combinations but I can't work out what algorithm my brain is using.
In this code a, b, c and d are items.

std::vector<char> unsorted = { 'a', 'b', 'c', 'd' };
int box_count = 3;

std::vector<std::vector<std::vector<char>>> sorted = {};
sorted = FillBoxes(unsorted, box_count);

Expected Result:

sorted = {
    { {a}, {b}, {c,d}},
    { {a}, {b,c}, {d} },
    { {a}, {b,d}, {c} },
    { {a}, {b,c,d}, {} },
    { {a,b}, {c}, {d} },
    { {a,b}, {c,d}, {} },
    { {a,c}, {b}, {d} },
    { {a,c}, {b,d}, {} },
    { {a,d}, {b}, {c} },
    { {a,d}, {b,c}, {} },
    { {a,b,c}, {d}, {} },
    { {a,b,d}, {c}, {} },
    { {a,c,d}, {b}, {} },
    { {a,b,c,d}, {}, {} }
}

I am looking for a logical algorithm that does the job of FillBoxes(), and outputs the list as seen in sorted.
I have had a few ideas involving binary trees and iterative pointers but none of them have worked. It looks like a solvable problem but if it is mathematically impossible, I'd appreciate the feedback too.
Thanks!





(prefered language c++ but I can read most common programming languages)

Community
  • 1
  • 1
  • 2
    Do you know the simple, basic, formula that calculates the total number of possible combinations? If you don't, you need to learn the formula first, and understand why it works. Then you should easily see how to implement it in C++. But without knowing the required math, you cannot do this. And if you do know the formula, you need to explain which ***specific*** part of it you are unclear about, as far as implementing it in C++ goes. – Sam Varshavchik Jan 28 '20 at 12:11
  • 1
    I can understand "how to applay math to get the combinations using a code" but I can't understand the opposite!!! – asmmo Jan 28 '20 at 12:14
  • 1
    Hi Sam, It's not the number of combinations that I'm looking for, it's the actual layout of the combinations. I'm looking to know how one might go about building the function 'FillBoxes()'. This missing function is just one part of a larger project that involves passengers in lifts. Also, this is my first ever question on StackOverflow so if I've asked too much, happy to take feedback. Thanks! – Matthew Appleby Jan 28 '20 at 12:27
  • As for a possible answer, I think you are using the wrong word when searching. As I don't know your level of math, I find that the more math you learn the easier programming gets, especially when it comes to understanding algorithms and [big-o](https://stackoverflow.com/questions/tagged/big-o). If you search for partition I know it will put you on the path to what you seek, e.g. Wikipedia [partition](https://en.wikipedia.org/wiki/Partition_of_a_set). While a partition can not have an empty set, there is nothing from having multiple partitions added together to form the answer. – Guy Coder Jan 28 '20 at 13:00
  • I did look for a name of partitions that allow empty sets and found [Terminology for a Partition of a Set which Includes Empty Sets](https://mathoverflow.net/q/145342) – Guy Coder Jan 28 '20 at 13:00
  • 1
    It seems you want the Cartesian product of `{a0, a1, a2}x{b0, b1, b2}x{c0, c1, c2}x{d0, d1, d2}` (so `{a0, b0, c0, d0}` means all `a`, `b`, `c`, `d` goes in group 0, whereas `{a2, b0, c2, d1}` means that `a` and `c` goes in group 2, `b` goes in group `0` and `d` goes in group 1). – Jarod42 Jan 28 '20 at 13:09
  • Reading [Partitions and Permutations](http://teaching.csse.uwa.edu.au/units/CITS7209/partition.pdf), section "Set Partitions" might also help. – Armali Jan 28 '20 at 14:03
  • The last one should be `{{a}, {b}, {c, d}}`? – btilly Jan 28 '20 at 18:43
  • Thank you everyone for all the feedback on the question especially to @GuyCoder who gave a lot of helpful advice. I have edited the question, hope this makes it more clear that I'm looking for. – Matthew Appleby Jan 30 '20 at 09:42

3 Answers3

1

Here is a Python solution using iterators so that it does not consume a lot of memory.

def sorted_box_partitions (boxes, things):
    if boxes == 0 and len(things) == 0:
        yield [set(things)]
    elif boxes < 1:
        yield None
    elif boxes == 1:
        yield [set(things)]
    elif len(things) == 0:
        yield [set() for _ in range(boxes)]
    else:
        sorted_things = sorted(things)
        min_thing = sorted_things[0]
        rest_things = sorted_things[1:]

        # First do all partitions with min_thing and others.
        for partition in sorted_box_partitions(boxes, rest_things):
            partition[0].add(min_thing)
            yield partition

        # Now do all partitions with min_thing by itself.
        for partition in sorted_box_partitions(boxes - 1, rest_things):
            yield [set([min_thing])] + partition

for p in sorted_box_partitions(4, ['a', 'b', 'c', 'd']):
    print(p)
btilly
  • 43,296
  • 3
  • 59
  • 88
  • [{'b', 'a', 'd', 'c'}, set(), set(), set()] [{'b', 'a', 'd', 'c'}, set(), set(), set()] [{'b', 'a', 'c'}, {'d'}, set(), set()] [{'b', 'a', 'c'}, {'d'}, set(), set()] [{'b', 'a'}, {'d', 'c'}, set(), set()] [{'b', 'a'}, {'d', 'c'}, set(), set()] [{'b', 'a'}, {'c'}, {'d'}, set()] [{'b', 'a'}, {'c'}, {'d'}, set()] [{'a'}, {'b', 'd', 'c'}, set(), set()] [{'a'}, {'b', 'd', 'c'}, set(), set()] [{'a'}, {'b', 'c'}, {'d'}, set()] [{'a'}, {'b', 'c'}, {'d'}, set()] [{'a'}, {'b'}, {'d', 'c'}, set()] [{'a'}, {'b'}, {'d', 'c'}, set()] [{'a'}, {'b'}, {'c'}, {'d'}] – Matthew Appleby Jan 30 '20 at 09:56
  • Thanks for trying this problem out, I gave it a run through python and got this output above. Is this the same result you were expecting or has my python down something odd? – Matthew Appleby Jan 30 '20 at 09:57
  • @MatthewAppleby This is expected. It gives an array of sets. That is just how your Python is displaying the sets. If you want a different representation, just call `list` on a `set` to get an array. See https://docs.python.org/3/tutorial/datastructures.html#sets for more. – btilly Jan 30 '20 at 19:40
1

Using Prolog, specifically SWI-Prolog

list_taken_rest([], [], []).
list_taken_rest([X|Xs], [X|Ys], Zs) :-
   list_taken_rest(Xs, Ys, Zs).
list_taken_rest([X|Xs], Ys, [X|Zs]) :-
   list_taken_rest(Xs, Ys, Zs).

list_partitioned([], []).
list_partitioned([X|Xs], [[X|Ys]|Pss]) :-
   list_taken_rest(Xs, Ys, Zs),
   list_partitioned(Zs, Pss).

Code from: All partitions of a list in Prolog

Example generating desired result.

?- list_partitioned([a,b,c,d], [A]).
A = [a, b, c, d] ;
false.

?- list_partitioned([a,b,c,d], [A,B]).
A = [a, b, c], B = [d] ;
A = [a, b, d], B = [c] ;
A = [a, b],    B = [c, d] ;
A = [a, c, d], B = [b] ;
A = [a, c],    B = [b, d] ;
A = [a, d],    B = [b, c] ;
A = [a],       B = [b, c, d] ;
false.

?- list_partitioned([a,b,c,d], [A,B,C]).
A = [a, b], B = [c],    C = [d] ;
A = [a, c], B = [b],    C = [d] ;
A = [a, d], B = [b],    C = [c] ;
A = [a],    B = [b, c], C = [d] ;
A = [a],    B = [b, d], C = [c] ;
A = [a],    B = [b],    C = [c, d] ;
false.
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • Thanks for this, I can see what you have done here. It's interesting that prolog calls this partitioning because in maths partitioning is either breaking a number into integers or about segmenting a list whilst maintaining the same order. The problem with code is that it doesn't allow for empty boxes and this problem was probably down to my failure to ask the question properly. I have made some changes to the question so hopefully it will make more sense now. Thanks!! – Matthew Appleby Jan 30 '20 at 09:50
  • @MatthewAppleby Some people think of votes as currency around here, and that is usually the way I think of it. So when someone does something that is of value you pay the back with the currency of an up-vote. However I don't think of an accept vote as currency but as stating to others that find this question that it has an accepted answer. – Guy Coder Jan 30 '20 at 11:06
  • @MatthewAppleby IMHO once an answer is given to a question as written, then you should not change the question because you learned something and realized your question had a mistake. You should ask a new question even if slightly different linking back to the preceding question and noting the change. Those who answered your question took the time to answer it and don't like trying to herd cats. Some other users see no problem with changing their question after an answer has been given and I no longer look at or answer their questions. – Guy Coder Jan 30 '20 at 11:14
0

Spent the whole day on it and at last, I have a solution. Thanks for all the help everyone, and if you're a fan of large blocks of c++ code, have fun!!

#include <iostream>
#include <vector>

bool CheckEnd(int base, int digits, std::vector<int> number) {
    int j = 0;
    for (int i = 0; i < digits; ++i) {
        if (i >= base) {
            j = base - 1;
        } else {
            j = i;
        }
        if (number[i] < j) {
            return false;
        }
    }
    return true;
}

bool CheckValid(std::vector<int> number) {
    int max = 0;
    for (int value : number) {
        if (value > max + 1) {
            return false;
        }
        if (value > max) {
            max = value;
        }
    }
    return true;
}

std::vector<std::vector<int>> BaseCounter(int base, int digits) {
    int start = 0;
    std::vector<int> number(digits, start);
    int *start_point = &(number[digits - 1]);
    int *point = start_point;
    std::vector<std::vector<int>> flipped_list;

    bool loop = true;
    while (loop) {
        if (CheckEnd(base, digits, number)) {
            loop = false;
        }
        if (CheckValid(number)) {
            flipped_list.push_back(number);
        }
        point = start_point;
        ++ *point;
        while ((*point == base)) {
            *point = start;
            -- point;
            ++ *point;
        }
    }
    return flipped_list;
}

std::vector<std::vector<std::vector<char>>> FillBoxes(
    std::vector<char> unsorted,
    int box_count) {

    int index = 0;
    bool loop = true;
    std::vector<std::vector<int>> flipped_list =
        BaseCounter(box_count, unsorted.size());
    std::vector<std::vector<std::vector<char>>> sorted;
    for (int i = 0; i < flipped_list.size(); ++i) {
        std::vector<std::vector<char>> boxes(box_count);
        for (int passenger_index = 0;
            passenger_index < unsorted.size();
            ++ passenger_index) {
            index = flipped_list[i][passenger_index];
            boxes[index].push_back(unsorted[passenger_index]);
        }
        sorted.push_back(boxes);
    }
    return sorted;
}


int main()
{
    std::vector<char> unsorted = { 'a', 'b', 'c', 'd' };
    int box_count = 3;

    std::vector<std::vector<std::vector<char>>> sorted;
    sorted = FillBoxes(unsorted, box_count);



    std::cout << "{ \n";
    for (std::vector<std::vector<char>> v1 : sorted) {
        std::cout << "{ ";
        for (std::vector<char> v2 : v1) {
            std::cout << "{ ";
            for (char v3 : v2) {
                std::cout << v3 << " ";
            }
            std::cout << "} ";
        }
        std::cout << "}\n";
    }
    std::cout << "}";
}

This is a very large way of solving it and I'm sure there are people who could find far more neat methods so if you do find a more elegant algorithm, please do share.
Thanks again!