如何生成列表元素的所有可能组合?
例如,给定列表[1,2,3]
,我想设计一个形如comb([1,2,3], L)
的谓词,它应该为L
返回以下答案:
[1]
[2]
[3]
[1,2]
[2,1]
[1,3]
[3,1]
[2,3]
[3,2]
[1,2,3]
[1,3,2]
[2,1,3]
[2,3,1]
[3,1,2]
[3,2,1]
如何生成列表元素的所有可能组合?
例如,给定列表[1,2,3]
,我想设计一个形如comb([1,2,3], L)
的谓词,它应该为L
返回以下答案:
[1]
[2]
[3]
[1,2]
[2,1]
[1,3]
[3,1]
[2,3]
[3,2]
[1,2,3]
[1,3,2]
[2,1,3]
[2,3,1]
[3,1,2]
[3,2,1]
comb(InList,Out) :-
splitSet(InList,_,SubList),
SubList = [_|_], /* disallow empty list */
permute(SubList,Out).
splitSet([ ],[ ],[ ]).
splitSet([H|T],[H|L],R) :-
splitSet(T,L,R).
splitSet([H|T],L,[H|R]) :-
splitSet(T,L,R).
permute([ ],[ ]) :- !.
permute(L,[X|R]) :-
omit(X,L,M),
permute(M,R).
omit(H,[H|T],T).
omit(X,[H|L],[H|R]) :-
omit(X,L,R).
使用Amzi! Prolog进行测试:
?- comb([1,2,3],L).
L = [3] ;
L = [2] ;
L = [2, 3] ;
L = [3, 2] ;
L = [1] ;
L = [1, 3] ;
L = [3, 1] ;
L = [1, 2] ;
L = [2, 1] ;
L = [1, 2, 3] ;
L = [1, 3, 2] ;
L = [2, 1, 3] ;
L = [2, 3, 1] ;
L = [3, 1, 2] ;
L = [3, 2, 1] ;
no
!
是干什么用的? - falsepermute/2
的第一个子句中有一个剪枝,这是为了提高效率(也称为“绿色剪枝”)。 - hardmathpermute(Xs, Ys), Xs = [_]
的意思是将一个只包含一个元素的列表Xs进行排列组合,结果为列表Ys。 - falsepermute/2
不支持未实例化的第一个参数。问题是如何产生“列表元素的所有可能组合”...“给定列表”,因此我的实现旨在为该模式提高效率。请参阅 SWI-Prolog 符号说明 获取模式指示器。 - hardmath通过基于 same_length/2
, prefix/2
, foldl/4
和 select/3
定义comb/2
,以保证其纯净性:
comb(As,Bs) :- same_length(As,Full), Bs = [_|_], prefix(Bs,Full), foldl(select,Bs,As,_).
这是由OP给出的示例查询:
?- comb([1,2,3],Xs).
Xs = [1]
; Xs = [2]
; Xs = [3]
; Xs = [1,2]
; Xs = [1,3]
; Xs = [2,1]
; Xs = [2,3]
; Xs = [3,1]
; Xs = [3,2]
; Xs = [1,2,3]
; Xs = [1,3,2]
; Xs = [2,1,3]
; Xs = [2,3,1]
; Xs = [3,1,2]
; Xs = [3,2,1]
; false.
好的!但是如果作为第一个参数给出的列表包含重复项怎么办?
?- comb([1,1,2],Xs).
Xs = [1]
; Xs = [1] % (redundant)
; Xs = [2]
; Xs = [1,1]
; Xs = [1,2]
; Xs = [1,1] % (redundant)
; Xs = [1,2] % (redundant)
; Xs = [2,1]
; Xs = [2,1] % (redundant)
; Xs = [1,1,2]
; Xs = [1,2,1]
; Xs = [1,1,2] % (redundant)
; Xs = [1,2,1] % (redundant)
; Xs = [2,1,1]
; Xs = [2,1,1] % (redundant)
; false.
不完全正确!我们能够去除以上冗余的答案吗?可以,只需使用selectd/3
!
comb(As,Bs) :- same_length(As,Full), Bs = [_|_], prefix(Bs,Full), foldl(selectd,Bs,As,_).
因此,让我们再次运行上面的查询,并使用改进后的comb/2
实现!
?- comb([1,1,2],Xs).
Xs = [1]
; Xs = [2]
; Xs = [1,1]
; Xs = [1,2]
; Xs = [2,1]
; Xs = [1,1,2]
; Xs = [1,2,1]
; Xs = [2,1,1]
; false.
library(purple)
: 纯Prolog库(基本)? - matif_/3
的依赖性不是立即可见的,但我链接到了selectd/3
的定义...这样可以吗? - repeat有一个预定义的谓词叫作permutation,它可以用于检查两个列表是否是排列关系。
1 ?- permutation([1,2,3],L).
L = [1, 2, 3] ;
L = [2, 1, 3] ;
L = [2, 3, 1] ;
L = [1, 3, 2] ;
L = [3, 1, 2] ;
L = [3, 2, 1] .
2 ?- listing(permutation).
lists:permutation([], [], []).
lists:permutation([C|A], D, [_|B]) :-
permutation(A, E, B),
select(C, D, E).
lists:permutation(A, B) :-
permutation(A, B, B).
true.
permutation/3
不是尾递归的,并且交换两个目标通常会大幅增加运行时间。它也很优雅,看起来很好,只涉及纯代码。请注意,OP要求略有不同:问题是子序列的排列,不一定包括整个列表。看看@repeat的简短、优雅和纯净的解决方案! - matinselt(X,Y,Z)
,它表示将Y
插入到X
中得到Z
,那么这很容易实现。inselt([E|X], Y, [E|Z]) :- inselt(X,Y,Z).
inselt(X, Y, [Y|X]).
然后,使用inselt/3
可以递归编写comb/3
。