繁体   English   中英

Prolog-如何按每个列表中数字的总和对包含数字的列表列表进行排序

[英]Prolog - How to sort a list of lists containing numbers by the sum of the numbers in each list

假设我有一个看起来像这样的列表:

S1 = [[3,6],[1,3],[4,12],[10]]

我希望排序列表为:

S = [[1,3],[3,6],[10],[4,12]](4 <9 <10 <16)

更新:我想我想出了一个使用predsort / 3和sumlist / 2的解决方案。

mycompare(Comp, [A|C], [B|D]):-
    sumlist(A, X), sumlist(B, Y), 
    (   X < Y -> Comp = '<'
    ;   X > Y -> Comp = '>'
    ;   compare(Comp, [A|C], [B|D])).    

predsort(mycompare, S1, S).

为什么要使用缓慢且不可携带的predsort/3 改用keysort/2双赢!

每个与 ISO兼容的Prolog系统都提供keysort/2不同于predsort/3msort/2

使用SICStus Prolog 4.3.2,我们定义zss_sumsorted/2像这样:

:- use_module(library(lists), [maplist/3,sumlist/2,keys_and_values/3]).
:- use_module(library(types), [must_be/4]).

zss_sumsorted(L0, L) :-
   must_be(L0, list(list(integer)), zss_sumsorted(L0,L), 1),
   maplist(sumlist, L0, L1),
   keys_and_values(L10, L1, L0),
   keysort(L10, L10_sorted),
   keys_and_values(L10_sorted, _, L).

OP给出的示例查询:

| ?- zss_sumsorted([[3,6],[1,3], [4,12],[10]], Xss).
Xss = [[1,3],[3,6],[10],[4,12]] ? ;
no

编辑: @tas建议使用非地面实例化的更多测试用例,这应该引发正确的异常—在must_be/4帮助下。

| ?- zss_sumsorted([[3,6],[1,3],[4,12],[10],[_]], Xss).
! Instantiation error in argument 1 of user:zss_sumsorted/2
! goal:  zss_sumsorted([[3,6],[1,3],[4,12],[10],[_229]],_179)

| ?- zss_sumsorted([[3,6],[1,3],[4,12],[10],x], Xss).
! Type error in argument 1 of user:zss_sumsorted/2
! expected list of an integer, but found x
! goal:  zss_sumsorted([[3,6],[1,3],[4,12],[10],x],_237)

另一个版本,基于与@lurker建议的相同思想,但没有maplist / 3的优雅之处:

:- use_module(library(clpfd)).
:- use_module(library(pairs)).

lists_sorted(L,S) :-
    lists_withsums(L,SL),
    keysort(SL,SSL),             % instead of msort/2
    pairs_values(SSL,S).

lists_withsums([],[]).
lists_withsums([List|Ls], [Sum-List|SLs]) :-
    sum(List,#=,Sum),
    lists_withsums(Ls,SLs).

主要区别是使用了keysort / 2而不是msort / 2,因为后者也将实际列表纳入排序,而keysort / 2保留了总和相等的列表出现的顺序。 使用msort / 2的示例查询:

   ?- lists_sorted([[3,6],[3,1],[1,3],[4,12],[10],[2,2],[1,3]], L).
L = [[1,3],[1,3],[2,2],[3,1],[3,6],[10],[4,12]]

与keysort / 2相同的查询:

   ?- lists_sorted([[3,6],[3,1],[1,3],[4,12],[10],[2,2],[1,3]], L).
L = [[3,1],[1,3],[2,2],[1,3],[3,6],[10],[4,12]]

原始代码有几个问题。 (1) AB实际上是你原来的列表子列表的头,让你真正想sumlist[A|C][B|D]而不是AB 您的逻辑( ; )逻辑将意味着只有在X = Y为true时才调用compare/3 (3)使用比较器来比较列表本身,这将失败。

mycompare(Comp, [A|C], [B|D]):-
    sumlist(A, X), sumlist(B, Y),      % ERROR: A and B are the heads of the list *element*
                                       % you really want sumlist([A|C], X), etc...
    (   X < Y -> Comp = '<'            % Choosing comparator based upon sum
    ;   X > Y -> Comp = '>'            % ERROR: Your disjunction (;) will ONLY do the
                                       % following compare if `X = Y`
    ;   compare(Comp, [A|C], [B|D])).  % ERROR: comparing two lists with comparator


作为另一种选择,您可以按照我的评论中所述的方式解决问题:

  1. 将列表映射到术语SL列表,其中SL的总和
  2. 使用msort排序将通过Prolog中的“自然”顺序出现的SL术语,并将最小的S放在第一位
  3. 将产生的SL排序列表映射回仅L的列表

实现看起来像这样:

 sort_by_sum(InputList, SortedList) :- maplist(pre_sum, InputList, SumWithList), msort(SumWithList, SumWithListSorted), maplist(un_sum, SumWithListSorted, SortedList). pre_sum(L, SL) :- sumlist(L, S). un_sum(_-L, L). ?- sort_by_sum([ [3, 6], [1, 3], [4, 12], [10] ], L). L = [[1, 3], [3, 6], [10], [4, 12]]. 

之所以可行,是因为Prolog将使用术语比较(请参见文档中的@<@>等)进行排序。 如果A小于YAB小于XY 因此,将9-[3,6]10-[10]会发现9-[3,6]较小。 比较16-[4,12]10-[10]会发现16-[4,12]更大。


正如在其他几个答案中所引用的那样, keysort/2在这里代替msort/2更加规范和可移植。

您的解决方案似乎错了:可能是

mycompare(C, A, B) :-
    sumlist(A, Sa), sumlist(B, Sb),
    ( Sa < Sb -> C = < ; C = > ).
lol_sorted(L, S) :-
    predsort(mycompare, L, S).

或者,或者:

lol_sorted(L, S) :-
    setof(N-E, (member(E,L),sumlist(E,N)), R),
    pairs_values(R, S).

这将丢失重复的相同列表(而不是总和)。 要保留它们,请使用稍微复杂一点的代码段:

lol_sorted(L, S) :-
    setof(N/P-E, (nth0(P,L,E),sumlist(E,N)), R),
    pairs_values(R, S).

暂无
暂无

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

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