The idea is to take a sequence of sequences (of the same type) and use tuples of [iterator, current index, sequence size]
to keep track of the current state, such that the cartesian_product
object can return one sequence at a time.
General questions:
- bugs? (would hope not!)
- code quality ok? anything i've missed?
- is there a simpler way to keep track of the object state (iterators, counts, etc)
- would it make sense to make this into an iterator (it could probably be bidirectional?)
Improvement ideas:
- when the number of sequences is known at compile time, maybe it would be better for
next()
to return a tuple/static array instead of astd::vector<T>
? - use a simple aggregate type instead of a tuple to avoid all the
std::get
ugliness?
Thanks a lot!
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
template <typename T>
class cartesian_product {
public:
template <typename... Args, typename = typename std::enable_if<(sizeof...(Args) > 1)>::type>
cartesian_product(Args&&... args)
: count(1)
{
std::cout << "constructor 1\n";
static_assert(std::is_arithmetic<T>::value, "T must be an arithmetic type.");
append(args...);
}
cartesian_product(std::vector<std::vector<T>> const& sequences)
: count(1)
{
std::cout << "constructor 2\n";
tuples.reserve(sequences.size());
for(auto&& s : sequences) {
tuples.emplace_back(s.cbegin(), 0ul, s.size());
count *= s.size();
}
}
cartesian_product(std::vector<std::vector<T>>&& sequences)
: cartesian_product(sequences)
{
std::cout << "constructor 3\n";
}
bool has_next() { return count > 0; }
// next combination
std::vector<T> next()
{
std::vector<T> res(tuples.size());
auto s = res.rbegin();
for (auto p = tuples.rbegin(); p < tuples.rend(); ++p, ++s) {
*s = *std::get<0>(*p);
if (p > tuples.rbegin()) {
auto q = p - 1;
if (std::get<1>(*q) == std::get<2>(*q)) {
std::get<0>(*q) -= std::get<2>(*q);
std::get<1>(*q) = 0;
++std::get<0>(*p);
++std::get<1>(*p);
}
} else {
++std::get<0>(*p);
++std::get<1>(*p);
}
}
--count;
return res;
}
private:
using I = typename std::vector<T>::const_iterator;
using E = std::tuple<I, std::size_t, std::size_t>;
std::vector<E> tuples;
int count;
template <typename... Args>
void append(std::vector<T> const& vec, Args&&... args)
{
append(vec);
append(args...);
}
void append(std::vector<T> const& vec)
{
count *= vec.size();
tuples.emplace_back(std::cbegin(vec), 0ul, vec.size());
}
};
int main(int argc, char** argv)
{
std::vector<int> a { 1, 2 };
std::vector<int> b { 3, 4 };
std::vector<int> c { 5, 6 };
std::vector<int> d { 7, 8, 9 };
std::vector<std::vector<int>> s{ a, b, c, d };
cartesian_product<int> p(a, b, c);
cartesian_product<int> q(s);
cartesian_product<int> r(std::vector<std::vector<int>>{ a, b, c, d });
auto print = [](auto&& vec) { for (auto v : vec) std::cout << v << " "; std::cout << "\n"; };
while (p.has_next())
print(p.next());
return 0;
}