【问题标题】:Partitioning a List in Prolog在 Prolog 中对列表进行分区
【发布时间】:2015-04-01 18:54:30
【问题描述】:

我正在尝试创建一个 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。任何帮助表示赞赏。

【问题讨论】:

  • 我意识到我将 X 和 Y 作为列表传递到 sum 谓词中,而它们本应作为自己传递,所以我不再收到错误消息。但是,即使我传入 partitionable([2,1,1]),谓词仍然返回 false - 这应该返回 true。这可能是因为我的求和谓词吗?
  • edit 您的问题,而不是在 cmets 中详细说明,否则会变得相当混乱。 X 是单个元素,列表的头部[X|Y]Y 是列表的尾部(另一个列表)。所以[Y] 是一个元素的列表,它本身就是一个列表。这可能不是你想要的。您还需要明确元素的顺序是否重要。例如,它是否应该在列表中成功,[1,2,1]
  • 列表的顺序确实很重要,因此在列表中满足求和约束的任何位置只能创建一个分区(因此 [1,2,1] 不起作用)。

标签: list prolog sum subset-sum


【解决方案1】:

要将列表分成两个不重叠的subsequences, 我们使用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.

【讨论】:

  • list_subseq_subseq([a,a],Ys, Zs) 的脱节是什么意思?
  • 这本来就是不确定的。也许用于诊断目的。
  • @false。我认为“不重叠的子序列”比“不相交的子序列”更适合。当我们说“分区”时,应该清楚Xs 中的每个项目都恰好属于YsZs 之一,你不觉得吗?
【解决方案2】:

我还会为分区问题提供另一种解决方案。辅助谓词有助于减少列表两个列表。例如 [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.

【讨论】:

    【解决方案3】:

    越来越好!

    对于非确定性分区列表,如果我们使用正确的元谓词/谓词组合,我们不需要实现像 list_subseq_subseq/3 这样的递归辅助谓词!

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

    _ * true.
    _ * false.
    

    让我们将tpartition/4(*)/2 约束(#=)/2 and sum/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.
    

    【讨论】:

      【解决方案4】:

      我认为需要将 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.
      

      【讨论】:

      • 好的,我明白为什么我需要把 X 放在括号里,而把 Y 单独放在里面。但是,在更改它之后,使用除partitionable([2,1,1]). 之外的任何内容都会产生错误:ERROR: is/2: Arithmetic: `[]/0' is not a function。我不知道是什么导致了这种情况发生。
      • @red_student 你能举一些出现错误的例子吗?我了解此答案不是可分区的正确解决方案,但我没有收到您提到的错误。你使用什么版本的 prolog?
      【解决方案5】:

      也许我没有考虑到这一点,但是......

      如果通过“对列表进行分区”,您的意思是“将列表一分为二,保持顺序,而不是创建列表的各种排列),看起来解决方案不应该比这样的事情更复杂:

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

      【讨论】:

        猜你喜欢
        • 2012-01-15
        • 1970-01-01
        • 1970-01-01
        • 2017-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-15
        相关资源
        最近更新 更多