c++中的递归生成器

Recursive generator in C++

本文关键字:递归 c++      更新时间:2023-10-16

我有一个大小为N的向量,其中每个元素I可以具有从0到possible_values[I]-1的值。我想用一个函数来遍历所有这些值

我可以在Python中使用递归生成器来完成:

def all_values(size,values,pos=0):
    if pos == size:
        yield []
    else:    
        for v in xrange(values[pos]):
            for v2 in all_values(size,values,pos+1):
                v2.insert(0,v)
                yield v2
possible_values=[3,2,2]
for v in all_values(3,possible_values):
    print v

示例输出:

[0, 0, 0]
[0, 0, 1]
[0, 1, 0]
[0, 1, 1]
[1, 0, 0]
[1, 0, 1]
[1, 1, 0]
[1, 1, 1]
[2, 0, 0]
[2, 0, 1]
[2, 1, 0]
[2, 1, 1]

由于c++没有Python的yield,我不知道在c++中实现它的正确方法是什么。

可选的问题:有没有更好的方法在Python中实现这个?

这个问题让我想起了一些奇怪的混合模数

我已经在Python中组合了一些东西。您应该能够在c++中轻松地重新实现它。我有时会使用输入流运算符operator>>(...),以便在c++中实现类似生成器的东西(惰性求值是Python生成器的一个非常好的特性)。否则,它就只是一个存储状态的对象,让你在需要的时候获得下一个值。

下面是一些示例代码:

class Digit:
    def __init__(self, modulus):
        self.modulus = modulus
        self.value = 0
    def __str__(self):
        return str(self.value)
    def __nonzero__(self):
        return bool(self.value)
    def increment(self):
        self.value += 1
        self.value %= self.modulus
        return self.value == 0
class Number:
    def __init__(self, moduli):
        self.digits = [Digit(m) for m in moduli]
    def __str__(self):
        return "".join(str(d) for d in self.digits)
    def __nonzero__(self):
        return any(d for d in self.digits)
    def increment(self):
        carryover = True
        for d in reversed(self.digits):
            if carryover:
                carryover = d.increment()
n = Number([3,2,2])
while True:
    print n
    n.increment()
    if not n:
        break

输出如下:

000
001
010
011
100
101
110
111
200
201
210
211

一些链接供进一步参考:

  • 操作符重载
  • 自定义类流

我在c++中设置了一个例子:

#include <sstream>
#include <string>
#include <iostream>
#include <vector>
struct number {
    struct digit {
        int value;
        int modulus;
        digit(int modulus) : value(0), modulus(modulus) {}
        bool increment() {
            value = (value+1)%modulus;
            return !value;
        }
        operator void*() {
            return value ? this : 0;
        }
        std::string to_str() {
            return std::to_string(value);
        }
    };
    std::vector<digit> digits;
    number(std::vector<int> const & moduli) {
        for (auto i : moduli)
            digits.push_back(digit(i));
    }
    void increment() {
        bool carry = true;
        for (auto d = digits.rbegin(); d != digits.rend(); d++)
            if (carry)
                carry = d->increment();
    }
    operator void*() {
        for (digit & d : digits)
            if (d) return this;
        return 0;
    }
    std::string to_str() {
        std::stringstream o;
        for (auto & d : digits)
            o << d.to_str();
        return o.str();
    }
};
int main() {
    number n({3,2,2});
    for(;;) { 
        std::cout << n.to_str() << 'n';
        n.increment();
        if (!n) break;
    }
}

示例输出:

$ g++ test.cc -std=c++11 && ./a.out
000
001
010
011
100
101
110
111
200
201
210
211

c++中的生成器并非微不足道,但仍然可以使用一些黑魔法:

http://www.codeproject.com/Articles/29524/Generators-in-C

你可以看看安全的跨平台协程的答案,因为尝试实际模拟python的"yield"(包括PEP 342)无论如何都会给你带来一些协程实现。

如果你想用c++的方式解决你的问题,更常见的是使用一个对象来存储你的"非生成器"方法的状态

又一个:

#include <vector>
#include <iostream>
typedef std::vector<unsigned int> uint_vector;
typedef std::vector<uint_vector> values_vector;
values_vector all_values (const uint_vector & ranges, unsigned int pos=0) {
   values_vector result;
   if (pos == ranges.size()) {
      result.push_back (uint_vector());
   }   
   else {
      values_vector rem_result = all_values (ranges, pos+1);
      for (unsigned int v = 0; v < ranges[pos]; ++v) {
         for (auto it : rem_result) {
            result.push_back (uint_vector(1,v));
            result.back().insert (result.back().end(), it.begin(), it.end());
         }
      }      
   }      
   return result;
}      
void print_values (const values_vector & combos) {
   for (auto combo : combos) {
      std::cout << "[ "; 
      for (auto num : combo) {
         std::cout << num << ' ';
      }      
      std::cout << "]n";
   }      
}      
int main () {
   uint_vector ranges {3,2,2};
   print_values (all_values (ranges));
   return 0;
}      

ideone.com的实现

EDIT:更简洁的通用代码,有更好的注释和解释(我希望;))。

这是一个迭代,而不是一个递归,对于任意数目的具有任意最大值的位置。思路如下:

