繁体   English   中英

在Prolog中对列表进行分区

[英]Partitioning a List in Prolog

我试图创建一个Prolog谓词,在给定一个列表的情况下,可以查看该列表是否可以拆分为两个总和相同的列表。

我有一个工作列表总和谓词,所以我在分区谓词中使用它。 我首先尝试对谓词进行编码,以查看列表的第一个元素是否等于列表其余部分的总和([2,1,1])。 这就是我要面对的情况。

partitionable([X|Y]) :-
   sum([X],SUM),
   sum([Y],SUM2),
   SUM = SUM2.

但是,我收到此错误消息:

ERROR: is/2: Arithmetic: `[]/0' is not a function. 

我想在深入研究递归列表的其余部分之前,先使这部分工作,尽管我对此消息的含义感到困惑,因为我没有编写'[]/0' function 任何帮助表示赞赏。

要将列表划分为两个不重叠的子序列 ,我们使用list_subseq_subseq/3

list_subseq_subseq([]    ,[]    ,[]).
list_subseq_subseq([X|Xs],[X|Ys],Zs) :-
   list_subseq_subseq(Xs,Ys,Zs).
list_subseq_subseq([X|Xs],Ys,[X|Zs]) :-
   list_subseq_subseq(Xs,Ys,Zs).

为了执行整数运算,我们使用

:- use_module(library(clpfd)).

让我们放在一起! 在以下示例查询中,我们对列表[1,2,3,4,5,6,7]分区:

?- Xs = [1,2,3,4,5,6,7],
   sum(Xs,#=,Total),
   Half*2 #= Total,
   list_subseq_subseq(Xs,Ys,Zs),
   sum(Ys,#=,Half),
   sum(Zs,#=,Half).
  Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [1,2,4,7], Zs = [3,5,6]
; Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [1,2,5,6], Zs = [3,4,7]
; Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [1,3,4,6], Zs = [2,5,7]
; Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [1,6,7]  , Zs = [2,3,4,5]
; Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [2,3,4,5], Zs = [1,6,7]
; Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [2,5,7]  , Zs = [1,3,4,6]
; Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [3,4,7]  , Zs = [1,2,5,6]
; Xs = [1,2,3,4,5,6,7], Total = 28, Half = 14, Ys = [3,5,6]  , Zs = [1,2,4,7]
; false.

我还将为分区问题提供另一种解决方案。 helper谓词有助于削减列表两个列表。 例如,可以将[1,2,3]删减:

[1,2](左侧)和[3](右侧)或

[3](左侧)和[1,2](右侧)。

helper([],[],[],0,0).  
helper([X|XS],[X|L],R,SUML,SUMR):-helper(XS,L,R,SUMN,SUMR),SUML is SUMN+X. 
helper([X|XS],L,[X|R],SUML,SUMR):-helper(XS,L,R,SUML,SUMN),SUMR is SUMN+X.
partition(S,L,R):-helper(S,L,R,X,X).

输出为:

1 ?- partition([1,2,3,4],L,R).
L = [1, 4],
R = [2, 3] ;
L = [2, 3],
R = [1, 4] ;
false.

不断变得更好!

对于不确定的分区列表,如果采用正确的元谓词/谓词组合,则无需实现像list_subseq_subseq/3这样的递归辅助谓词!

在这个答案中,我们使用tpartition/4作为并使用“通用通配符”谓词(*)/2作为传递给tpartition/4的谓词。 (*)/2可以这样定义:

_ * true.
_ * false.

让我们将tpartition/4(*)/2约束(#=)/2sum/3

?- use_module(library(clpfd)).   % use clp(FD) library
true.

?- ABs = [1,2,3,4,5,6,7],        % same data as in the earlier answer
   sum(ABs,#=,NN),
   N*2 #= NN,
   tpartition(*,ABs,As,Bs),
   sum(As,#=,N),
   sum(Bs,#=,N).
  NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [1,2,4,7], Bs = [3,5,6] 
; NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [1,2,5,6], Bs = [3,4,7]
; NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [1,3,4,6], Bs = [2,5,7]
; NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [1,6,7]  , Bs = [2,3,4,5]
; NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [2,3,4,5], Bs = [1,6,7]    
; NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [2,5,7]  , Bs = [1,3,4,6]    
; NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [3,4,7]  , Bs = [1,2,5,6]
; NN = 28, N = 14, ABs = [1,2,3,4,5,6,7], As = [3,5,6]  , Bs = [1,2,4,7]
; false.

我认为必须将X传递为[X],因为X只是元素(示例中的数字2)。 另一方面,Y本身就是一个列表,不应放在另一个列表中。 这是修改后的版本:

partitionable([X|Y]) :- sum([X],SUM), sum(Y,SUM2), SUM=SUM2.
sum([X|Y],SUM) :- sum(Y, SUBSUM), SUM is SUBSUM + X.
sum([X],SUM) :- X=SUM.

在我的情况下, partitionable([2,1,1])返回true。

编辑 :由于您不使用is/2这可能是sum谓词中的错误。

另外请注意:据我所知,您不需要可partitionable的解决方案,而是收到的错误消息。 尽管如此,这是我对实现它的看法( 可能是前面的破坏者 ):

/* partitionable(X)
 * If a 2-partition of X exists where both sublists have the same sum, then X
 * is considered partitionable.
 */
partitionable(X) :- partition(X, A, B), sum(A, SUM), sum(B, SUM2), SUM =:= SUM2, !.

/* partition(X, A, B)
 * X is split in two lists A and B and will, during backtracking, bind A and B to
 * ALL permutations of the list partition, including permutations for each list.
 */
partition([], [], []).
partition([X|Y], A, B) :- partition(Y, R, B), extract(X, A, R).
partition([X|Y], A, B) :- partition(Y, A, R), extract(X, B, R).

/* extract(X, L, R)
 * Extracts exactly one element X from L and unify the result with R.
 */
extract(X, [H|T], R) :- X = H, R = T.
extract(X, [H|T], R) :- R = [H|R2], extract(X, T, R2).

sum([X|Y],SUM) :- sum(Y, SUBSUM), SUM is SUBSUM + X.
sum([X],SUM) :- X = SUM.

也许我在想这个,但是...

如果用“对列表进行分区”的意思是“将列表切成两部分,保留顺序,而不是创建列表的各种排列),那么解决方案似乎应该不会像下面这样复杂:

partitionable( Ns ) :-
  append( Prefix , Suffix , Ns ) ,
  compute_sum(Prefix,Sum) ,
  compute_sum(Suffix,Sum) .

compute_sum( Ns , S ) :- compute_sum(Ns,0,S) .

compute_sum( []     , S , S ) .
compute_sum( [N|Ns] , T , S ) :- T1 is N+T , compute_sum(Ns,T1,S) .

如果您想避免使用内置函数,则可以执行以下操作,它在增加优雅的同时又最大程度地减少了列表遍历:

partitionable( List ) :-
  sum_prefix( List , Sum , Sfx ) ,
  sum_prefix( Sfx  , Sum , []  ) .

sum_prefix( List , Sum , Suffix ) :- sum_prefix(List,0,Sum,Suffix) .

sum_prefix( Suffix   , Sum , Sum , Suffix ) .
sum_prefix( [H|List] , Acc , Sum , Suffix ) :-
  Acc1 is Acc+H ,
  sum_prefix(List,Acc1,Sum,Suffix)
  .

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM