Prolog: Choosing NOT to insert an element during list recursion - list

I've made a predicate that takes 2 list as arguments and returns a list concatenated with the product of "recipesub", however I need to make a third rule that prohibits the product from being inserted if the product at hand is an empty list.
So the first list could look like this:
recipe([ingredient(rice,4),ingredient(salt,3),ingredient(water,5)]).
And the second one like this:
ingredients([ingredient(rice,3),ingredient(salt,4),ingredient(water,4),
At the moment it returns: List = [ingredient(rice,1),[],ingredient(water,1)]
I WANT it to return: List = [ingredient(rice,1),ingredient(water,1)]
need_to_buy([],_,List):- List = [].
need_to_buy([H|Hs],[X|Xs],[Difference|List]):-
H = ingredient(Type,Amount),
recipesub(Type,Amount,[X|Xs],Difference),
need_to_buy(Hs,[X|Xs],List).
Below is how far I've gotten with the solution.
/*need_to_buy([H|Hs],[X|Xs],List):-
H = ingredient(Type,Amount),
recipesub(Type,Amount,[X|Xs],Difference),
Difference = [],
need_to_buy(Hs,[X|Xs],List).*/
And this is the support-predicate, recipesub.
recipesub(Type,Amount,[],Difference):-
Difference = ingredient(Type,Amount).
recipesub(Type,Amount,[Z|_],Difference):-
Z = ingredient(Type,Stock),
Amount>Stock,
NewAmount is Amount-Stock,
Difference = ingredient(Type,NewAmount).
recipesub(Type,Amount,[Z|_],Difference):-
Z = ingredient(Type, Stock),
Stock >= Amount,
Difference = [].
recipesub(Type,Amount,[Z|Zs],Difference):-
Z = ingredient(WrongType,_),
WrongType \= Type,
recipesub(Type,Amount,Zs,Difference).

I normally don't do a bunch of nested conditionals but it "felt right" this time, and this is the solution I found:
need_to_buy([], _, []).
need_to_buy([ingredient(Type, AmountNeeded)|Ingredients], OnHand, Needed) :-
% Do we have any on-hand?
member(ingredient(Type, AmountOnHand), OnHand) ->
% If the amount on-hand is greater than the amount needed,
% just hand off the rest
(AmountOnHand >= AmountNeeded ->
need_to_buy(Ingredients, OnHand, Needed)
% otherwise, calculate the amount needed and recur
; (AmountToBuy is AmountNeeded - AmountOnHand,
need_to_buy(Ingredients, OnHand, RestNeeded),
Needed = [ingredient(Type, AmountToBuy)|RestNeeded]))
% If we have none on-hand, we can just use the amount needed
% to form the request, and recur
; need_to_buy(Ingredients, OnHand, RestNeeded),
Needed = [ingredient(Type, AmountNeeded)|RestNeeded].
Otherwise I think you'll have a lot of fairly inefficient testing and retesting. The main mistake I see in your code is that you're pattern matching on the second argument. It's easier to rely on member/2 or memberchk/2 to do the dirty work of finding the right ingredient in the stuff you have on-hand.
If I did it with a bunch of clauses instead it would probably look like this:
need_to_buy([], _, []).
% case 1: we don't have the ingredient at all
need_to_buy([ingredient(Type, AmountNeeded) | Ingredients],
OnHand,
[ingredient(Type, AmountNeeded)|Needed]) :-
\+ memberchk(ingredient(Type, _), OnHand),
need_to_buy(Ingredients, OnHand, Needed).
% case 2: we have it, but not enough
need_to_buy([ingredient(Type, AmountNeeded) | Ingredients],
OnHand,
[ingredient(Type, AmountToBuy)|RestNeeded]) :-
memberchk(ingredient(Type, AmountOnHand), OnHand),
AmountNeeded > AmountOnHand,
AmountToBuy is AmountNeeded - AmountOnHand,
need_to_buy(Ingredients, OnHand, RestNeeded).
% case 3: we have enough
need_to_buy([ingredient(Type, AmountNeeded) | Ingredients],
OnHand,
RestNeeded) :-
memberchk(ingredient(Type, AmountOnHand), OnHand),
AmountNeeded =< AmountOnHand,
need_to_buy(Ingredients, OnHand, RestNeeded).
This leaves a choice point on the stack and just generally seems like a lot of retesting the same conditions and re-traversal for my taste. But if it looks better to you it should work the same.

I ultimately solved it by splitting the second rule of need_to_buy into two rules, one which handles the case where difference is an empty list, and on where it isnt an empty list.
I had some trouble with it at first but it turns out the "orientation" of the rules was giving me trouble, so I had to place the rule which handles the case where Difference \= [], above the one where Difference = [].
It now looks like this:
need_to_buy([],_,List):- List = [].
need_to_buy([H|Hs],[X|Xs],[Difference|List]):-
H = ingredient(Type,Amount),
recipesub(Type,Amount,[X|Xs],Difference),
Difference \= [],
need_to_buy(Hs,[X|Xs],List).
need_to_buy([H|Hs],[X|Xs],List):-
H = ingredient(Type,Amount),
recipesub(Type,Amount,[X|Xs],Difference),
Difference = [],
need_to_buy(Hs,[X|Xs],List).

Related

Prolog - Finding even elements in a list

I want to write a rule in Prolog that returns the even elements in a given list. For example:
even_elements([1,2,3,4], Result) would return Result: [2,4]
Here is what I have so far:
% This is my base case.
even_elements([H|T], Result) :- (0 is mod(H,2) -> Result = [H|T] ; Result = T).
% This is my recursion.
even_elements([H|T], [H|NT]) :- even_elements(T, NT).
The base case works properly and eliminates the first element if it is odd; but the recursion doesn't change anything. Any tips on how to complete the recursion is appreciated.
Often the base case in list processing deals with the empty list. Indeed, we can just write:
even_elements([], []).
For the recursive case, we can use quite a lot from your base case, the only thin that we still need to do is recurse on the tail of the list, so:
even_elements([H|T], Result) :-
( 0 is mod(H,2)
-> Result = [H|T2]
; Result = T2
),
even_elements(T, T2).
That being said, there is no need to implement the logic to filter a list. You can make use of the include/3 predicate [swi-doc], and thus define an even predicate:
even(N) :-
0 is N mod 2.
Then we can filter with:
even_elements(L, R) :-
include(even, L, R).
This then gives us:
?- even_elements([1,4,2,5], R).
R = [4, 2].
I also found this solution from this post although Willem's answer is way more readable:
even_elements(L1,L2):-findall(X,(member(X,L1), X mod 2=:=0),L2).

Prolog palindrome

I'm trying to write a palindrome function in Prolog. I know I could just use something like
palindrome(List) :- reverse(List, List).
But I'm trying to figure out a way without using the built in reverse. I've created my own reverse rule:
rev([], []).
rev([H|T], X) :- rev(T, Y), append(Y, [H], X).
And what I'd like is, given a list, say [a,b,c,d], I'd like to do something like "X = rev([a,b,c,d]), but I'm really not sure whether this is possible in Prolog.
If it is, the way I would write my palindrome function would be something like:
palindrome(List) :- append(L1, rev(L1), List).
Is it possible to do what I'm trying to do - i.e. X = rev([a,b,c,d])?.
Thanks.
Palindromes are lists that read the same from front to back and from back to front. So the example you have given, [a,b,c,d] and it's reversal, constitute a palindrome if the first is directly followed by the second: [a,b,c,d,d,c,b,a]. Since you are trying to describe specific kinds of lists, it is very tempting to use Prolog DCGs for the task. With them you can define palindromes like so:
palindrome(X) :-
phrase(palindrome,X).
palindrome --> % base case for even number of elements
[].
palindrome --> % base case for odd number of elements
[A].
palindrome --> % general case: a palindrome is
[A], % some element A...
palindrome, % ... followed by a palindrome ...
[A]. % ... followed by element A
The most general query is producing palindromes with variables for each position:
?- palindrome(P).
P = [] ? ;
P = [_A] ? ;
P = [_A,_A] ? ;
P = [_A,_B,_A] ? ;
P = [_A,_B,_B,_A] ? ;
P = [_A,_B,_C,_B,_A] ?
...
Or alternatively you can test if a specific list is a palindrome:
?- palindrome("rats live on no evil star").
yes
?- palindrome([1,2,3,2,1]).
yes
?- palindrome([a,b,c,d]).
no
?- palindrome([a,b,c,d,d,c,b,a]).
yes
If you insist on using list reversal you can define the relation like so:
list([]) -->
[].
list([X|Xs]) -->
[X],
list(Xs).
invlist([]) -->
[].
invlist([X|Xs]) -->
invlist(Xs),
[X].
palindrome --> % a paindrome is
list(L), % a list followed
invlist(L). % by its reversal
palindrome --> % a palindrome is
list(L), % a list followed by
[_A], % some element
invlist(L). % then by the reversed list
The first of the above queries produces the answers in a different order now, namely the solutions with an even number of elements first:
?- palindrome(P).
P = [] ? ;
P = [_A,_A] ? ;
P = [_A,_B,_B,_A] ? ;
P = [_A,_B,_C,_C,_B,_A] ?
...
The other example queries yield the same result. However, the first definition seems to be clearly preferable to me. Not only because it is shorter as there is no need for additional DCG rules but also because it is producing the results in a fair order: empty list, one element, two elements, ... With the second version you get all the lists with an even number of elements first and there are infinitely many of those. So you never get to see a solution with an odd number of elements with the most general query.

List - counting atoms related to their previous term

I want to count the number of elements in a list which have a relation with the element following.
The predicate I have works by using an accumulator variable which it increments if the predicate related returns true.
The following example code is to check the number of times an element is greater than it's previous element.
So for example
count_list([1,2,3,2,1,3,2],Count).
should return 3.
The code almost works. It increments the accumulator variable correctly. However, the function returns false, when it tries to compare the final 2 at the end with the non-existent next term.
listofitems([],N,N).
%count number of items which are related to the previous
listofitems([A,B|T],Acc,N) :-
write(A),write(' '), write(B),
( related(A,B) -> Acc1 is Acc+1 ; Acc1 = Acc ),
write(Acc1),write('\n'),
listofitems([B|T],Acc1,N).
count_list(L,N):-
listofitems(L,0,N).
%define the relationship to be counted
related(A,B):-
B>A.
Does anyone have any suggestions as to how to create an elegant terminating condition so I can return the accumulated value?
Does anyone have any suggestions as to how to create an elegant terminating condition so I can return the accumulated value?
The problem you have is that your query fails. Try first to minimize the query as much as possible. Certainly, you expect it to work for:
?- listofitems([], Count).
Count = 0.
Yet, it already fails for:
?- listofitems([1], Count).
false.
So let's try to dig into the reason for that.
And since your program is pure (apart from those writes), it is possible to diagnose this a little better by considering a generalization of your program. I prefer to look at such generalizations as I do not want to read too much (eye strain and such):
:- op(950, fy, *).
*_.
listofitems([], N,N).
listofitems([A,B|T], Acc,N) :-
* ( related(A,B) -> Acc1 is Acc+1 ; Acc1 = Acc ),
* listofitems([B|T], Acc1,N).
count_list(L,N):-
listofitems(L,0,N).
?- count_list([1], Count).
false.
Even this generalization fails! So now in desperation I try to ask the most general query. It's like when I ask one thing after the other and get a noe after a no. Good this is Prolog, for we can ask: "Say me just everything you know".
?- count_list(Es,Count).
Es = [], Count = 0
; Es = [_,_|_].
So it is only the case for the empty list and lists with at least two elements. But there is no answer for one-elemented lists! You will thus have to generalize the program somehow.
A natural way would be to add a fact
listofitems([_], N, N).
As a minor remark, this isn't called a "terminating condition" but rather a "base case".
And if you really want to trace your code, I recommend these techniques instead of adding manual writes. They are much too prone to error.
If the all list items are integers and your Prolog system supports clpfd, you can proceed like this:
:- use_module(library(clpfd)).
:- use_module(library(lists), [last/3]).
:- use_module(library(maplist), [maplist/4]).
To relate adjacent items, look at two sublists of [E|Es], Es and Fs. If, say,
[E|Es] = [1,2,3,2,1,3,2] holds ...
... then Fs lacks the last item (Fs = [1,2,3,2,1,3,2]) ...
... and Es lacks the first item (Es = [1,2,3,2,1,3,2]).
maplist/4 and i0_i1_gt01/3 map corresponding list items in Fs and Es to 0 / 1:
i_j_gt01(I, J, B) :- % if I #< J then B #= 1
I #< J #<=> B. % if I #>= J then B #= 0
?- maplist(i_j_gt01, [1,2,3,2,1,3], [2,3,2,1,3,2], Bs).
Bs = [1,1,0,0,1,0].
Last, sum up [1,1,0,0,1,0] using sum/3:
?- sum([1,1,0,0,1,0], #=, N).
N = 3.
Let's put it all together!
count_adj_gt([E|Es], N) :-
last(Fs, _, [E|Es]), % or: `append(Fs, [_], [E|Es])`
% or: `list_butlast([E|Es], Fs)`
maplist(i_j_gt01, Es, Fs, Bs),
sum(Bs, #=, N).
Sample query using SICStus Prolog 4.3.2:
?- count_adj_gt([1,2,3,2,1,3,2], N).
N = 3. % succeeds deterministically
not sure about
an elegant terminating condition
my whole code would be
?- Vs=[1,2,3,2,1,3,2], aggregate_all(count, (append(_,[X,Y|_], Vs), X<Y), Count).
That's all...
If you need something more complex, remember that library(clpfd) has more to offer.

Re-organizing a list in prolog

I am trying to write some Prolog code to take a list such as:
[[park, joe], [park, bob], [park, kate], [school, joe], [zoo, amy], [zoo, ted]].
and organizes the list into the form:
[[park,[joe, bob, kate]], [school,[joe]], [zoo,[amy, ted]]].
It can be assumed that all matching heads of each element (park = park, zoo = zoo) are directly next to each other in the list because the code I made to create the list sorts it in alphabetical order. I can't seem to figure out how to accomplish this and seem to get errors at every turn :(. Below is the code that I have so far in the last state that it ran without errors and I will try to explain what I was thinking.
merge([],[]).
merge([First|Rest], Z) :-
merge(Rest, U),
[Meet1, Person1] = First,
( =(U, []) -> % beginning case from recursion, U is empty
Meet2 = [],
Person2 = [];
[[Meet2|Person2]|_] = U),
( =(Meet1, Meet2) -> % Case of matching heads, combine the tails
print('Match '),
append([First], U, Z);
print('No-match '), % otherwise, not matching
append([First], U, Z) ).
So what I was trying to do is use appends to add all of the changes to U and return it to the console with Z. such as,
( =(Meet1, Meet2) ->
append(Person1, Person2, Combpersons),
append([Meet1], [Combpersons], T),
append(T, U, Z);
...no match code here..).
However my code keeps ending prematurely with a false when I try to change or add appends like this in the first block of code I put here. Even a change such as turning append([First], U, Z) into append([Meet1], U, Z) makes my code end with a false and I am not understanding why. Any help/hints on creating a solution would be appreciated.
I think that learning any language it's a process where low and high level issues must be interleaved. So far, you're learning the basic syntax. But why you use such unreadable constructs ? And of course, any programming language builds upon a set of patterns, usually covered by libraries. Consider
l2p([A,B],A-B).
?- maplist(l2p,[[park, joe], [park, bob], [park, kate], [school, joe], [zoo, amy], [zoo, ted]], L),group_pairs_by_key(L,G).
L = [park-joe, park-bob, park-kate, school-joe, zoo-amy, zoo-ted],
G = [park-[joe, bob, kate], school-[joe], zoo-[amy, ted]].
Anyway, here is your code restructured:
merge([],[]).
merge([[Meet, Person]|Rest], Z) :-
merge(Rest, U),
( U = []
-> % beginning case from recursion, U is empty
Z = [[Meet, [Person]]]
; U = [[Meet, Persons] | Rest1]
-> % Case of matching heads, combine the tails
Z = [[Meet, [Person | Persons]] | Rest1]
; % otherwise, not matching
Z = [[Meet, [Person]] | U]
).
If you had your initial list as a list of pairs instead, you could use library(pairs), available in SWI-Prolog.
?- group_pairs_by_key([park-joe, park-bob, park-kate, school-joe, zoo-amy, zoo-ted], G).
G = [park-[joe, bob, kate], school-[joe], zoo-[amy, ted]].
Using the library gives you more than just this "reorganization". There is also library(ugraphs), which might be better suited, depending on what you are doing.
Your code fails, because you are trying to extract the place and the person before checking if there are any.
No use for append here, the reordering looks pretty complicated, but I did my best with the variable names. Furthermore you'll only need one ->: If you hadn't found a tuple with the same first coordinate, you'll want a to start a new, it doesn't matter if there was none before.
merge([],[]).
merge([[Place,Person]|Rest], [[Place,Group]|OtherGroups]) :-
merge(Rest, U),
(U = [[Place,Others]|OtherGroups] ->
Group = [Person|Others];
[OtherGroups,Group] = [U, [Person]]).
Edit: I changed the solution for readablity reasons.

List of all the elements in sublist in Prolog

I need to write a predicate f(L,R) that succeeds if and only if L is a list containing all terms in R that are not lists.
For example:
f(L,[1,2,3,[4,5,6],[[7,8,9]],[]]).
Should give:
L = [1,2,3,4,5,6,7,8,9]
I wrote a predicate that gives the following result instead:
L = [1,2,3,4,5,6,7,8,9,[]]
Empty lists should not be present in the result. My predicate is the following:
f([],[]).
f(V,[H|T]):- H = [_|_] -> append(L,R,V),
f(L,H), f(R,T),!;
V = [H1|T1], H1=H, f(T1,T).
I have two doubts. First of all, the empty lists should not be present in the result. Also I don't know why it does not work if I don't put the cut (!). In fact, if I don't put the cut it gives me the result as above, but if I ask for another result it loops forever. I really don't understand why this should loops.
To remove the empty list, handle that case (discard it).
About the loop: I think the cause could be that you're calling append(L,R,V) with all arguments not instantiated: move append after the recursive calls.
Finally, maybe you don't use rightly the 'if then else' construct: I've indented using the usual SWI-Prolog source style, using indentation to highlight 'sequential' calls
f([], []).
f(V, [H|T]) :-
( H = [] % if H = []
-> f(V, T) % then discard
; H = [_|_] % else if H is list
-> f(L,H), % flat head
f(R,T), % ...
append(L,R,V)
; V = [H|T1], % else
f(T1,T) % ...
).