Произвольное число вложенных циклов?
Я хочу взять произвольное количество списков (например, [2, 1, 4...], [8, 3,...],...) и выбирать номера из каждого списка, чтобы сгенерировать все перестановки. Например:.
[2, 8,...],
[2, 3,...],
[1, 8,...],
[1, 3,...],
[4, 8,...],
[4, 3,...], ...
Это легко выполнить с помощью вложенных for-loops, но поскольку я хотел бы, чтобы он принял произвольное количество списков, кажется, что for-loops должны быть жестко закодированы. Один для каждого списка. Кроме того, поскольку моя программа, скорее всего, сгенерирует множество десятков тысяч перестановок, я бы хотел создать единую перестановку за раз (вместо того, чтобы вычислять их все за один раз и сохранять результат в вектор). Есть ли способ сделать это программно?
Поскольку количество списков известно во время компиляции, я подумал, может быть, я мог бы использовать мета-программирование на основе шаблонов. Однако это кажется неуклюжим, а также не соответствует требованиям "по одному". Любые предложения?
Ответы
Ответ 1
Вы можете использовать фундаментальный принцип подсчета, например, увеличивать последнюю цифру до достижения ее максимального значения, а затем увеличивать второй последний и т.д., как и обратный отсчет
Вот пример кода, предполагая, что может быть длина разности списков различий.
#include <iostream>
using namespace std;
int main() {
int n;
cin>>n;
int a[n], len[n],i,j;
for(i = 0 ; i < n ; i++)
{
cin>>len[i];
a[i]=0;
}
while(1)
{
for(i = 0 ; i< n;i++)
cout<<a[i]<<" ";
cout<<endl;
for(j = n-1 ; j>=0 ; j--)
{
if(++a[j]<=len[j])
break;
else
a[j]=0;
}
if(j<0)
break;
}
return 0;
}
Попробуйте запустить код с помощью 4 1 1 1 1
, и он даст все 4-значные перестановки 0 и 1.
0 0 0 0
0 0 0 1
0 0 1 0
0 0 1 1
0 1 0 0
0 1 0 1
0 1 1 0
0 1 1 1
1 0 0 0
1 0 0 1
1 0 1 0
1 0 1 1
1 1 0 0
1 1 0 1
1 1 1 0
1 1 1 1
Вы можете использовать 2d массивы для получения комбинаций nos.
Ответ 2
Рекурсивный способ...
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);
}
Ответ 3
У STL не было готовой функции для этого, но вы можете написать свою собственную реализацию, изменив некоторые части next_permutation
.
Проблема аналогична реализации двоичного разрядника. Приращение array[0]
. Если новое значение array[0]
переполняется (что означает, что его значение больше, чем количество списков, которое у вас есть), установите array[0]
на ноль и увеличивайте array[1]
. И так далее.
Ответ 4
Забавное.
То, что вам кажется желательным, на самом деле является своего рода итератором, который будет перебирать по заданным диапазонам и на каждом шаге дает вам перестановку.
Его можно, как правило, писать без метапрограммирования, тем более, что вариационные шаблоны поддерживаются только с С++ 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
as, чтобы указать контейнерный индекс.
Ответ 5
Итак, после дальнейших исследований выясняется, что то, что я пытаюсь сделать, называется декартовым произведением. В итоге я использовал слегка измененную версию этого кода. Просто подумал, что обновляю это, если кто-то еще наткнется на этот вопрос, ища тот же ответ.
Ответ 6
Используя рекурсию, вы, вероятно, можете "прокормить себя" текущей позицией, оставшимися списками и т.д. Это может привести к переполнению, но часто рекурсивная функция может быть превращена в нерекурсивную (например, с циклом for), хотя большая часть элегантности исчезает.
Ответ 7
Нерекурсивный способ:
#include <vector>
#include <iostream>
// class to loop over space
// no recursion is used
template <class T>
class NLoop{
public:
// typedefs for readability
typedef std::vector<T> Dimension;
typedef std::vector< Dimension > Space;
typedef std::vector< typename Dimension::iterator > Point;
// the loop over space and the function-pointer to call at each point
static void loop(Space space, void (*pCall)(const Point&))
{
// get first point in N-dimensional space
Point current;
for ( typename Space::iterator dims_it = space.begin() ; dims_it!=space.end() ; ++dims_it )
{
current.push_back( (*dims_it).begin() );
}
bool run = true;
while ( run )
{
// call the function pointer for current point
(*pCall)(current);
// go to next point in space
typename Space::iterator dims_it = space.begin();
typename Point::iterator cur_it = current.begin();
for ( ; dims_it!=space.end() ; ++dims_it, ++cur_it )
{
// check if next in dimension is at the end
if ( ++(*cur_it) == (*dims_it).end() )
{
// check if we have finished whole space
if ( dims_it == space.end() - 1 )
{
// stop running now
run = false;
break;
}
// reset this dimension to begin
// and go to next dimension
*cur_it = (*dims_it).begin();
}
else
{
// next point is okay
break;
}
}
}
}
};
// make typedef for readability
// this will be a loop with only int-values in space
typedef NLoop<int> INloop;
// function to be called for each point in space
// do here what you got to do
void call(const INloop::Point &c)
{
for ( INloop::Point::const_iterator it = c.begin() ; it!=c.end() ; ++it)
{
std::cout << *(*it) << " ";
}
std::cout << std::endl;
}
int main()
{
// fill dimension
INloop::Dimension d;
d.push_back(1);
d.push_back(2);
d.push_back(3);
// fill space with dimensions
INloop::Space s;
s.push_back(d);
s.push_back(d);
s.push_back(d);
// loop over filled 'space' and call 'call'
INloop::loop(s,call);
return 0;
}