如何找到排列的索引

时间:2017-04-02 14:43:19

标签: prolog permutation

% 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.

我的解决方案:

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

为什么这是错的? 我怎样才能增加Idx?

2 个答案:

答案 0 :(得分:4)

我找到了同样想法的清洁版,所以我展示了代码:

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
    ).

这个解决方案不是尾递归,因为看起来递归的深度不会成为问题?不做尾递归更容易,它需要更少的参数。它适用于任何元素,只有先决条件是元素是唯一的。没有愚蠢的不必要使用foldlnth0/4!如果你想要,你可以另外给它自己的比较函数,只需要在element_rank内进行评估,但这是过度的。但是C ++标准库有next_permutation,它允许你给它比较谓词,所以可能有用例吗?

现在我们可以看到是否真的可以在合理的时间内找到英文字母所有字母的排列索引。

?- 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.

你可以看到索引正好是26!-2所以也许它是正确的?您可以在下面找到原始答案,其中包含算法的一些解释和不充分的实现。这个实现并不好,但至少我希望它好一点吗?

你真的想列举所有可能的排列吗?在许多情况下,这可能太多了?如果您对英文字母中的所有字母进行排列,例如您已经有26个字母! =一个非常大的数字(403291461126605635584000000)。

所以也许最好只计算而不计算?另外我不认为库permutation/2有这个选项,但你应该能够按字典计算“下一个排列”,而不需要枚举所有的排列。因为当你说“排列索引”时,这假设所有可能的排列都按某种顺序排列,但你没有说明这是什么顺序。也许这是字典顺序?和@CapelliC在另一个答案中的库permutation/2有一个恼人的“特征”,它不关心它是否真的是一个排列:

?- 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.

这对我来说看起来不正确。如果你问你的程序,“排列的索引是什么[1,1,1],它应该回答”它是1,2,3,4,和5,以及6?“我很不舒服这个答案。

在开始询问“permutaton的索引是什么”之前,首先需要问“如何排列排列?” (按字典顺序??)你还需要确保列表中的所有元素实际上都是唯一的,并且它们也有订单。我假设如果你有长度 n 的列表,那么在这个列表中你有1和 n 之间的所有整数,就像在你的例子中一样!如果你有其他元素(如字母),你必须确保它们是唯一的并且可以订购,然后你可以在1和 n 之间分配它们,但我认为这是微不足道的所以我不喜欢我想为它编写代码。但它看起来像这样:

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

你明白为什么吗?如果没有,我可以解释为什么这很重要。

然后,如果你有一个像[4,1,3,2]这样的列表及其长度,那么你可以使用以下算法:

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

这已知道排列的长度和列表,因为我们使用list_indices_len/4。那么现在我们只需要进行n-1步骤,每次我们将剩余数字列表中数字的基于0的索引与剩余数字的阶乘相乘。

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).

必须有更好的方法来计算阶乘,但这有效吗?

所以现在我按预期得到了:

?- 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.

最后一个正好是预期的10!-2。

如果您需要更多解释,我可以做到,但如果您能理解逻辑,它看起来很容易理解。或许我对逻辑错了?它似乎有效。

我自己做了测试,看到我对我的方法的复杂性并不感到困惑,所以我再次测试更大的数字,看起来是正确的。

?- 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.

还有更有效的方法来计算排列索引,但也许你应该首先阅读......你可以从头开始:

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

答案 1 :(得分:1)

置换/ 2在回溯时生成元素。跟踪解决方案的索引并不容易,因此这是解决您问题的更简单方法:

?- 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.

修改

更高效的东西,可以使用这个

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

以这种方式:

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

索引现在它是一个计数器,所以它偏移了1