Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to avoid this for-loop mess in c++?

Tags:

c++

for-loop

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.

like image 342
solid Avatar asked Nov 23 '15 19:11

solid


2 Answers

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;
    });
}
like image 62
bames53 Avatar answered Oct 03 '22 20:10

bames53


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.

like image 43
Alexandre C. Avatar answered Oct 03 '22 20:10

Alexandre C.