I want to sum in SWI-Prolog depending on certain user answers - prolog

I need to sum in a variable depending on certain user answers and I'm starting to know Prolog syntax and paradigm.
Right now, I can read user data, and print it too, but I lack a way to accumulate the results, because right now the results are inconsistent.
What I have now is:
inicio :-
write('¿You have overweight yes/no?'),
read(R1),
write('¿Are you a smoker yes/no?'),
read(R2),
write('¿Do you have some direct relative with diabetes?'),
read(R3),
Risk is 0,
( R1 = yes -> Risk is Risk + 2 ; Risk is Risk + 0 ),
imprimir(['The result is ', Risk]),
( R2 = yes -> Risk is Risk + 1 ; Risk is Risk + 0 ),
imprimir(['The result is ', Risk]),
( R3 = yes -> Risk is Risk + 3 ; Risk is Risk + 0 ),
imprimir(['The result is ', Risk]).
imprimir([]).
imprimir([Term| Terms]) :-
write(Term),
imprimir(Terms).

I'm going to show you a fundamentally different way of approaching this program that leverages Prolog a little better. First, let's make a table of the penalties. Making tables for your program's configuration is often a useful thing to do:
risk_penalty(overweight, 2).
risk_penalty(smoker, 1).
risk_penalty(diabetes, 3).
Now that we have a uniform way of thinking about the problem, let's see if we can make a uniform way of getting information from the user. Let's use the dynamic store to keep track of what the user has told us, because it will simplify querying later:
:- dynamic risk/2.
ask(Prompt, Fact) :-
format('~a [yes/no]> ', [Prompt]),
read(Response),
assertz(risk(Fact, Response)).
Now we have a little predicate we can use to interview the user. This kind of print-read-assert function is pretty common in small expert systems like yours, because it helps you separate the logic of the system from its front-end. When you do ask('Do you have X?', has_x), the dynamic store will either receive risk(has_x, yes) or risk(has_x, no) depending on which the user has entered. It also gives you a natural place to make the user input more robust, by checking it and re-asking if you get something weird.
Now we can do your initial loop a little more cleanly:
inicio :-
ask('Are you overweight?', overweight),
ask('Are you a smoker?', smoker),
ask('Do you have some direct relative with diabetes?', diabetes).
This just does the interview portion. Now if you run through it once, say answering "yes", "no", "yes", then the database will contain these facts:
?- risk(Factor, Response).
Factor = overweight,
Response = yes ;
Factor = smoker,
Response = no ;
Factor = diabetes,
Response = yes.
What we need to do now is select out "yes" factors and then look up their penalties and add them up. To do this, we can use findall/3, which takes a Template, a Goal, and gives back a result list:
?- findall(risk(Factor, Response), risk(Factor, Response), Responses).
Responses = [risk(overweight, yes), risk(smoker, no), risk(diabetes, yes)].
As you can see, I used the same template and goal here, just to see all the results, but we can put "yes" in to filter it down to just the risk factors we care about:
?- findall(risk(Factor), risk(Factor, yes), Responses).
Responses = [risk(overweight), risk(diabetes)].
Now you can see that the Template (first argument) is just some arbitrary structure populated with the variables that findall/3 found by running Goal, the second argument. So we could also just obtain the list of penalty values, if we look them up inside the Goal query. Like this:
?- findall(Penalty, %% <- template
(risk(Factor, yes), risk_penalty(Factor, Penalty)), %% <- goal
Penalties). %% <- result
Penalties = [2, 3].
We can then follow this with just sumlist/2 to add everything up:
?- findall(Penalty,
(risk(Factor, yes), risk_penalty(Factor, Penalty)),
Penalties),
sumlist(Responsa, Score).
Responsa = [2, 3],
Score = 5.
Now we can finish the inicio/0 predicate:
inicio :-
retractall(risk(_, _)),
ask('Are you overweight?', overweight),
ask('Are you a smoker?', smoker),
ask('Do you have some direct relative with diabetes?', diabetes)
findall(Penalty,
(risk(Factor, yes), risk_penalty(Factor, Penalty)), Penalties),
sumlist(Penalties, Score),
format('The result is ~a~n', [Score]).
This now looks like this when run:
?- inicio.
Are you overweight? [yes/no]> yes.
Are you a smoker? [yes/no]> |: no.
Do you have some direct relative with diabetes? [yes/no]> |: yes.
The result is 5
true.
I hope you find the result pleasing to look at, much less procedural, and easier to modify and maintain.

