How to find the index of the permutation

只谈情不闲聊 提交于 2021-01-28 00:00:52

问题


% index(+List, -Idx) Predicate will get List with permutation and I want to
know index of permutation

For example: ?- index([4,1,3,2],X).
                X= 19.

My solution:

index([],0).
index([_],1).
index([X,Y],2):- Y > X.
index([H,X|T],Idx):-index([X|T],Idx+1),H > X.

Why is it wrong? And how can I make incremention of Idx?


回答1:


I found cleaner version of same idea so I show the code:

permutation_index([X|Xs], I) :-
    prerequisite(
        (   sort([X|Xs], S),
            length([X|Xs], Len),
            length(S, Len)
        )),
    permutation_index(Xs, X, _N, _N_fac, I).

prerequisite(P) :- P.

permutation_index([], _Last, 0, 1, 0).
permutation_index([X|Xs], Prev, N, N_fac, I) :-
    permutation_index(Xs, X, N0, N_fac0, I0),
    succ(N0, N),
    N_fac is N*N_fac0,
    element_rank([X|Xs], Prev, R),
    I is I0 + R*N_fac.

element_rank([], _, 0).
element_rank([X|Xs], Y, R) :-
    element_rank(Xs, Y, R0),
    (   X @< Y
    ->  succ(R0, R)
    ;   R0 = R
    ).

This solution is not tail recursion because it seems that depth of recursion will not be a problem? It is easier to not do tail recursion, it needs less arguments. It works with any elements, only prerequisite is that elements are unique. No stupid unnecessary use of foldl or nth0/4! If you want you can additionally give it your own comparison function that only needs to be evaluated inside element_rank but this is overkill. However C++ standard library has next_permutation which lets you give it comparison predicate so maybe there is use case for this?

Now we can see if really it is possible to find the index of permutation of all letters of the English alphabet in reasonable time.

?- bagof(C, ( char_type(C, lower), C @> b, C @=< z ), Cs),
   reverse(Cs, Cs_rev),
   append(Cs_rev, [a,b], Letters),
   time( permutation_index(Letters, I) ).
% 1,103 inferences, 0.000 CPU in 0.000 seconds (98% CPU, 4226847 Lips)
Cs = [c, d, e, f, g, h, i, j, k|...],
Cs_rev = [z, y, x, w, v, u, t, s, r|...],
Letters = [z, y, x, w, v, u, t, s, r|...],
I = 403291461126605635583999998.

You can see that index is exactly 26!-2 so maybe it is correct? Below you can find original answer which has some explanation of algorithm and inadequate implementation. This implementation is not good but at least it is a bit better I hope?


You really want to enumerate all possible permutations? This is maybe too many in many of the cases? If you have a permutation of all the letters in the English alphabet for example you already have 26! = a very big number (403291461126605635584000000).

So maybe it is better to just calculate without enumerating? Also I don't think the library permutation/2 has this option but you should be able to calcuate just "next permutation" lexicographically, without enumerating all permutations. Because when you say "index of permutation" this assumes that all possible permutations are in some order, but you don't say what order this is. Maybe it is lexicographical order? And the library permutation/2 as in the other answer by @CapelliC has one annoying "feature" that it does not care if it is really a permutation or not:

?- permutation([1,1,1], P).
P = [1, 1, 1] ;
P = [1, 1, 1] ;
P = [1, 1, 1] ;
P = [1, 1, 1] ;
P = [1, 1, 1] ;
P = [1, 1, 1] ;
false.

This does not look correct to me at all. If you ask your program, "what is the index of permutation [1,1,1], should it answer "it is 1, and 2, and 3, and 4, and 5, and 6?" I am very uncomfortable with this answer.

Before you begin asking "what is the index of permutaton" first you need to ask "how are permutations ordered?" (lexicographically??) and you also need to make sure that all elements of your list are actually unique, and that they have an order, too. I will assume that if you have list of length n then in this list you have all integers between 1 and n, as in your example!!! If you have other elements (like letters) than you must make sure that they are unique and can be ordered and then you can assign them numbers between 1 and n but I think this is trivial so I don't want to write code for it. But it can look like this:

