10

I need to program all possible sets of numbers from 1 to N for an arbitrary number m of integers without permutation.

Since I don't know how to explain it better here are some examples:

for m = 2

vector<vector<int>> box;    
int N = 5;

for(int i = 1; i <= N; i++) {
    for(int j = N; j >= i; j--) {
        vector<int> dummy;
        dummy.push_back(i);
        dummy.push_back(j);
        box.push_back(dummy);
    }
}

for m = 3

vector<vector<int>> box;    
int N = 5;  

for(int i = 1; i <= N; i++) {
    for(int j = N; j >= i; j--) {
        for(int k = N; k >= j; k--) {
            vector<int> dummy;
            dummy.push_back(i);
            dummy.push_back(j);
            dummy.push_back(k);
            box.push_back(dummy);
        }
    }
}

This works perfectly fine and the result is what I need. But like already mentioned, m can be arbitrary and I can't be bothered to implement this for m = 37 or what ever. N and m are known values but change while the program is running. There must be a better way to implement this than for the m = 37 case to implement a row of 37-for-loops. Can someone help me? I'm kind a clueless :\

edit: to explain better what I'm looking for here are some more examples.

Let's say N = 5 and m = 4, than 1223 is a feasible solution for me, 124 is not since it is to short. Let's say I already found 1223 as a solution, than I don't need 2123, 2213 or any other permutation of this number.

edit2: Or if you prefer a more visual (mathematical?) problem formulation here you go.

Consider m the dimension. With m been 2 you are left with a N size Matrix. I am looking for the upper (or lower) triangle of this Matrix including the diagonal. Let's move to m = 3, the Matrix becomes a 3 dimensional cube (or Tensor if you so wish), now I'm looking for the upper (or lower) tetrahedron including the diagonal-plain. For higher dimensions than 3 I'm looking for the hyper-tetrahedron of the hyper-cube including the hyper-diagonal-plane.

solid
  • 377
  • 2
  • 10

2 Answers2

3

You can use recursion to find all subsets. This can probably be improved stylistically, but here is a quick take at the problem:

std::vector<std::set<int>> subsets(std::vector<int> x)
{
    if (x.size() == 0)
        return { std::set<int>() };
    else
    {
        int last = x.back();
        x.pop_back();

        auto sets = subsets(x);
        size_t n = sets.size();

        for (size_t i = 0; i < n; i++)
        {
            std::set<int> s = sets[i];
            s.insert(last);
            sets.push_back(std::move(s));
        }

        return sets;
    }
}

This doubles the number of answers at each recursion step : the number of subsets is 2^n, as expected.