Related

How to join rules and print out outputs in prolog

I have list of facts as follows.
items(itemId('P01'),prodName('Pots'),stockQty(50),price(8200)).
items(itemId('P02'),prodName('Pans'),stockQty(50),price(400)).
items(itemId('P03'),prodName('Spoons'),stockQty(50),price(200)).
items(itemId('P04'),prodName('Forks'),stockQty(50),price(120)).
items(itemId('P05'),prodName('Kettles'),stockQty(50),price(500)).
items(itemId('P06'),prodName('Plates'),stockQty(50),price(60)).
How to print on the console something like the following when a command like print_all_products. is given.
..............
Available Products
..........
Name Qty
Pots 60
Pans 50
Spoons 40
..................
The Name and Qty must be properly formatted in a tabular structure.
I tried using forall and foreach I am unsuccessful in generating what i need.
Answer with more details is posted here.
Below is the code so that this is not a link only answer.
items(itemId('P01'),prodName('Pots'),stockOty(50),price(8200)).
items(itemId('P02'),prodName('Pans'),stockOty(50),price(400)).
items(itemId('P03'),prodName('Spoons'),stockOty(50),price(200)).
items(itemId('P04'),prodName('Forks'),stockOty(50),price(120)).
items(itemId('P05'),prodName('Kettles'),stockOty(50),price(500)).
items(itemId('P06'),prodName('Plates'),stockOty(50),price(60)).
header("\n........................\nAvailable Products\n........................\nName Qty\n").
footer("........................\n").
spaces(Length,Spaces) :-
length(List,Length),
maplist([_,0'\s]>>true,List,Codes),
string_codes(Spaces,Codes).
padded_string(String,Width,Padded_string) :-
string_length(String,String_length),
Padding_length is Width - String_length,
spaces(Padding_length,Padding),
atom_concat(String,Padding,Padded_string).
format_detail_line(item(Name,Quantity),width(Name_width),Formatted_item) :-
padded_string(Name,Name_width,Padded_name),
atom_concat(Padded_name,Quantity,Formatted_item).
add_detail_line(width(Name_Width),Item,Lines0,Lines) :-
format_detail_line(Item,width(Name_Width),Formatted_item),
atomic_list_concat([Lines0,Formatted_item,"\n"], Lines).
items_detail(Detail) :-
findall(item(Name,Quantity),items(_,prodName(Name),stockOty(Quantity),_),Items),
aggregate_all(max(Width),Width,(items(_,prodName(Name),_,_),string_length(Name,Width)),Name_Width),
Name_field_width is Name_Width + 1,
foldl(add_detail_line(width(Name_field_width)),Items,"",Detail).
print_all_products(Report) :-
header(Header),
items_detail(Detail),
footer(Footer),
atomic_list_concat([Header,Detail,Footer], Report).
print_all_products :-
print_all_products(Report),
write(Report).
:- begin_tests(formatted_report).
test(1) :-
print_all_products(Report),
with_output_to(atom(Atom),write(Report)),
assertion( Atom == '\n........................\nAvailable Products\n........................\nName Qty\nPots 50\nPans 50\nSpoons 50\nForks 50\nKettles 50\nPlates 50\n........................\n' ).
:- end_tests(formatted_report).
Note: The answer given by Peter is the customary way to do the formatting, but as I noted, that drives me nuts. Even so, that is the way I would do it in a production environment.
I gave this answer because the OP noted they were looking for a way to do it using predicates like forall/2 or foreach/2. Granted neither of them is used in this answer but the intent of using a more functional approach is used.
If the question was more open ended I would have given a answer using DCGs.
format/2 ... for putting things in neat columns, use ~|, ~t, ~+.
~| sets a tab to "here", ~t inserts fill characters, ~+ advances the tab beyond the last "here" (~|) and distributes the fill characters. So,
format("(~|~`.t~d~5+)~n", [123])
produces (..123) -- the format string right-justifies the number with .s in a width of 5, surrounded by parentheses.
You are asking for SQL-style tabular output and yes, that should be in the language as basic predicate set since when Reagan was prez. I don't know what's going on. It's probably out there in a library though (but where is the library?)
Meanwhile, here is the "failure driven-loop" using some of my personal toolbox goodies, but it uses SWI Prolog:
In file printthem.pl:
:- use_module(library('heavycarbon/strings/string_of_spaces.pl')).
:- use_module(library('heavycarbon/strings/string_overwriting.pl')).
items(itemId('P01'),prodName('Pots'),stockOty(50),price(8200)).
items(itemId('P02'),prodName('Pans'),stockOty(50),price(400)).
items(itemId('P03'),prodName('Spoons'),stockOty(50),price(200)).
items(itemId('P04'),prodName('Forks'),stockOty(50),price(120)).
items(itemId('P05'),prodName('Kettles'),stockOty(50),price(500)).
items(itemId('P06'),prodName('Plates'),stockOty(50),price(60)).
printthem :-
% ideally these should be built by getting max(length) over a column - hardcode for now!
string_of_spaces(5,SpacesId),
string_of_spaces(10,SpacesName),
string_of_spaces(4,SpacesQuant),
string_of_spaces(6,SpacesPrice),
% begin failure-driven loop!
items(itemId(Id),prodName(Name),stockOty(Quant),price(Price)), % backtrack over this until no more solutions
% transform data into string; see predicate format/2;
% capture output instead of letting it escape to STDOUT
with_output_to(string(TxtId),format("~q",[Id])),
with_output_to(string(TxtName),format("~q",[Name])),
with_output_to(string(TxtQuant),format("~d",[Quant])),
with_output_to(string(TxtPrice),format("~d",[Price])),
% formatting consist in overwriting the space string with the data-carrying string
string_overwriting(SpacesId,TxtId, 1,TxtIdFinal),
string_overwriting(SpacesName,TxtName, 1,TxtNameFinal),
string_overwriting(SpacesQuant,TxtQuant, 1,TxtQuantFinal),
string_overwriting(SpacesPrice,TxtPrice, 1,TxtPriceFinal),
% output the line
format("~s~s~s~s\n",[TxtIdFinal,TxtNameFinal,TxtQuantFinal,TxtPriceFinal]),
% close the loop
fail.
The above is just an ébauche. Improvements are possible in several distinct directions.
The modules loaded via
:- use_module(library('heavycarbon/strings/string_of_spaces.pl')).
:- use_module(library('heavycarbon/strings/string_overwriting.pl')).
can be obtained from GitHub here. You will have to grab several files and arrange them appropriately. Read the script load_and_test_script.pl. Don't mind the mess, this is work in progress.
If everything has been set up correctly:
?- [printthem].
true.
?- printthem.
'P01' 'Pots' 50 8200
'P02' 'Pans' 50 400
'P03' 'Spoons' 50 200
'P04' 'Forks' 50 120
'P05' 'Kettles' 50 500
'P06' 'Plates' 50 60
false.

Compile time testfor 'atoms'

Completely new to prolog. Interesting journey so far in trying to change how I think, so appreciate any help here.
I am trying to assert facts for a pre-defined set of names. For example, assume I have a a set of people [alice, bob, ...] in one file. I would like to assert facts about these folks in other files, but want to make sure that these folks exist and that is checked when the facts are loaded/compiled(?).
For example, assume I don't have 'chuck' in the list and I make an assertion:
user: swipl app.pl
?- full_name(chuck, "Charlie Steel").
should result in an error.
What is the best way I can do this?
So, here's the code I came up with:
person(deborah).
person(tony).
read_my_file(Filename) :-
open(Filename, read, In),
read_my_file1(In),
close(In).
read_my_file1(In) :-
read(In, Term),
( Term == end_of_file
-> true
; assert_or_abort(Term),
read_my_file1(In)
).
assert_or_abort(Term) :-
( full_name(Person, Name) = Term
-> ( person(Person)
-> assertz(full_name(Person, Name))
; format(user, '~w is not a person I recognize~n', [Person])
)
; format(user, '~w is not a term I know how to parse~n', [Term])
).
The trick here is using read/2 to obtain a Prolog term from the stream, and then doing some deterministic tests of it, hence the nested conditional structure inside assert_or_abort/1. Supposing you have an input file that looks like this:
full_name(deborah, 'Deborah Ismyname').
full_name(chuck, 'Charlie Steel').
full_name(this, has, too, many, arguments).
squant.
You get this output:
?- read_my_file('foo.txt').
chuck is not a person I recognize
full_name(this,has,too,many,arguments) is not a term I know how to parse
squant is not a term I know how to parse
true.
?- full_name(X,Y).
X = deborah,
Y = 'Deborah Ismyname'.

Obtain intermediate variables assignments

I am trying to use Prolog to represent state of a room.
I have a set of rules and a set of facts, but sometimes some of the facts are
not defined. For instance, temperature in a room can decrease due to either
cooling or opening a window, but sometimes I do not have a window sensor.
% Rules
temperature_trend(decrease) :-
cooling(on).
temperature_trend(decrease) :-
window(open).
% Facts
cooling(off).
%window(close). % Unknown, I do not have a window sensor
% Main
main() :-
temperature_trend(decrease).
If I run this program I would get an undefined procedure error. I can deal with
this by explicitly setting the window
status to "anything" with window(W). (I programmatically prepare the Prolog
source, so this is quite easy).
Now the query temperature_trend(decrease)
would succeed because window(W) would lead to window(open). However, in this
case I want to know that W = open.
Is there a way to return the variable assignments for this fact? Or maybe am I approaching the problem in the wrong way?
Note that the
rule tree could be arbitrarily deep, for instance I could add a new rule next_temperature(lower) :- temperature_trend(decrease). and I still
want to know that next_temperature(lower) succeeds only by setting W = open. Terms are also more complex
because they also have a time index (T = 232).
Maybe one option would be to return a list of assignments, which would be empty
if all facts were known.
Write a meta-interpreter that gives you what is true, e.g.,
prove(Goal, True) :-
phrase(prove(Goal), True).
prove(true) -->
!.
prove((A,B)) -->
!,
prove(A),
prove(B).
prove((A;B)) -->
!,
( prove(A)
; prove(B)
).
prove(Fact) -->
[Fact],
{ clause(Fact, Body) },
prove(Body).
Now, given window(_), we get:
?- prove(temperature_trend(decrease), L).
L = [temperature_trend(decrease), window(open)].
Lots of variations are possible!

Subtracting variables from a Prolog knowledge base [duplicate]

Trying to create a predicate (timePeriod/2) that calculates the time period between two dates for a specific fact. I've managed to do this by myself, but face issues when 'other answers' exist in the same list (i.e. easier to explain with examples).
I have the following knowledge-base facts;
popStar('Jackson',1987,1991).
popStar('Jackson',1992,1996).
popStar('Michaels',1996,2000).
popStar('Newcastle',2000,2007).
popStar('Bowie',2008,2010).
And the following function, calculates the time between dates for a specific fact (as per below).
Predicate (timePeriod/2) -
timePeriod(PS,X) :-
bagof((Name,Start,End),popStar(Name,Start,End),PSs),X is End-Start+1)
Using Bowie as an example; it returns X=3 (which is correct).
However, when there is repetition in the list, with more than one answer available, the predicate just states 'false'. Using the facts 'Jackson' as an example, I want to be able to calculate both of the time periods for both facts; at the same time.
So, if the predicate would work for both of the Jackson facts, the predicate timePeriod would state X=10.
Would really appreciate if anyone could suggest what to change in order for this to work correctly.
Thanks.
You probably don't quite understand what foreach/3 does. I don't think I fully understand foreach/3 either. I know for sure that it is not the same as say:
for each x in xs:
do foo(x)
Another thing: "tuples" in Prolog are not what you might expect, coming from a language like Python or Haskell. This: (a,b,c) is actually this: ','(a,','(b,c)). Much better is to use a flat term, the generic form would be triple(a,b,c). For a pair, the idiom is First-Second.
So, you can simplify your call to bagof/3 to this:
?- bagof(From-To, pop_star(Name, Start, End), Ts).
Name = 'Bowie',
Ts = [2008-2010] ;
Name = 'Jackson',
Ts = [1987-1991, 1992-1996] ;
Name = 'Michaels',
Ts = [1996-2000] ;
Name = 'Newcastle',
Ts = [2000-2007].
Once you have a list as above, you need to sum the differences, which would be maybe something like:
periods_total(Ps, T) :-
maplist(period_length, Ps, Ls),
sum_list(Ls, T).
period_length(From-To, Length) :-
Length is To - From + 1.
And then you can query like this:
?- bagof(From-To, pop_star('Jackson', From, To), Ps), periods_total(Ps, T).
Ps = [1987-1991, 1992-1996],
T = 10.
?- bagof(From-To, pop_star(Name, From, To), Ps), periods_total(Ps, T).
Name = 'Bowie',
Ps = [2008-2010],
T = 3 ;
Name = 'Jackson',
Ps = [1987-1991, 1992-1996],
T = 10 ;
Name = 'Michaels',
Ps = [1996-2000],
T = 5 ;
Name = 'Newcastle',
Ps = [2000-2007],
T = 8.
SWI-Prolog has a nice library to handle aggregation: it builds upon standard 'all solutions' predicates like findall/3,setof/3,bagof/3, so you should first grasp the basic of these (as Boris explained in his answer). With the library, a single query solves your problem:
timePeriod(PS,X) :-
aggregate(sum(P), B^E^(popStar(PS,B,E),P is E-B+1), X).

Prolog error in loop

I would need help about Prolog.
I posted my code, the problem is that i do not obtain the expected result.
I want planning actions for moving on table all blocks until is possible. To do this I prompt :
?- do(while(some(x, block(x) & -onTable(x)),pi(x,putOnTable(x))),s0,S).
I expect to see a response like :
S = do(putOnTable(e), do(putOnTable(b), do(putOnTable(c), s0)))
but Prolog returns "false" only. Someone can help me??
% Golog interpreter
%:- [golog_swi].
:- discontiguous clear/2, on/3, onTable/2.
:- op(800,xfy,[&]).
do(E,S,do(E,S)):- primitive_action(E),poss(a,S).
% Primitive Action Declarations.
primitive_action(putOn(_,_)).
primitive_action(putOnTable(_)).
poss(putOn(X,Y),S) :- clear(X,S), clear(Y,S), \+ on(X,Y,S), \+ X=Y.
poss(putOnTable(X),S):- clear(X,S), \+(onTable(X,S)).
% Successor State Axioms.
on(X,Y,do(A,S)):- A = putOn(X,Y); on(X,Y,S), \+ (A = putOnTable(X); A = putOn(X,_)).
onTable(X,do(A,S)) :- A = putOnTable(X); onTable(X,S), \+ A= putOn(X,_).
clear(X,do(A,S)) :- on(Y,X,S), (A = putOn(Y,_) ; A = putOnTable(Y)); clear(X,S), \+ A = putOn(_,X).
% Restore suppressed situation arguments
restoreSitArg(onTable(X),S,onTable(X,S)).
restoreSitArg(on(X,Y),S,on(X,Y,S)).
restoreSitArg(clear(X),S,clear(X,S)).
block(X):- member(X,[a,b,c,d,e]).
% iniTial COndition
onTable(a,s0).
on(b,a,s0).
on(c,b,s0).
clear(c,s0).
onTable(d,s0).
on(e,d,s0).
clear(3,s0).
thank you!!!
Your predicate do/3 cannot succeed because the goal primitive_action/1 will fail with your query.
Currently, while/2 is not described in primitive_action/1 and it seems it is missing also from your program. So you need to extend primitive_action/1 by further facts, or add a new rule to do/3. And in addition to that you need to describe what while/2 means.
This question is actually about Golog. Your mistake is pretty mundane: you didn't copy the Golog interpreter code into your source file/directory.
Golog defines a number of high-level programming constructs, including while-loops and non-deterministic picks (pi), used here. I'm sure you don't want to reinvent Golog, so just go and get it. I'm assuming that your question is part of an assignment of sorts, and your teacher probably pointed you to the Golog interpreter. Otherwise, you can always find it on the pages of the cognitive robotics group at the Univ. of Toronto: http://www.cs.toronto.edu/cogrobo/main/systems/index.html

Resources