Произвольное количество вложенных циклов?

Я хочу взять произвольное количество списков (например, [2, 1, 4 ....], [8, 3, ...], ...) и выбрать номера из каждого списка, чтобы генерировать все перестановки. Например:

[2, 8, ...], [2, 3, ...], [1, 8, ...], [1, 3, ...], [4, 8, ...], [4, 3, ...], ...

Это легко сделать, используя вложенные циклы for, но, поскольку я хотел бы принять произвольное количество списков, кажется, что циклы for должны быть жестко закодированы. Один для каждого списка. Кроме того, поскольку моя программа, вероятно, будет генерировать многие десятки тысяч перестановок, я хотел бы генерировать одну перестановку за раз (вместо того, чтобы вычислять их все за один раз и сохранять результат в векторе). Есть ли способ сделать это программно?

Поскольку количество списков известно во время компиляции, я подумал, что, возможно, я мог бы использовать метапрограммирование на основе шаблонов. Однако это кажется неуклюжим и также не соответствует требованию «по одному за раз». Есть какие-нибудь предложения?

11
задан Nick L 21 August 2010 в 07:49
поделиться

5 ответов

Рекурсивный способ ...

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
ответ дан 3 December 2019 в 08:28
поделиться

Забавно.

То, что вы, кажется, хотите, на самом деле является своего рода итератором, который будет перебирать заданные диапазоны и на каждом шаге выдает вам перестановку.

Обычно его можно написать без метапрограммирования, тем более что вариативные шаблоны поддерживаются только начиная с 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 в качестве параметра для указания индекса контейнера.

2
ответ дан 3 December 2019 в 08:28
поделиться

Итак, после дальнейшего исследования выяснилось, что то, что я пытаюсь сделать, называется декартовым произведением. В итоге я использовал слегка измененную версию этого кода. Просто подумал, что обновлю это, если кто-то еще наткнется на этот вопрос в поисках того же ответа.

1
ответ дан 3 December 2019 в 08:28
поделиться

Используя рекурсию, вы, вероятно, могли бы «накормить себя» текущей позицией, оставшимися списками и так далее. Это может привести к переполнению, но часто рекурсивная функция может быть превращена в нерекурсивную (например, с циклом for), хотя большая часть элегантности исчезает.

0
ответ дан 3 December 2019 в 08:28
поделиться

В STL не было готовой функции для этого, но вы можете написать свою собственную реализацию, изменив некоторые части next_permutation .

Задача аналогична реализации сумматора двоичных разрядов. Увеличить массив [0] .Если новое значение array [0] переполняется (это означает, что его значение больше, чем количество имеющихся у вас списков), установите array [0] на ноль и увеличьте массив [1] . И так далее.

2
ответ дан 3 December 2019 в 08:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: