【问题标题】:Translate Recursive Python List Generator to C++将递归 Python 列表生成器翻译成 C++
【发布时间】:2013-06-30 09:03:04
【问题描述】:

为了提高速度,我需要转换这段 python 代码。 r 和 n 是用户定义的整数变量。

该函数应该生成具有以下条件的所有列表:

listSum = n, length = r, 值(有替换)在 [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

我尝试使用静态变量,但它们在不正确的时间递增。我试图从主函数中更改我需要的变量(r、n 和 i)并将它们传递给我的生成器等效函数,但这个解决方案似乎不适用于 r 和 n 的不同初始值。我正在使用未安装 Boost 的系统,并且我没有安装它的系统权限。那么如何将递归 python 列表生成器转换为 C++?

当我迭代 recurse(r=3, n=4) 时,我得到:

[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]

【问题讨论】:

  • IMO,您不应该尝试将语言 A 翻译成语言 B(永远)。相反,从高层次上看这段代码在做什么,然后用目标语言实现它。尝试翻译通常会导致代码混淆。
  • Equivalent C++ to Python generator pattern 的可能副本 ...您还可以查看 Boost 的协程库,我非常不同意 @Borgleader,我认为有时追求对任何事物都有意义的习语是有意义的你想说的是,不管这个概念来自什么语言。也就是说,在这种特殊情况下,我认为您只需要一个迭代器。
  • 这不只是对itertools.combinations_with_replacement 的重新实现吗?无论如何,组合的数量实在是太大了,所以即使在 C++ 中,如果输入足够大,你的代码也会“永远”循环。
  • 实际上并没有那么大(我保持 r 和 n

标签: c++ python recursion generator translate


【解决方案1】:

首先,您可以查看this thread 以获取有关您的算法的更多信息。您会发现您生成的列表数量是 (n+r-1)C(r-1),这可能会有所帮助。翻译这段代码有多种方法,但我给你两种。

迭代方式

首先,在 C++ 中,生成器模式不是很常见。根据您想要做什么,大多数时候您更喜欢在开始时为所有这些输出实际分配内存,计算日期,然后返回完整的矩阵。其次,你不能在 C++ 中以这种方式递归,你会很快毁掉你的堆栈。因此,您需要算法的迭代版本。下面是如何做到这一点(使用迭代器,就像我们在 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;
    }
}

注意:与您的示例相比,生成是反向,因为在使用 C++ 容器时这种方式更自然(因为迭代器与容器的比较 end())。 boost 部分还用于预先计算大小并尽早抛出异常以避免内存耗尽(并保留内存以避免重新分配)。这不是强制性的,您也可以将这部分注释掉(风险自负^^)。

生成器方式

但是您可能需要一个生成器,就像您正在编写一个程序,该程序将在保存在 Peta 磁盘上的大文件中写入 Tera-octets 整数列表(好吧,谁知道?)。或者您可能希望能够在 n=100、r=80 上进行计算,跳过 2 或 3 百万个向量,然后然后从中挑选出一堆。或者你只是想避免大量的内存使用。好吧,无论如何,生成器可能会派上用场。在这里。

#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&) {}
}

注意: 再次计数成员函数可以被删除。此外,您通常避免在 C++ 中的预期执行路径上引发异常(与 python 相对)。在这里,生成器可以用来填充一些其他结构,如果你的参数选择得当,它就不会抛出。如果您尝试过多地使用它,它当然会抛出超出范围。最终,像这里一样捕获异常并使其静音是非常糟糕的设计 - 这只是一个示例,您可以用来尝试一些有趣的参数,例如 (100, 80)。如果您需要完整的向量列表,count() 函数会为您提供准确的边界:使用它。

【讨论】:

    【解决方案2】:

    回调函数通常用作迭代器:

    #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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-22
      • 2016-05-03
      • 1970-01-01
      • 2021-10-17
      • 1970-01-01
      • 2018-11-06
      相关资源
      最近更新 更多