给出了每个位置的最大可能值。对于每个位置,我们生成一个包含该位置所有可能值的数组。我们找到了这些值如何被挑选出来填充位置的组合总数("排列数",等于所有可能值的乘积)。然后我们遍历所有组合,将每个当前组合存储在组合数组中,并更新当前索引以在下一次迭代中选择下一个组合。我们不需要担心边界检查,因为我们天生就受到组合数量的限制。遍历所有组合后,返回一个包含所有组合的2D数组(然后打印它们)。

希望它可能是有用的(代码在ideone.com):

#include <vector>
#include <iostream>
#include <algorithm>
namespace so {
using size = std::size_t;
using array_1d = std::vector<size>;
using array_2d = std::vector<array_1d>;
array_2d generate_combinations_all(array_1d const & _values_max) {
 array_2d values_all_; // arrays of all possible values for each position
 size count_combination_{1}; // number of possible combinations
 for (auto i_ : _values_max) { // generate & fill in 'values_all_'
  array_1d values_current_(i_);
  size value_current_{0};
  std::generate(values_current_.begin(), values_current_.end(), [&] {return (value_current_++);});
  values_all_.push_back(std::move(values_current_));
  count_combination_ *= i_;
 }
 array_2d combinations_all_; // array of arrays of all possible combinations
 array_1d indices_(_values_max.size(), 0); // array of current indices
 for (size i_{0}; i_ < count_combination_; ++i_) {
  array_1d combinantion_current_; // current combination
  for (size j_{0}; j_ < indices_.size(); ++j_) // fill in current combination
   combinantion_current_.push_back(values_all_[j_][indices_[j_]]);
  combinations_all_.push_back(std::move(combinantion_current_)); // append to 'combinations_all_'
  for (size m_{indices_.size()}; m_-- > 0;) // update current indices
   if (indices_[m_] < _values_max[m_] - 1) { // ++index at highest position possible
    ++indices_[m_];
    break;
   }
   else indices_[m_] = 0; // reset index if it's alrady at max value
 }
 return (combinations_all_);
}
void print_combinations_all(array_2d const & _combinations_all) {
 for (auto const & i_ : _combinations_all) { // "fancy" printing
  std::cout << "[";
  for (size j_{0}; j_ < i_.size(); ++j_)
   std::cout << i_[j_] << ((j_ < i_.size() - 1) ? ", " : "]n");
 }
}
} // namespace so
int main() {
 so::array_1d values_max_a_{3, 2, 2};
 so::array_1d values_max_b_{2, 1, 3, 2};
 so::print_combinations_all(so::generate_combinations_all(values_max_a_));
 std::cout << "***************" << std::endl;
 so::print_combinations_all(so::generate_combinations_all(values_max_b_));
 return (0);
}

程序的输出:

[0, 0, 0]
[0, 0, 1]
[0, 1, 0]
[0, 1, 1]
[1, 0, 0]
[1, 0, 1]
[1, 1, 0]
[1, 1, 1]
[2, 0, 0]
[2, 0, 1]
[2, 1, 0]
[2, 1, 1]
***************
[0, 0, 0, 0]
[0, 0, 0, 1]
[0, 0, 1, 0]
[0, 0, 1, 1]
[0, 0, 2, 0]
[0, 0, 2, 1]
[1, 0, 0, 0]
[1, 0, 0, 1]
[1, 0, 1, 0]
[1, 0, 1, 1]
[1, 0, 2, 0]
[1, 0, 2, 1]

另一个实现。

PS:可以自定义值的打印,使其看起来像Python的输出,但我认为没有必要说明生成输出数据的算法。

#include <iostream>
#include <vector>
using namespace std;
void print_values(vector<vector<int> > const& values)
{
   for ( auto v1 : values)
   {
      for ( auto v : v1 )
      {
         cout << v << " ";
      }
      cout << "n";
   }
}
vector<vector<int> > get_all_values(int size,
                                    vector<int>::const_iterator iter)
{
   vector<vector<int> > ret;
   if ( size == 1 )
   {
      for (int v = 0; v != *iter; ++v )
      {
         std::vector<int> a = {v};
         ret.push_back(a);
      }
      return ret;
   }
   vector<vector<int> > prev = get_all_values(size-1, iter+1);
   for (int v = 0; v != *iter; ++v )
   {
      for ( vector<int>& v1 : prev )
      {
         std::vector<int> a = {v};
         a.insert(a.end(), v1.begin(), v1.end());
         ret.push_back(a);
      }
   }
   return ret;
}
vector<vector<int> > get_all_values(vector<int> const& in)
{
   return get_all_values(in.size(), in.begin());
}
int main()
{
   vector<int> a{2};
   vector<int> b{2,3};
   vector<int> c{2,3,2};
   cout << "----------n";
   print_values(get_all_values(a));
   cout << "----------n";
   print_values(get_all_values(b));
   cout << "----------n";
   print_values(get_all_values(c));
   cout << "----------n";
   return 0;
}

运行程序产生的输出:

----------
0
1
----------
0 0
0 1
0 2
1 0
1 1
1 2
----------
0 0 0
0 0 1
0 1 0 
0 1 1
0 2 0
0 2 1
1 0 0
1 0 1
1 1 0
1 1 1
1 2 0
1 2 1
----------