?- list_indices_len([c,b,a,x], Ns, Is, Len).
Ns = [3, 2, 1, 4],
Is = [1, 2, 3, 4],
Len = 4.

Do you see why? If not I can explain why this is important.

Then once you have a list like [4,1,3,2] and its length then you can use following algorithm:

permutation_index(P, Es, Len, I) :-
    succ(Len0, Len),
    P = [N|Ns],
    permutation_index(Ns, N, Len0, Es, 0, I).

This already knows the length of the permutation and the list because we made it with list_indices_len/4. Then now we only have to do n-1 steps and each time we multiply the 0-based index of the number in the list of remaining numbers with the factorial of the number of remaining numbers.

permutation_index([], _, _, _, I, I).
permutation_index([N|Ns], N0, X, Es, Acc, I) :-
    once( nth0(N_i, Es, N0, Es0) ),
    factorial_expr(X, X_fac),
    succ(X0, X),
    Acc1 is N_i*X_fac + Acc,
    permutation_index(Ns, N, X0, Es0, Acc1, I).

factorial_expr(F, E) :-
    (   F =:= 0
    ->  E = 1
    ;   F =:= 1
    ->  E = 1
    ;   F > 1
    ->  X is F,
        numlist(2, X, [N|Ns]),
        foldl(prod, Ns, N, E)
    ).

prod(X, Y, Y*X).

There must be better way to calculate factorial but this works?

So now I get as expected:

?- permutation_index([4,1,3,2], [1,2,3,4], 4, I).
I = 19.

?- permutation_index([4,3,2,1], [1,2,3,4], 4, I).
I = 23.

?- permutation_index([1,2,3,4], [1,2,3,4], 4, I).
I = 0.

?- permutation_index([1,2,4,3], [1,2,3,4], 4, I).
I = 1.

?- permutation_index([10,9,8,7,6,5,4,3,1,2], [1,2,3,4,5,6,7,8,9,10], 10, I).
I = 3628798.

The last one is exactly 10!-2 as expected.

If you need more explaining I can do it but it looks quite easy to understand if you can understand the logic. Or maybe I am wrong about the logic? It seems to work however.

I made test myself to see that I am not confused about the complexity of my approach so I test again with even bigger number and it looks correct.

?- time(permutation_index([12,11,10,9,8,7,6,5,4,3,1,2], [1,2,3,4,5,6,7,8,9,10,11,12], 12, I)).
% 466 inferences, 0.000 CPU in 0.000 seconds (99% CPU, 1498045 Lips)
I = 479001598.

?- factorial_expr(12, E), X is E - 2.
E = ... * ... * 4*5*6*7*8*9*10*11*12,
X = 479001598.

There are also even more efficient ways to calculate index of permutation but maybe you should read first... you can start at the beginning:

https://en.wikipedia.org/wiki/Permutation#Permutations_in_computing




回答2:


permutation/2 generates elements on backtracking. It's not really easy to track the index of a solution, so this is the easier way for your immediate problem:

?- findall(P,permutation([1,2,3,4],P),L), nth0(I,L,[4,1,3,2]).                                                                                                                                             L = [[1, 2, 3, 4], [1, 2, 4, 3], [1, 3, 2, 4], [1, 3, 4, 2], [1, 4, 2, 3], [1, 4, 3|...], [2, 1|...], [2|...], [...|...]|...],
I = 19 ;
false.

edit

something more efficient, could use this

nthsol(Goal, N) :-
    State = state(0, _),
    Goal,
    arg(1, State, C),
    N is C+1,
    nb_setarg(1, State, N).

in this way:

?- nthsol(permutation([1,2,3,4],P),I),P=[4,1,3,2].
P = [4, 1, 3, 2],
I = 20 ;
false.

the index now it's a counter, so it's offset by 1



来源:https://stackoverflow.com/questions/43169633/how-to-find-the-index-of-the-permutation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!