Я хочу взять произвольное количество списков (например, [2, 1, 4 ....], [8, 3, ...], ...) и выбрать номера из каждого списка, чтобы генерировать все перестановки. Например:
[2, 8, ...], [2, 3, ...], [1, 8, ...], [1, 3, ...], [4, 8, ...], [4, 3, ...], ...
Это легко сделать, используя вложенные циклы for, но, поскольку я хотел бы принять произвольное количество списков, кажется, что циклы for должны быть жестко закодированы. Один для каждого списка. Кроме того, поскольку моя программа, вероятно, будет генерировать многие десятки тысяч перестановок, я хотел бы генерировать одну перестановку за раз (вместо того, чтобы вычислять их все за один раз и сохранять результат в векторе). Есть ли способ сделать это программно?
Поскольку количество списков известно во время компиляции, я подумал, что, возможно, я мог бы использовать метапрограммирование на основе шаблонов. Однако это кажется неуклюжим и также не соответствует требованию «по одному за раз». Есть какие-нибудь предложения?
Рекурсивный способ ...
void Recurse(const vector<vector<int>>& v,
size_t listToTwiddle,
vector<int>& currentPermutation)
{
// terminate recursion
if (listToTwiddle == v.size())
{
for(auto i = currentPermutation.cbegin(); i != currentPermutation.cend(); ++i)
{
cout << *i << " ";
}
cout << endl;
return;
}
for(size_t i = 0; i < v[listToTwiddle].size(); ++i)
{
// pick a number from the current list
currentPermutation.push_back(v[listToTwiddle][i]);
// get all permutations having fixed this number
Recurse(v, listToTwiddle + 1, currentPermutation);
// restore back to original state
currentPermutation.pop_back();
}
}
void Do(const vector<vector<int>>& v)
{
vector<int> permutation;
Recurse(v, 0, permutation);
}
Забавно.
То, что вы, кажется, хотите, на самом деле является своего рода итератором, который будет перебирать заданные диапазоны и на каждом шаге выдает вам перестановку.
Обычно его можно написать без метапрограммирования, тем более что вариативные шаблоны поддерживаются только начиная с C ++ 0x. Тем не менее, я чувствую, что это очень интересный вызов.
Нашим первым помощником здесь будет небольшой класс tuple
. Нам также понадобится ряд уловок программирования мета-шаблонов для преобразования одного кортежа в другой, но я позволю читателю в качестве упражнения написать как необходимые функции мета-шаблона, так и фактические функции для выполнения преобразования. (читайте: сегодня днем слишком жарко, чтобы до него добраться).
Вот что вам поможет.
template <class... Containers>
class permutation_iterator
{
public:
// tuple of values
typedef typename extract_value<Containers...>::type value_type;
// tuple of references, might be const references
typedef typename extract_reference<Containers...>::type reference;
// tuple of pointers, might be const pointers
typedef typename extract_pointer<Containers...>::type pointer;
permutation_iterator(Containers&... containers) { /*extract begin and end*/ }
permutation_iterator& operator++()
{
this->increment<sizeof...(Containers)-1>();
return *this;
}
private:
typedef typename extract_iterator<Containers...>::type iterator_tuple;
template <size_t N>
typename std::enable_if_c<N < sizeof...(Containers) && N > 0>::type
increment()
{
assert(mCurrent.at<N>() != mEnd.at<N>());
++mCurrent.at<N>();
if (mCurrent.at<N>() == mEnd.at<N>())
{
mCurrent.at<N>() = mBegin.at<N>();
this->increment<N-1>();
}
}
template <size_t N>
typename std::enable_if_c<N == 0>::type increment()
{
assert(mCurrent.at<0>() != mEnd.at<0>());
++mCurrent.at<0>();
}
iterator_tuple mBegin;
iterator_tuple mEnd;
iterator_tuple mCurrent;
};
Если вы не знаете, как перейти на мета, проще перейти к рекурсии, а затем потребовать от пользователя указать, к какому контейнеру он хочет получить доступ, с помощью метода at
, принимающего N
в качестве параметра для указания индекса контейнера.
Итак, после дальнейшего исследования выяснилось, что то, что я пытаюсь сделать, называется декартовым произведением. В итоге я использовал слегка измененную версию этого кода. Просто подумал, что обновлю это, если кто-то еще наткнется на этот вопрос в поисках того же ответа.
Используя рекурсию, вы, вероятно, могли бы «накормить себя» текущей позицией, оставшимися списками и так далее. Это может привести к переполнению, но часто рекурсивная функция может быть превращена в нерекурсивную (например, с циклом for), хотя большая часть элегантности исчезает.
В STL не было готовой функции для этого, но вы можете написать свою собственную реализацию, изменив некоторые части next_permutation
.
Задача аналогична реализации сумматора двоичных разрядов. Увеличить массив [0]
.Если новое значение array [0]
переполняется (это означает, что его значение больше, чем количество имеющихся у вас списков), установите array [0]
на ноль и увеличьте массив [1]
. И так далее.