All Permutations - non-recursive

Updated spacerat 1 Tallied Votes 662 Views Share

The other code snipplets I found were either recursive or too complex.
I therefore developed a simple, fast and yet non-recursive method;
thats useful especially when working on the graphics card with CUDA as recursion is not possible there.


(c) Sven Forstmann

std::string default = "12345";

int perm=1, digits=default.size();
for (int i=1;i<=digits;perm*=i++);
for (int a=0;a<perm;a++)
{
	std::string avail=default;

	for (int b=digits,div=perm;b>0; b--) 
	{
		div/=b;
		int index = (a/div)%b;
		printf("%c", avail[index] );
		avail[index]=avail[b-1];
	}
	printf("\n");
}
printf("permutations:%d\n",perm);

/*******************************************/
/* And now the slower but lexigraphic correct version: */
/*******************************************/

std::string default = "12345";

int perm=1, digits=default.size();
for (int i=1;i<=digits;perm*=i++);
for (int a=0;a<perm;a++)
{
	std::string avail=default;

	for (int b=digits,div=perm;b>0; b--) 
	{
		div/=b;
		int index = (a/div)%b;
		printf("%c", avail[index] );
		avail.erase(index,1) ;
	}
	printf("\n");
}
printf("permutations:%d\n",perm);

Dani AI

Generated

The technique shown by is the factorial-number (factoradic) mapping: an index is decomposed into mixed-radix digits that tell you which remaining element to pick at each position. That is why you can jump straight to permutation number a without generating earlier permutations. The tradeoff is cost per query — naive removal with erase is O(n) so the full generation is O(n^2) per permutation.

Practical improvements: use a clear name (avoid default), accept the input as std::vector<T> (as suggested by ), precompute factorials once and range-check the requested index (or throw). Beware factorial overflow: 64-bit indexes cover up to 20! — beyond that you need big integers (Boost.Multiprecision, etc.). Also heed : prefer well-tested components for production; this technique is useful when you must index permutations directly, not to wholesale-replace std::next_permutation.

A faster selection step avoids repeated O(n) removals. Use a Fenwick (binary indexed) tree to represent which slots are still available and find the k-th free slot in O(log n), yielding O(n log n) total per permutation. Example (concise):

#include <vector>
#include <cstdint>
#include <limits>
#include <stdexcept>

template<typename Int = int>
struct Fenwick {
    int n; std::vector<Int> bit;
    Fenwick(int n_=0): n(n_), bit(n_+1,0) {}
    void add(int idx, Int val){ for(++idx; idx<=n; idx += idx & -idx) bit[idx] += val; }
    int find_kth(Int k){ // 0-based k-th one
        int idx = 0, mask = 1;
        while ((mask << 1) <= n) mask <<= 1;
        for (; mask; mask >>= 1){
            int nxt = idx + mask;
            if (nxt <= n && bit[nxt] <= k){ k -= bit[nxt]; idx = nxt; }
        }
        return idx; // 0-based position
    }
};

template<typename T>
std::vector<T> permutation_by_index(const std::vector<T>& items, uint64_t index){
    size_t n = items.size();
    std::vector<uint64_t> fact(n+1,1);
    for(size_t i=1;i<=n;++i){
        if (fact[i-1] > std::numeric_limits<uint64_t>::max() / i)
            throw std::overflow_error("factorial overflow");
        fact[i] = fact[i-1]*i;
    }
    if (index >= fact[n]) throw std::out_of_range("index");
    Fenwick<> fw((int)n);
    for(int i=0;i<(int)n;++i) fw.add(i,1);
    std::vector<T> out; out.reserve(n);
    uint64_t rem = index;
    for(size_t pos=n; pos>0; --pos){
        uint64_t f = fact[pos-1];
        int digit = (int)(rem / f);
        rem %= f;
        int ix = fw.find_kth(digit);
        out.push_back(items[ix]);
        fw.add(ix, -1);
    }
    return out;
}

For CUDA (answering ): do the index->permutation work per thread, precompute factorials in constant memory, avoid std::vector on device, and use plain arrays. For small n a simple linear scan for the k-th unused element is easiest on GPU; for larger n implement a per-thread BIT or do host-side generation. If lexicographic order is not required, the O(1) swap-remove method (replace removed slot with last element) is the fastest on massively parallel hardware.

CppBuilder2006 -5 Junior Poster

thanks for sharing your code.
but default is a keyword!
you used it as a variable name!
I hardly can imagine a permutation program without recursion (but it is possible).
I have a recursive solution!

spacerat 0 Newbie Poster

>>default is a keyword!
well, the compiler fortunately didnt complain :-)

anyway, the advantage of this implementation over the next_permutation in STL is, that you can instantly get the permutation at any index.

In STL, you need to start at a certain permutation to figure out the next one.

mrnutty 761 Senior Poster

>>well, the compiler fortunately didnt complain :-)

No, in fact thats unfortunate.

>>anyway, the advantage of this implementation over the next_permutation in STL is, that you can instantly get the permutation at any index.

The disadvantages is that this is not a safe code. Its a homemade code,
and is likely to fail at some point with some wrong input. Its not as
efficient as the stl...and much more to list.

Generally, one does this for practice, NOT to replace stl's algorithm.

spacerat 0 Newbie Poster

for the code you just need to write a function with range-checks to return you your desired permutation.

The algorithm used is safe. it computes the order in which you have to remove and print the items of the "avail" array.

It is based on the following property.
Lets say you have 6 characters in the string that you want to permute.
Then you get 6! = 720.
In the first column of your results, you get 720=6*120 lines of each character. This means if your index counts from 0 to 719, then the first character to be removed is computed as remove_index = index/120.
For the next column, you have 5 numbers left. This means, that 120 = 5 * 24 lines of the same character - and so on. Its not based on a random thought as you might think.

You get the following list:
column0: remove_index = (a/120)%5;
column1: remove_index = (a/24)%4;
column2: remove_index = (a/6)%3;
column3: remove_index = (a/2)%2;
column4: remove_index = (a/1)%1;

Not sure if this is published somewhere but it works.

CppBuilder2006 -5 Junior Poster

why don't you put your code completely?!
with main() function & headers! so that I can test it with VC++!

spacerat 0 Newbie Poster

Ok, here we go:

#include <string>

int main(int,char**)
{ 
	std::string default_str = "12345";

	int perm=1, digits=default_str.size();
	for (int i=1;i<=digits;perm*=i++);
	for (int a=0;a<perm;a++)
	{
		std::string avail=default_str;

		for (int b=digits,div=perm;b>0; b--) 
		{
			div/=b;
			int index = (a/div)%b;
			printf("%c", avail[index] );
			avail.erase(index,1) ;
		}
		printf("\n");
	}
	printf("permutations:%d\n",perm);
	while(1);
}
CppBuilder2006 -5 Junior Poster

yes
it works! thank you!
permutations are sometimes necessary. for example, when you want to have n nested for-loops! where n is variable!
I think your short code is faster than my long recursive one! (I should test it)
And I think you can improve your code. instead of a string use a vector!
:)

Also, your first code showed that in VC++ 'default' can be a variable name! in Borland compilers it can't! perhaps it's a bug for VC!

Good Luck

chtulu 0 Newbie Poster

Hi,

How would you implement that in Cuda?

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.