tags:

views:

645

answers:

4

std::next_permutation (and std::prev_permutation) permute all values in the range [first, last) given for a total of n! permutations (assuming that all elements are unique).

is it possible to write a function like this:

template<class Iter>
bool next_permutation(Iter first, Iter last, Iter choice_last);

That permutes the elements in the range [first, last) but only chooses elements in the range [first, choice_last). ie we have maybe 20 elements and want to iterate through all permutations of 10 choices of them, 20 P 10 options vs 20 P 20.

  • Iter is a random access iterator for my purposes, but if it can be implemented as a bidirectional iterator, then great!
  • The less amount of external memory needed the better, but for my purposes it doesn't matter.
  • The chosen elements on each iteration are input to the first elements of the sequence.

Is such a function possible to implement? Does anyone know of any existing implementations?

Here is essentially what I am doing to hack around this. Suggestions on how to improve this are also welcome.

  • Start with a vector V of N elements of which I want to visit each permutation of R elements chosen from it (R <= N).
  • Build a vector I of length R with values { 0, 1, 2, ... R - 1 } to serve as an index to the elements of V
  • On each iteration, build a vector C of length R with values { V[I[0]], V[I[1]], ... V[I[R - 1]] }
  • Do something with the values in C.
  • Apply a function to permute the elements of I and iterate again if it was able to.

That function looks like this:

bool NextPermutationIndices(std::vector<int> &I, int N)
{
    const int R = I.size();
    for (int i = R - 1; ; --i) {
        if (I[i] < N - R + i) {
            ++I[i];
            return true;
        }

        if (i == 0)
            return false;

        if (I[i] > I[i-1] + 1) {
            ++I[i-1];
            for (int j = i; j < R; ++j)
                I[j] = I[j-1] + 1;
            return true;
        }
    }
}

That function is very complicated due to all the possible off-by-one errors, as well everything using it are more complicated than is probably necessary.


EDIT:

It turns out that it was significantly easier than I had even imagined. From here, I was able to find exact implementations of many of the exact algorithms I needed (combinations, permutations, etc.).

template<class BidirectionalIterator>
bool next_partial_permutation(BidirectionalIterator first,
                              BidirectionalIterator middle,
                              BidirectionalIterator last)
{
    std::reverse(middle, last);
    return std::next_permutation(first, last);
}

Plus there is a combination algorithm there that works in a similar way. The implementation of that is much more complication though.

+1  A: 

Source code for a Java combination generator is at http://www.merriampark.com/comb.htm. Strip out the Java idioms, and it's almost exactly what you're looking for, implemented as a generator to keep a lid on your memory usage.


This problem is from the mathematical field known as Combinatorics, which is part of Discrete mathematics. Discrete math is crucial to practitioners of computer science, as it includes nearly all of the math we use daily (like logic, algorithms, counting, relations, graph theory, etc.). I highly recommend Discrete and Combinatorial Mathematics: An applied introduction or Discrete Mathematics and Its Applications, if you can afford it.

(Note: this question is related to "Algorithm for Grouping," but not quite a duplicate since this question asks to solve it in the general case.)

Jacob
Good link. This is essentially the approach I am using, although I could eliminate some waste by packaging some of the code into a class instead of what I am doing on a per use basis. Is there no way to do this by having it inspect and manipulate the actual data array?
Greg Rogers
+3  A: 

To iterate over nPk permutations, I've used the for_each_permutation() algorithm presented in this old CUJ article before. It uses a nice algorithm from Knuth which rotates the elements in situ, leaving them in the original order at the end. Therefore, it meets your no external memory requirement. It also works for BidirectionalIterators. It doesn't meet your requirement of looking like next_permutation(). However, I think this is a win - I don't like stateful APIs.

fizzer
Great link, although it is misleading in that the implementation really requires RandomAccessIterator's not BidirectionalIterator's for the Iter + int operation. They don't make this clear though.
Greg Rogers
My apologies - not looked at it in a while. Maybe advance() will fix it.
fizzer
A: 

An algorithmic simplification would be to split this into two separate steps.

  • Generate a list of all possible selections of R elements out of the original data.
  • For each of those selections, create all possible permutations of the selected elements.

By interleaving those operations, you can avoid allocating the intermediate lists.

Selection can be implemented on a bidirectional iterator by skipping over non-selected items. Generate all selections, e.g. by permuting a sequence of R ones and (N-R) zeroes. This will need O(N) additional memory, but enables you to permute the original sequence in place.

David Schmitt
A: 

For what its worth, here is an implementation that sort of works.

It requires that the elements above choice start in sorted order. It only works if there are no duplicate elements in the sequence (if there are, it misses some permutations, and doesn't end in the correct perumtation). It also might be missing some edge cases as I didn't really test it thoroughly as I have no plans on actually using it.

One benefit of this way over this answer's, is that way doesn't visit permutations in lexicographical order, which may (but probably not) be important. It also is kind of a pain to use boost::bind sometimes to create a functor to pass to for_each.

template<class Iter>
bool next_choice_permutation(Iter first, Iter choice, Iter last)
{
    if (first == choice)
        return false;

    Iter i = choice;
    --i;
    if (*i < *choice) {
        std::rotate(i, choice, last);
        return true;
    }

    while (i != first) {
        Iter j = i;
        ++j;
        std::rotate(i, j, last);
        --i;
        --j;
        for (; j != last; ++j) {
            if (*i < *j)
                break;
        }
        if (j != last) {
            std::iter_swap(i, j);
            return true;
        }
    }
    std::rotate(first, ++Iter(first), last);
    return false;
}
Greg Rogers