Domanda

I have a vector of size = N where each element i can have values from 0 to possible_values[i]-1. I want to do a function that iterates me through all those values.

I was able to do that in Python using a recursive generator:

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

Example output:

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

Since C++ doesn't have the Python's yield I don't know what is the right way to implement this in C++.

Optional Question: Is there a better way to implement this in Python?

È stato utile?

Soluzione

This problem reminded me of some strange mixed-modulus arithmetic numbers.

I've put something together in Python. You should be able to reimplement this easily in C++. I sometimes used the input stream operator operator>>(...) in order to implement something like a generator in C++ (lazy evaluation is a really nice feature of Python's generators). Otherwise it'd be just an object that stores the state and let's you get the next value when you need it.

Here's some example code:

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

Here's the output:

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

Some links for further reference:


I've set up an example in 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;
    }
}

Example output:

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

Altri suggerimenti

Generators in C++ aren't trivial but still possible with a bit of black magic:

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

You can look at answers to Safe cross platform coroutines because attempts to actually emulate python "yield" (including PEP 342) will bring you to some coroutine implementation anyway.

If you want to solve your problem in C++ way, it's more common to use an object for storing state of your "non-generator" method.

Yet another:

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

Implementation at ideone.com

EDIT: A bit more concise general code with better comments and explanation (I hope ;)).

This is an iterative, not a recursive, approach for an arbitrary number of positions with arbitrary maximum possible values. The idea is as follows.

We are given maximum possible value in each position. For each position we generate an array containing of all possible values for this position. We find total number of combinations of how these values can be picked out to fill in positions ("number of permutations", equal to product of all possible values). We then iterate through all combinations, storing each current combination in an array of combinations, and updating current indices to select next combination on the next iteration. We don't need to worry about boundary checks, because we are inherently limited by number of combinations. After iterated through all combinations, we return a 2D array that holds all of them (and then print them).

Hope it might be useful (code on 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);
}

Program's output:

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

Another implementation.

PS: The printing of the values can be customized to look like the output from Python but I didn't think that was necessary to illustrate the algorithm to generate the output data.

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

The output generated from running the program:

----------
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
----------
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top