2

I need to convert this piece of python code for speed purposes. r and n are user defined integer variables.

The function is supposed to generate all lists with the following criteria:

listSum = n, length = r, values (with replacement) are in [0,1,2,...,n]

def recurse(r,n):
    if r == 1:
        yield [n]
        return
    for i in range(n+1):
        for j in recurse(r-1,n-i):
            yield [i]+j

I tried using static variables but they are incrementing at the incorrect times. I tried to change the variables I need (r, n, and i) from a main function and pass them to my generator equivalent function but this solution doesn't seem like it will work with different initial values for r and n. I am working on a system that does not have Boost installed and I don't have the system permission to install it. So how do I convert a recursive python list generator to C++?

When I iterate recurse(r=3, n=4), I get:

[0, 0, 4]
[0, 1, 3]
[0, 2, 2]
[0, 3, 1]
[0, 4, 0]
[1, 0, 3]
[1, 1, 2]
[1, 2, 1]
[1, 3, 0]
[2, 0, 2]
[2, 1, 1]
[2, 2, 0]
[3, 0, 1]
[3, 1, 0]
[4, 0, 0]
Eric
  • 95,302
  • 53
  • 242
  • 374
KevinShaffer
  • 760
  • 5
  • 14
  • 26
  • IMO you shouldn't be trying to translate from language A to language B (ever). Instead look at a high level what the piece of code is doing and then implement that in the target language. Attempting to translate will often result in obfuscated code. – Borgleader Jul 02 '13 at 20:23
  • possible duplicate of [Equivalent C++ to Python generator pattern](http://stackoverflow.com/questions/9059187/equivalent-c-to-python-generator-pattern) ... you could also check out Boost's coroutine library, I respectfully disagree with @Borgleader, I think there are times when it makes sense to pursue idioms that make sense for whatever you're trying to say, regardless of what language the concept hails from. That said, in this particular case, I think you just want an iterator. – crowder Jul 02 '13 at 20:25
  • 1
    Isn't that just a reimplementation of `itertools.combinations_with_replacement`? Anyway, the number of combinations is simply too big, so even in C++, with big enough inputs, your code would loop "forever". – Bakuriu Jul 02 '13 at 20:31
  • It's actually not that large ( I keep r and n < 10). And it is a slight reimplementation with a twist. I originally used itertools, but in the spirit of cutting down stuff that I was throwing away, I wrote this. – KevinShaffer Jul 02 '13 at 20:33

2 Answers2

2

First, you can check this thread for more information on your algorithm. You will discover than the number of lists you generate is (n+r-1)C(r-1), this may help. There is multiple ways to translate this code, but I'll give you two.

The iterative way

First, in C++, the generator pattern is not very common. Depending on what you want to do, most of the time you prefer to actually allocate the memory for all this output at inception, compute the date, then return the full matrix. Second, you can not recurse this way in C++, you will ruin your stack very quickly. So you need an iterative version of your algorithm. Here is how to do it (with iterators, as we like them in C++).

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <boost/math/special_functions/binomial.hpp>
#include <boost/numeric/conversion/cast.hpp>

using namespace std;

vector<vector<size_t>> gen_matrix(unsigned int n, unsigned int r)
{
    vector<vector<size_t>> res;
    if(r < 1) return res;
    // reserve memory space
    // this will throw positive_overflow if size is too big to be represented as size_t
    // this can also throw out_of_memory if this is size_t-representable but memory is too small.
    double result_size = boost::math::binomial_coefficient<double>(n + r - 1, r - 1);
    res.reserve(boost::numeric_cast<size_t>(result_size));
    vector<size_t> current(r, 0);
    current.front() = n;
    res.push_back(current);
    vector<size_t>::iterator inc = next(current.begin()); // what we increment
    while(inc != current.end())
    {
        while(current.front() != 0)
        {
            (*inc)++;
            current.front()--;
            res.push_back(current);
            while(prev(inc) != current.begin())
                inc--;
        }
        swap(current.front(), *inc++);
    }
    return move(res);
}

int main()
{
    auto r = gen_matrix(6, 4);
    for(auto v : r)
    {
        copy(v.begin(), v.end(), ostream_iterator<int>(cout, ", "));
        cout << endl;
    }
}

Note : The generation is made in reverse compared to your example, because this way is much more natural when using C++ containers (because of the iterator comparison with container end()). Also the boost part is used to pre-compute size and throw an exception early-on to avoid memory depletion (and reserve memory to avoid reallocations). This is not mandatory, you can as well comment this part out (at your own risks ^^).

The generator way

But you may need a generator, like if you're coding a program that will write Tera-octets of integer lists in big files saved on Peta-disks (well, who knows ?). Or you may want to be able to compute on n=100, r=80, skip 2 or 3 millions of vectors and then pick a bunch of them. Or you just want to avoid heavy memory usage. Well, anyway, a generator might come in handy; here it is.

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <boost/math/special_functions/binomial.hpp>
#include <boost/numeric/conversion/cast.hpp>


struct sum_list_generator
{
    typedef vector<unsigned int> result_type;

    sum_list_generator(unsigned int n, unsigned int r):
        current(r, 0),
        inc(current.begin())
    {
        if(inc != current.end()) *inc++ = n;
    }

    result_type operator()()
    {
        if(inc == current.end())
            throw out_of_range("end of iteration");
        result_type res = current;
        if(current.front() == 0)
            swap(current.front(), *inc++);
        if(inc != current.end())
        {
            (*inc)++;
            current.front()--;
            if(current.front() != 0)
                while(prev(inc) != current.begin())
                    inc--;
        }
        return move(res);
    }

    // helper function : number of outputed vectors
    static size_t count(unsigned int n, unsigned int r)
    {
        return boost::numeric_cast<size_t>(
            boost::math::binomial_coefficient<double>(n + r - 1, r - 1)
            );
    }

    private:
        result_type current;
        result_type::iterator inc;
};

int main()
{
    sum_list_generator g(6, 4);
    try
    {
        while(true)
        {
            auto v = g();
            copy(v.begin(), v.end(), ostream_iterator<int>(cout, ", "));
            cout << endl;
        }
    }
    catch(out_of_range const&) {}
}

Note: Again the count member function can be erased. Also, you usually avoid to throw an exception on an expected execution path in C++ (as opposed to python). Here the generator can be used to fill some other structure and, if your parameters are well-chosen, it will not throw. If you try to use it too much, of course it throws an out-of-range. Ultimately, catching the exception and silencing it like here in the main is very bad design - this is just an example you can use to try some fun parameters like (100, 80). The count() function gives you exact boundaries if you need a full list of vectors : use it.

Community
  • 1
  • 1
lip
  • 670
  • 3
  • 9
0

Callback functions often work well as iterators:

#include <functional>
#include <iostream>

// manipulates the memory at `dest`, and calls `doNext()` each time it contains a new set of data
void recurseHelper(int r, int n, std::function<void()> doNext, int* dest) {    
    if(r == 1) {
        *dest = n;
        doNext();
    }
    else for(int i = 0; i <= n; i++) {
        *dest = i; 
        recurseHelper(r-1, n-i, doNext, dest + 1);
    }
}

void recurse(int r, int n, std::function<void(int*)> f) {
    int dest[r];
    recurseHelper(r, n, [&] () {
        f(dest);
    }, dest);
}

int main() {
    recurse(3, 4, [] (int* i) {
        std::cout << i[0] << i[1] << i[2] << std::endl;
    });
    return 0;
}
Eric
  • 95,302
  • 53
  • 242
  • 374