elfii
elfii

Reputation: 59

Split a list into list of lists in Prolog

I have a list of predicates:

[patient(204,4,2),patient(203,3,2),patient(303,7,3),patient(302,6,3),patient(404,12,4),patient(403,11,4),patient(504,16,5),patient(503,15,5)]

I want to have a list of lists depending on the 3rd argument of each predicate:

[ [patient(204,4,2),patient(203,3,2)] , [patient(303,7,3),patient(302,6,3)] , [patient(404,12,4),patient(403,11,4)] , [patient(504,16,5),patient(503,15,5)] ]

Upvotes: 1

Views: 118

Answers (2)

brebs
brebs

Reputation: 4438

Copying my answer from https://swi-prolog.discourse.group/t/sorting-predicates-in-prolog/5618/19 here:

test_sort_group(Group) :-
    Patients = [patient(204,4,2),patient(203,3,2),patient(304,8,3),patient(303,7,3),patient(404,12,4),patient(403,11,4),patient(504,16,5),patient(503,15,5),patient(1,1,1),patient(506,6,5)],
    group_list_by_arg(3, Patients, Group).


group_list_by_arg(Arg, Lst, Group) :-
    % Keeping duplicates
    sort(Arg, @=<, Lst, [H|T]),
    group_list_by_arg_main_(T, Arg, H, Group).

group_list_by_arg_main_(T, Arg, H, Group) :-
    arg(Arg, H, ArgVal),
    Upto = [H|Upto0],
    group_list_by_arg_loop_(T, Arg, ArgVal, Upto0, Rem),
    group_list_by_arg_rem_(Rem, Arg, Upto, Group).

% Reached end of groups
group_list_by_arg_rem_([], _, G, G).
group_list_by_arg_rem_([_|_], _, G, G).
group_list_by_arg_rem_([H|T], Arg, _Upto, Group) :-
    % Assemble next group
    group_list_by_arg_main_(T, Arg, H, Group).

% Reached end of sorted list
group_list_by_arg_loop_([], _, _, [], []).
group_list_by_arg_loop_([H|T], Arg, ArgVal, Group, Rem) :-
    arg(Arg, H, ArgVal), !,
    % Add element to current group
    Group = [H|Group0],
    group_list_by_arg_loop_(T, Arg, ArgVal, Group0, Rem).
% Finished this group, but there may be other elements
group_list_by_arg_loop_([H|T], _Arg, _ArgVal, [], [H|T]).

Result in swi-prolog:

?- time(bagof(G, test_sort_group(G), Gs)).
% 49 inferences, 0.000 CPU in 0.000 seconds (94% CPU, 599807 Lips)
Gs = [[patient(1,1,1)],[patient(204,4,2),patient(203,3,2)],[patient(304,8,3),patient(303,7,3)],[patient(404,12,4),patient(403,11,4)],[patient(504,16,5),patient(503,15,5),patient(506,6,5)]].

Upvotes: 0

TessellatingHeckler
TessellatingHeckler

Reputation: 28993

You can group_by/4 which works in mysterious ways and will get you a single sublist at a time. E.g. assuming your list is Patients:

group_by(C, patient(A,B), member(patient(A,B,C), Patients), Group)

Then

Group = [patient(204,4), patient(203,3)]

I can't get "C" into the group, which is annoying because we'll have to put it back in later. Use findall/3 to get all the groups:

findall(C-Group,
    group_by(C, patient(A,B), member(patient(A,B,C), _Patients), Group),
AllGroups)

That makes something of this shape, with the grouping value on the front of the sublists:

AllGroups = [
  2-[patient(204,4), patient(203,3)],
  3-[patient(303,7), patient(302,6)],
  4-[patient(404,12), patient(403,11)],
  5-[patient(504,16), patient(503,15)]
]

And then some post-processing to put C back in:

restoreC([], []).
restoreC([C-Group|CBs], [L|Ls]) :-
    maplist({C}/[P1,P2]>>(P1=patient(A,B),P2=patient(A,B,C)), Group, L),
    restoreC(CBs, Ls).

Tag ,restoreC(AllGroups, Result) onto the end for:

Result = [
  [patient(204,4,2), patient(203,3,2)],
  [patient(303,7,3), patient(302,6,3)],
  [patient(404,12,4), patient(403,11,4)],
  [patient(504,16,5), patient(503,15,5)]
]

Upvotes: 1

Related Questions