Split a list into list of lists in Prolog - 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)] ]

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

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

Related

How to ask for specific result in Prolog?

I prepared a Prolog solution of a riddle about people living in a three-floor block with two sides(left,right) - so 6 flats. The solution is correct. The code for solution is below:
What I want to achieve is to have three different predicates answer1/1, answer2/2, answer3/3 where:
Answer1/1 - returns information about all people, I think that clues(Left,Right) already does that, however I think that /1 means the number of arguments in predicate. Is it possible to make it with just one argument?
Answer2/2 - returns information about person with certain attribute, let's say it's supposed to show citizen with first attribute equal to 'wladyslaw'.
Answer3/3 - returns information about people from the left side of building (so those, who are mentioned in parameter Left in Answer1/1.
I wonder, whether this slash and value means number of parameters and if so - is it possible to make it? Thanks in advance.
There's 2 variables for each person - their floor (numbered 1-3, with 3 being ground), and their block (left or right, i.e. 1 or 2).
Your clue1, clue2 etc. are kinda cheating because they are specifying the floor and block of the persons.
Here is not-completely-tested code, with lots of clues:
% Like nth1, but for multiple lists
% LN is list number, starting at 1
nth1_lists(N, [H|T], E, LN) :-
nth1_lists_(T, H, N, E, 1, LN).
nth1_lists_(_, H, N, E, LN, LN) :-
nth1(N, H, E).
nth1_lists_([H|T], _, N, E, LN0, LN) :-
LN1 is LN0 + 1,
nth1_lists_(T, H, N, E, LN1, LN).
% Changes to plural - presumably don't care which block
in_blocks(P, Blocks) :-
% Don't care which pos or block
nth1_lists(_, Blocks, P, _).
lives_above(X, Y, Blocks) :-
% Same block B
nth1_lists(XN, Blocks, X, B),
nth1_lists(YN, Blocks, Y, B),
XN < YN.
lives_near(X, Y, Blocks) :-
% Different block, same pos
dif(XB, YB),
nth1_lists(Pos, Blocks, X, XB),
nth1_lists(Pos, Blocks, Y, YB).
lives_1_floor_above(X, Y, Blocks) :-
% Pos 1 is highest, pos 3 is ground floor
nth1_lists(XN, Blocks, X, _),
nth1_lists(YN, Blocks, Y, _),
YN is XN + 1.
lives_between(X, Y, Z, Blocks) :-
dif(C, =),
% Same block
nth1_lists(XN, Blocks, X, B),
nth1_lists(YN, Blocks, Y, B),
nth1_lists(ZN, Blocks, Z, B),
% Same comparison sign
compare(C, XN, YN),
compare(C, YN, ZN).
% Removed person Y - only care about person X
on_left(X, Blocks) :-
% Left block is block 1
nth1_lists(_, Blocks, X, 1).
ground_floor(X, Blocks) :-
% Don't care which block
nth1(3, Blocks, X, _).
clues(Blocks) :-
% Size of blocks
length(Blocks, 2),
maplist(length(3), Blocks),
% Or just use: Blocks = [[_,_,_], [_,_,_]],
% clue1
lives_above(karmikanarki, wkazdejchwili, Blocks),
% clue2
lives_near(weronika, o4rano, Blocks),
lives_near(zamiatachodniki, wladyslaw, Blocks).
% and so on, up to clue 13
answer1(Blocks) :-
clues(Blocks).
answer2(Person, Pos-Block) :-
clues(Blocks),
nth1_lists(Pos, Blocks, Person, Block).
% Unsure whether Block is desired in arguments
answer3(Person, Pos-Block) :-
clues(Blocks),
% Person is in block 1
on_left(Person, Blocks),
nth1_lists(Pos, Blocks, Person, Block).
Answer1/1 - returns information about all people, I think that clues(Left,Right) already does that, however I think that /1 means the number of arguments in predicate. Is it possible to make it with just one argument?
Yes the /1 does mean the number of arguments (it's called the 'arity'). Change clues(Left,Right) :- to clues(Left-Right) :- and it is one argument. Just change that header and the way you query it, nothing else.
Answer2/2 - returns information about person with certain attribute, let's say it's supposed to show citizen with first attribute equal to 'wladyslaw'.
Your comment to #brebs says you cannot use lists, so you cannot use member/2. Here is a way:
answer2(Name, Citizen) :-
clues(left(L1, L2, L3)-right(R1, R2, R3)),
( (L1 = citizen(Name, _, _), Citizen = L1)
; (L2 = citizen(Name, _, _), Citizen = L2)
; (L3 = citizen(Name, _, _), Citizen = L3)
; (R1 = citizen(Name, _, _), Citizen = R1)
; (R2 = citizen(Name, _, _), Citizen = R2)
; (R3 = citizen(Name, _, _), Citizen = R3)).
Answer3/3 - returns information about people from the left side of building (so those, who are mentioned in parameter Left in Answer1/1.
answer3(L1, L2, L3) :-
clues(left(L1, L2, L3)-_).

PROLOG - LCM of couples in list

I want to find the Least Common Multiple (LCM) of couples from a list. But in the following way:
For example if I have this list:
L1 = [1,2,3,4,5].
I want to produce this list:
L2 = [1,2,6,12,60].
I use the first element of L1 as first element of L2 and the rest follow this form:
L2[0] = L1[0]
L2[i+1] = lcm( L1[i+1] , L2[i] )
Here is what I've done so far, but it doesn't work. Always printing false.
%CALL_MAKE----------------------------------------
%Using call_make to append Hd to L2 list
call_make([Hd|Tail], Result) :-
make_table(Tail, [Hd], Result).
%MAKE_TABLE---------------------------------------
%Using make_table to create the rest L2
make_table([],Res,Res).
make_table([Head|Tail], List, Result) :-
my_last(X, List),
lcm(Head, X, R),
append(List, R, Res),
make_table(Tail, Res, Result).
%last element of a list---------------------------
my_last(X,[X]).
my_last(X,[_|L]):- my_last(X, L).
%GCD----------------------------------------------
gcd(X, 0, X) :- !.
gcd(X, Y, Z) :-
H is X rem Y,
gcd(Y, H, Z).
%LCM----------------------------------------------
lcm(X,Y,LCM):-
gcd(X,Y,GCD),
LCM is X*Y//GCD.
I want to run the program and get this:
?- call_make([1,2,3,4,5], Result).
Result = [1,2,6,12,60].
append/3 is taking two lists to concatenate, while here:
append(List, R, Res),
R is a scalar. Change it to
append(List, [R], Res),

Prolog list of predicates to list of lists

I have a list like: [a([x,y]), b([u,v])] and I want my result as [[x,y], [u,v]].
Here is my code:
p(L, Res) :-
findall(X, (member(a(X), L)), A1), append([A1],[],L1),
findall(Y, (member(b(Y), L)), A2), append(L1,[A2],L2),
append(L2, Res).
This provides a partially good result but if my list is [a([x,y]), c([u,v])], I would like the result to be: [[x,y],[]] and it is [[x,y]].
More examples:
p([b([u,v]), a([x,y]), c([s,t]), d([e,f])], R)
The result I get: [[x,y],[u,v]] (as expected).
p([b([u,v]), z([x,y]), c([s,t]), d([e,f])], R)
The result I get: [[u,v]]'.
The result I want: [[],[u,v]].
EDIT: Added more examples.
Now that it's clear what the problem statement really is, the solution is a little more understood. Your current solution is a little bit overdone and can be simplified. Also, the case where you want to have a [] element when the term isn't found falls a little outside of the paradigm, so can be handled as an exception. #AnsPiter has the right idea about using =../2, particularly if you need a solution that handles multiple occurrences of a and/or b in the list.
p(L, Res) :-
find_term(a, L, As), % Find the a terms
find_term(b, L, Bs), % Find the b terms
append(As, Bs, Res). % Append the results
find_term(F, L, Terms) :-
Term =.. [F, X],
findall(X, member(Term, L), Ts),
( Ts = [] % No results?
-> Terms = [[]] % yes, then list is single element, []
; Terms = Ts % no, then result is the list of terms
).
Usage:
| ?- p([b([u,v]), z([x,y]), c([s,t]), d([e,f])], R).
R = [[],[u,v]]
yes
| ?- p([b([x,y]), a([u,v])], L).
L = [[u,v],[x,y]]
yes
| ?-
The above solution will handle multiple occurrences of a and b.
If the problem really is restricted to one occurrence of each, then findall/3 and append/3 are way overkill and the predicate can be written:
p(L, [A,B]) :-
( member(a(A), L)
-> true
; A = []
),
( member(b(B), L)
-> true
; B = []
).
Term =.. List : Unifies List with a list whose head is the atom corresponding to the principal functor of
Term and whose tail is a list of the arguments of Term.
Example :
| ?- foo(n,n+1,n+2)=..List.
List = [foo,n,n+1,n+2] ?
| ?- Term=..[foo,n,n+1,n+2].
Term = foo(n,n+1,n+2)
rely on your suggestion; you have a term contains a single argument List
so ;
p([],[]).
p([X|Xs], Result) :-
X=..[F,Y],
(%IF
\+(F='c')-> % not(F=c)
Result=[Y|Res];
%ELSE
Result = Res % Result = [Res] ==> [[x,y],[]]
),
p(Xs,Res).
Test :
| ?- p([a([x,y]), c([u,v])],R).
R = [[x,y]] ?
yes
| ?- p([a([x,y]), b([u,v])],R).
R = [[x,y],[u,v]] ?
yes

prolog program : compares two lists' items and return a list of unique element that are on List1

This program compares two lists' items and returns a list with items that are members of first list and are not members of second list. For example: list1=[a,b,d], list2=[r,a,f,b] ----> result =[a,b].
go:- comp([y,h,b],[b,t],R),!.
comp([],_,_) :- !.
comp(_,[],_) :- !.
comp([H|T],B,_) :- memberchk(H,B),comp(T,B,_); comp(T,B,R),write([H]).
current result is [h][y]
result I need should be [h,y]
Your request is for a predicate which returns a list with items that are members of first list and are not members of second list. But your example:
list1=[a,b,d], list2=[r,a,f,b] ----> result =[a,b]
Is the result of returns a list with members that are in both lists (intersection). I'm assuming you want what you requested, not what your example shows.
In your original, you had:
comp(_, [], _).
Which would not give a correct result if you queried, say, comp([a], [], X) since you're using the "don't care" term, _. It's an improper expression of what you probably intended, which is comp(L, [], L) (a list is itself if you exclude elements of an empty list from it). In addition, none of your original clauses instantiates a result (all of them have the "don't care" _ in that position).
A corrected version might look like this:
comp([], _, []).
comp(L, [], L).
comp([H|T], S, R) :-
( memberchk(H, S)
-> comp(T, S, R)
; R = [H|RT],
comp(T, S, RT)
).
?- comp([y,h,b],[b,t],R).
R = [y, h] ;
false.
?-
Note that the "false" response after typing ; means there are no additional solutions.

Prolog: Replace list items using facts

im trying to make a function that loops through a list and replaces the element if it matches a fact.
I was able to implement a simple replacement that replaces every element in the list.
replace([X|T], Y, [Y|T2]) :- replace(T,Y,T2).
replace([],X,[X]).
so this just replaces every list item in X with Y.
Now i want to replace every list item in X using a fact like so:
replace([1,2,3], [ rule(1, [one]), rule(2, [two]) ], Result)
so if the list is [1,2,3], the result will be [one, two, 3]
how would I do this ?
I do prefer to use higher order library support
replace(In, Replacements, Out) :-
maplist(replace_one(Replacements), In, Out).
replace_one(RepList, Rep, Val) :-
memberchk(rule(Rep, [Val]), RepList) -> true ; Rep = Val.
I think it can be simply:
replace([], _, []).
replace([H|T], Rules, [R|TR]) :-
( memberchk(rule(H, [R]), Rules)
-> true
; H = R
),
replace(T, Rules, TR).
You can do it by adding a second rule that goes through the list of replacements, and either picks the first one that matches, or leaves the item unchanged, like this:
replace([],_,[]).
replace([H|T], L, [RH|RT]) :- replace(T,L,RT), replace_one(H, L, RH).
replace_one(H, [], H).
replace_one(H, [rule(H,B)|_], B).
replace_one(H, [rule(A,_)|T], R) :- H \= A, replace_one(H, T, R).
Demo on ideone.

Resources