LECTURES 4-5 REVISITED: Issue #7
tag/2
Assign the correct label to a given point, the correct label = the label of the nearest point.
Bonus: assign unknown
in cases similar to tag([3,-1], Label).
point([3,7], male).
point([3,-2], male).
point([0,0], female).
point([3,0], female).
point([3,3], female).
point([6,0], female).
% Sample queries
% ?- tag([4,6], Label).
% Label = male.
% ?- tag([0,-10], Label).
% Label = male.
% ?- tag([1,1], Label).
% Label = female.
% ?- tag([3,0], Label).
% Label = female.
% ?- tag([3,-1], Label). % bonus
% Label = unkown.
Example 1.
teaches(dr_fred, history).
teaches(dr_fred, english).
teaches(dr_fred, drama).
teaches(dr_fiona, physics).
studies(alice, english).
studies(angus, english).
studies(amelia, drama).
studies(alex, physics).
/*
?- teaches(dr_fred, Course), studies(Student, Course).
?- teaches(dr_fred, Course), !, studies(Student, Course).
?- teaches(dr_fred, Course), studies(Student, Course), !.
*/
Example 2.
a(X) :- b(X), !, c(X). % what happened without cut?
b(1).
b(2).
b(3).
c(2).
% ?- a(X).
Example 3.
% rule :- condition, !, then_part. % ! affects the left-hand side as well
% rule :- else_part.
max(X,Y,Y):- X =< Y.
max(X,Y,X):- X > Y.
% ?- max(3,4,Max).
% X = 4 ;
% false.
max(X,Y,Y) :- X =< Y, !.
max(X,Y,X) :- X > Y.
% Let's squeez it a little bit..
max(X,Y,Y) :- X =< Y,!.
max(X,Y,X).
% but we will have another issue, try the following!
% ?- max(2,3,2).
% true.
% it should return false.. the result should be 3
% Because any query with the same number in first and last parameter will return true due to unification!
% So we have to prevent prolog from entering the second max/3 via making sure it hits the CUT ! in the first max/3.. but why it didn't hit it at first?
% Because of the unification, once it sees the second and last parameters are not the same: it does not go to the right hand side at all..
% We might use a trick of introducing a third variable to avoid such a problem...
max(X,Y,Z) :- X =< Y,!, Z = Y.
max(X,Y,X).
% ?- max(2,3,2).
% false.
% Now it works fine.. because Z = Y will return false, and the second max/3 will not be hit due to the CUT ! being executed.
Example 4.
Splits a list of integers into two lists: one containing the positive ones (and zero), the other containing the negative ones.
split1([],[],[]).
split1([H|T], [H|P], N) :- H >= 0, split1(T,P,N).
split1([H|T], P, [H|N]) :- H < 0, split1(T,P,N).
/*
?- split([3,4,-5,-1,0,4,-9],P,N).
P = [3,4,0,4] ,
N = [-5,-1,-9];
false
*/
% eliminate the false thing, use !
split2([],[],[]).
split2([H|T], [H|P], N) :- H>=0, !, split2(T,P,N).
split2([H|T], P, [H|N]) :- split2(T,P,N).
/*
?- split([3,4,-5,-1,0,4,-9],P,N).
P = [3,4,0,4] ,
N = [-5,-1,-9].
But again, a validation issue..
?- split([3,4,-5,-1,0,4,-9],[4,0,4],[3, -5,-1,-9]).
true.
*/
split3([],[],[]).
split3([H|T], P, N) :- H>=0, !, split3(T,P1,N), P = [H|P1].
split3([H|T], P, [H|N]) :- split3(T,P,N).
/*
?- split([3,4,-5,-1,0,4,-9],[4,0,4],[3, -5,-1,-9]).
false.
*/
% SOLVED.
**Notes on !
**
- Always succeeds.
- Prevents unwanted backtracking.
- Should not be used generously, use it only for a reason.
Example 1. (command-line example)
% between/3 ensures looping over the first-second numbers..
% ?- between(1,3,N), format("line ~w\n", [N]).
% you have to hit ; to get the next result,, can you make it automated? i.e. can you enforce the backtracking?
% these are the same..
% ?- between(1,3,N), format("line ~w\n", [N]), fail.
% ?- between(1,3,N), format("line ~w\n", [N]), false.
% ?- between(1,3,N), format("line ~w\n", [N]), 0=1.
Example 2.
%
writeall(X) :-
is_list(X), member(A,X), write(A), nl, fail.
% ?- writeall([1,2,3]).
% previous call will print the values of [1,2,3] with false at the end, can you eleminate the false thing?
% adding this rule would help prolog not returning false due to the fail call.
writeall(_).
Example 3.
% 3 facts
student(joe).
married(john).
unmarried_student(X) :- \+ married(X), student(X).
% ?- unmarried_student(joe).
% true
% ?- unmarried_student(john).
% false
% ?- unmarried_student(X).
% false (this should be joe, how to fix it?)
% fix1?
% if he is married, return false
unmarried_student1(X) :- married(X), fail.
% otherwise, all students are unmarried
unmarried_student1(X) :- student(X).
% fix2?
% swap the conditions
unmarried_student2(X) :- student(X), \+ married(X).
Example 4.
b(1).
b(4).
c(1).
c(3).
d(4).
a(X) :- b(X), c(X), !, fail. % try this..
% a(X) :- b(X), c(X), fail. % or this!
a(X) :- d(X).
% ?- a(X). % What is the output? is it X = 4?
Example 5.
man(jim).
man(fred).
woman(jane).
woman(X):- man(X), fail.
woman(_).
% ?- woman(X). % it should be jane, what is the output?
Final thoughts:
- when Prolog fails, it tries to backtrack, thus
fail
can be viewed as an instruction to force backtracking. - this is a good resource to read.
Example 1.
?- assert(lookup(1,2,3,4)).
true
?- lookup(1,2,3,4).
true
Example 2.
% We can store the result once we found it, to redo none of the calculations for the same query next time.
:- dynamic lookup/3.
add_and_square(X, Y, Res):- lookup(X, Y, Res), !.
add_and_square(X, Y, Res):- Res is (X+Y)*(X+Y),
assert(lookup(X, Y, Res)).
Example 3.
:- dynamic fib/2.
fib(0,0).
fib(1,1).
fib(X,Y) :- X>1,X1 is X - 1, X2 is X - 2,
fib(X1,Y1),fib(X2,Y2),
Y is Y1 + Y2,
% save the query result as a fact.
% note: assertz is useless in this case.
% if ! is not used, then prolog will want to try the recursive rule after using the asserted fact
asserta(fib(X, Y) :- !), !.
% use time(:Goal) to log how many recursive calls occurs to find out the query result, call `help(time)` to read its documentation.
% the first fib(15, Res) needs 72 inferences, but the second time you ask for fib(15, Res) it will take 0 inferences, because it is stored as a fact
let's try it..
?- time(fib(15, Res)).
% 72 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
Res = 987.
?- time(fib(15, Res)).
% 0 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
Res = 987 .
Note: more examples on assert, retract can be found in Utilities.md
child(martha, charlotte).
child(charlotte, caroline).
child(caroline, laura).
child(laura, rose).
descend(X, Y) :- child(X, Y).
descend(X, Y) :- child(X, Z), descend(Z, Y).
% ?- descend(martha, X). % use ; to get another solution..
% ?- findall(X, child(X, Y), Z).
% ?- findall(Y, child(X, Y), Z).
% ?- findall(X, descend(martha, X), Z), length(Z, N).
get_range(Min, Max, List) :- findall(X, between(Min, Max, X), List).
get_positive_elements(List, PosList) :- findall(X, (member(X, List), X >= 0), PosList).
Question: Can you implement is_prime/1
using findall
and between
?
X/O Game 1
/*
Q1. print_grid/1 (1 Mark)
% example
?- print_grid([x, 0, x, 0, x, o, o, 0, o])
x 0 x
0 x o
o 0 o
Q2. get_empty_cells/2 (2 Marks)
% examples
?- get_empty_cells([0, 0, 0, 0, 0, 0, 0, 0, 0], EmptyCells).
EmptyCells = [1, 2, 3, 4, 5, 6, 7, 8, 9]
?- get_empty_cells([x, 0, x, 0, x, o, o, 0, o], EmptyCells).
EmptyCells = [2, 4, 8]
Q3. insert_char_at_position/4 (2 Marks)
% example
?- insert_char_at_position(x, 2, [x, 0, x, 0, x, o, o, 0, o], UpdatedGrid).
UpdatedGrid = [x, x, x, 0, x, o, o, 0, o]
Q4. get_winner_name/2 (WinnerName can be x, o or noBody) (2 Marks)
% example
?- get_winner_name([x, x, x, 0, x, o, o, 0, o], WinnerName).
WinnerName = x
Q5. play/0. this will start the X/O game using the previous rules.. (3 Marks)
% User is X, Computes is O.
% Game ends when One player wins or the Grid is filled with no winner.
% Example
?- play.
0, 0, 0
0, 0, 0
0, 0, 0
Your turn.. available cells are [1,2,3,4,5,6,7,8,9]: 5
0, 0, o
0, x, 0
0, 0, 0
Your turn.. available cells are [1,2,4,6,7,8,9]: 1
x, 0, o
0, x, o
0, 0, 0
Your turn.. available cells are [2,4,7,8,9]: 9
x, 0, o
0, x, o
0, 0, x
You win.
true.
*/
% MY SOLUTION (you can submit your solution in the issues)
% Answer1.
print_grid([One, Two, Three, Four, Five, Six, Seven, Eight, Nine]) :-
format('~w ~w ~w', [One, Two, Three]), nl,
format('~w ~w ~w', [Four, Five, Six]), nl,
format('~w ~w ~w', [Seven, Eight, Nine]), nl.
% Answer2.
get_empty_cells([], []).
get_empty_cells([H|T], EmptyCells) :- H \= 0, get_empty_cells(T, EmptyCells), !.
get_empty_cells([H|T], [Index|EmptyCells]) :- length(T, Len), Index is (9 - Len),
get_empty_cells(T, EmptyCells).
% Answer3.
insert_char_at_position(Char, 1, [GridH|GridT], [Char|GridT]).
insert_char_at_position(Char, Position, [GridH|GridT], [GridH|NewGridT]) :-
log('insert_char_at_position'),
NewPos is Position - 1,
insert_char_at_position(Char, NewPos, GridT, NewGridT).
% Answer4.
get_winner_name([A, A, A, Four, Five, Six, Seven, Eight, Nine], A) :- A = x; A = o.
get_winner_name([One, Two, Three, A, A, A, Seven, Eight, Nine], A) :- A = x; A = o.
get_winner_name([One, Two, Three, Four, Five, Six, A, A, A], A) :- A = x; A = o.
get_winner_name([A, Two, Three, A, Five, Six, A, Eight, Nine], A) :- A = x; A = o.
get_winner_name([One, A, Three, Four, A, Six, Seven, A, Nine], A) :- A = x; A = o.
get_winner_name([One, Two, A, Four, Five, A, Seven, Eight, A], A) :- A = x; A = o.
get_winner_name([A, Two, Three, Four, A, Six, Seven, Eight, A], A) :- A = x; A = o.
get_winner_name([One, Two, A, Four, A, Six, A, Eight, Nine], A) :- A = x; A = o.
get_winner_name(_, noBody).
% Answer5.
play :- play([0, 0, 0, 0, 0, 0, 0, 0, 0]).
play(Grid) :- print_grid(Grid),
get_empty_cells(Grid, EmptyCells),
format('Your turn.. available cells are ~w: ',[EmptyCells]),
% game ends if player inserts non-valid value :(
read(XPosition), member(XPosition, EmptyCells),
insert_char_at_position(x, XPosition, Grid, NewGrid),
can_continue_playing(NewGrid), % print the winner name inside this function
get_empty_cells(NewGrid, NewEmptyCells),
random_member(OPosition, NewEmptyCells), % or simply: [OPosition|_] = NewEmptyCells.
insert_char_at_position(o, OPosition, NewGrid, NextRoundGrid),
can_continue_playing(NextRoundGrid), % print the winner name inside this function
play(NextRoundGrid).
play(_). % this helps not printing false when at the end of the game.
% helper rules
log(_).
% log(Msg) :- write(Msg), nl.
can_continue_playing(Grid) :- log('can_continue_playing'),
get_winner_name(Grid, WinnerName),
(WinnerName = x -> write('You Win'), nl, !, fail; true),
(WinnerName = o -> write('You Lose'), nl, !, fail; true).
EOL
Footnotes
-
10 Marks out of 18 ↩