You can substitute std::set for std::vector if you wish.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • 1
    I'm not quite sure what return { std::set() }; dose, but this won't compile for me. – solid Nov 23 '15 at 20:25
  • @solid: It initialises the return vector with a single, empty set. Are you using an ancient compiler? – Lightness Races in Orbit Nov 23 '15 at 20:26
  • @solid it could be breaking on std::vector>, it should have a space between right angle brackets: std::vector > – lowtech Nov 23 '15 at 20:27
  • 1
    @solid: You can replace it with `std::vector >(1)` (mind the space if you have too old a compiler). – Alexandre C. Nov 23 '15 at 20:30
  • i'm using VisualStudio2012 and it throws this on the first return line error C2059: Syntaxfehler: '{' with and without the space – solid Nov 23 '15 at 20:30
  • @solid: You can't use the initializer list with VS2012 (C++11 support is incomplete), you must use the explicit form given in my last comment (with or without the space between the angle brackets). – Alexandre C. Nov 23 '15 at 20:39
  • OK, after getting this running on my VS2015 I have a hard time figuring out what x is. – solid Nov 23 '15 at 20:50
  • @solid: The vector `x` is supposed to contain the (distinct) elements of the set of which you want to take the subsets. In your case, you can initialize it with the numbers from `1` to `n`. Again, this can probably be simplified a lot. – Alexandre C. Nov 23 '15 at 20:51
  • But if this is just a vector of integers, and if I initialize it with let's say 1 to 3, how to determine the number m of “places”. If I want for m=3 just something like 113 or for m=5 something like 11223? – solid Nov 23 '15 at 21:04
  • @solid: You can 1/ replace `std::set` with `std::vector` if you have duplicate elements in `x`, 2/ add a check for `s.size() <= m`, and don't `push_back` sets with too many elements 3/ remove the sets with strictly less than `m` elements at the very end. I believe there is a more elegant way to do it, but it'd take more time that I have right now. – Alexandre C. Nov 23 '15 at 21:09
  • @AlexandreC. Indeed the inner lists will need to be arrays, vectors, or something like that; I also originally misunderstood the question, but OP is asking for sequences with possible duplicates. – Kyle Strand Nov 24 '15 at 19:29
3

http://howardhinnant.github.io/combinations.html

The following generic algorithms permit a client to visit every combination or permuation of a sequence of length N, r items at time.

Example usage:

std::vector<std::vector<int>> box;  

std::vector<int> v(N);
std::iota(begin(v), end(v), 1);

for_each_combination(begin(v), begin(v) + M, end(v), [](auto b, auto e) {
    box.emplace_back(b, e);
    return false;
});

The above code just shows inserting each combination into box as an example, but you probably don't want to actually do that: assuming that box is simply an intermediary and that your actual work then uses it somewhere else, you can avoid an intermediary and simply do whatever work you need directly in the body of the functor.

Here's a complete, working example using code from the provided link.


Since what you want is combinations with repetition rather than just combinations. Here's an example of implementing this on top of for_each_combination():

template<typename Func>
void for_each_combination_with_repetition(int categories, int slots, Func func) {
    std::vector<int> v(slots + categories - 1);
    std::iota(begin(v), end(v), 1);

    std::vector<int> indices;
    for_each_combination(begin(v), begin(v) + slots, end(v), [&](auto b, auto e) {
        indices.clear();
        int last = 0;
        int current_element = 0;

        for(;b != e; ++last) {
            if (*b == last+1) {
                indices.push_back(current_element);
                ++b;
            } else {
                ++current_element;
            }
        }
        func(begin(indices), end(indices));
        return false;
    });
}

The wikipedia article on combinations shows a good illustration of what this is doing: it's getting all the combinations (without repetition) of numbers [0, N + M - 1) and then looking for the 'gaps' in the results. The gaps represent transitions from repetitions of one element to repetitions of the next.

The functor you pass to this algorithm is given a range that contains indices into a collection containing the elements you're combining. For example if you want to get all sets of three elements from the set of {x,y}, the results are you want are {{x,x,x}, {x,x,y}, {x,y,y}, {y,y,y}}, and this algorithm represents this by returning ranges of indices into the (ordered) set {x,y}: {{0,0,0}, {0,0,1}, {0,1,1}, {1,1,1}}.

So normally to use this you have a vector or something containing your elements and use the ranges produced by this algorithm as indices into that container. However in your case, since the elements are just the numbers from 1 to N you can use the indices directly by adding one to each index:

for_each_combination_with_repetition(N, M, [&](auto b, auto e) {
    for(; b != e; ++b) {
        int index = *b;
        std::cout << index + 1 << " ";
    }
    std::cout << '\n';
});

Complete example


An alternative implementation can return vectors that represent counts of each category. E.g. the earlier {{x,x,x}, {x,x,y}, {x,y,y}, {y,y,y}} results could be represented by: {{3,0} {2,1},{1,2}, {0,3}}. Modifying the implementation to produce this representation instead looks like this:

template<typename Func>
void for_each_combination_with_repetition(int categories, int slots, Func func) {
    std::vector<int> v(slots + categories - 1);
    std::iota(begin(v), end(v), 1);

    std::vector<int> repetitions(categories);
    for_each_combination(begin(v), begin(v) + slots, end(v), [&](auto b, auto e) {
        std::fill(begin(repetitions), end(repetitions), 0);

        int last = 0;
        int current_element = 0;

        for(;b != e; ++last) {
            if (*b == last+1) {
                ++repetitions[current_element];
                ++b;
            } else {
                ++current_element;
            }
        }
        func(begin(repetitions), end(repetitions));
        return false;
    });
}
bames53
  • 86,085
  • 15
  • 179
  • 244
  • The output according to your link dose not give me 122 as a solution (with I need). But I don't want permutations of 122 if I already fount it, so I don't want 212 or 221. – solid Nov 23 '15 at 21:08
  • @solid Ah, I see; rather than combinations what you want is called combinations with repetition. The linked library doesn't include this functionality but the algorithm can build built on top of `for_each_combination`. – bames53 Nov 23 '15 at 21:59
  • @solid I've added an implementation of combinations with repetitions based on `for_each_combination()` – bames53 Nov 23 '15 at 22:23