How to findall for universal facts in prolog? - prolog

In Prolog I can write
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).
And then write
?- findall(X,descend(martha,X),Z).
and get four solutions
Z = [charlotte,caroline,laura,rose]
But if I then add an universal fact
likes(X,pomegranate).
and try
?- findall(X,likes(X, pomegranate),Z).
I get:
Z = [_G17].
What is that _G17?
What do I need to change to get essentially all variables? ( since likes(X,pomegranate) should mean everything likes pomegranate... right?) :
Z = [martha,charlotte,caroline,laura,rose]

Two solutions. The clean solution is to have a table that lists all "things" in the universe you describe:
person(martha).
person(charlotte).
% etc
And then your "likes" would be rather:
person_likes(P, pomegranate) :-
person(P).
You can also try to hack around it:
person(P) :- child(P, _).
person(P) :- child(_, P).
But this is... unsatisfactory? Think about a relational database: you would have two tables:
CREATE TABLE person (
id INTEGER PRIMARY KEY, -- usually autogenerated
name TEXT NOT NULL
);
CREATE TABLE parent_child (
parent_id INTEGER NOT NULL,
child_id INTEGER NOT NULL,
FOREIGN KEY parent_id REFERENCES person(id),
FOREIGN KEY child_id REFERENCES person(id)
);
The only reason, as far as I am aware, why you don't do the exact same thing in Prolog, is that most introductory tutorials are trying to seduce you and avoid going into such details. And of course the Prolog "database" is not a true relational database (for example, argument position does matter!).
TL;DR You can't avoid thinking about Prolog's resolution strategy when using Prolog.

Related

Visual Prolog 5.2 The variable is not bound in this clause

I have "in" predicate that goes through "people". But the X doesnt get a "person" and returns false, as i understood. Test goal and Run throw the same error
domains
person = c(name, nationality)
people = person*
name, nationality = symbol
predicates
name(person, name)
nationality (person, nationality)
in(person, people)
solve
clauses
name(c(N,_), N).
nationality (c(_,T), T).
in(X,[X,_,_,_]). %Error here
in(X,[_,X,_,_]).
in(X,[_,_,X,_]).
in(X,[_,_,_,X]).
solve :- in(C1, People), name(C1, brown),
in(C2, People), name(C2, clemens),
in(C3, People), name(C3, grifit),
in(C4, People), name(C4, grin),
in(C5, People), nationality(C5, canadian),
in(C6, People), nationality(C6, american),
in(C7, People), nationality(C7, australian),
in(C8, People), nationality(C8, british),
not(nationality(C1, british)),
not(nationality(C2, british)),
not(nationality(C3, american)),
not(nationality(C3, australian)),
not(nationality(C1, australian)),
not(nationality(C4, australian)),
write(People), nl.
goal
solve.
I expect X to be bound to the person (people item). What am I doing wrong?

Sort a list in Prolog

The fact base stores exam schedule information in the form: exam ('group', 'subject', 'teacher', date). Write a program that allows you to select exam information for the group / teacher, sorting it by date.
So, I already know how to get data from base depending on what you want (by Group number or by teacher name):
exam('426-1', 'PROGRAMMING', 'John', 08-12-2019).
exam('426-1', 'MATH', 'John', 18-12-2019).
exam('426-3', 'ENG', 'Gabe', 16-12-2019).
exam('426-4', 'LITERATURE', 'Homer', 11-12-2019).
findByGroup(Number, Exams) :-
findall([A,B|C], exam(Number, A,B,C), Exams).
findByTeacher(Name, Exams) :-
findall([A,B|C], exam(A, B, Name,C), Exams).
Here's the input
findByGroup('426-1', X).
And here's the output
X = [['PROGRAMMING', 'John'|8-12-2019], ['MATH', 'John'|18-12-2019]]
But I have no idea how should I sort my output by date. For example, how can I sort the data by ascending of the date?
Example:
Before X = [['MATH', 'John'|18-12-2019], ['PROGRAMMING', 'John'|8-12-2019]]
After X = [['PROGRAMMING', 'John'|8-12-2019], ['MATH', 'John'|18-12-2019]]
There are a couple of things that need to be considered:
The date format needs to be in a format that sorts chronologically
The information for each exam item needs to be organized into a term that will sort based upon the date
For the date format, you are using a - functor with integer arguments. This is Ok. To make it sortable chronologically, the order needs to be year-month-day. You can either change your original data to be in that form, or translate to the sortable form before you do your sorting.
Regarding the exam data record, if you create a record formatted as a term with the date as the first argument, then sorting the records will sort by date. For example, any of the following terms will sort by date in Prolog:
exam(Date, Number, A, B)
[Date, Number, A, B]
...etc...
Now we apply this to your problem. Sorting can be done using setof/3 instead of findall/3 if your date is formatted in a sortable form:
% exam(Number, Class, Name, Date)
exam('426-1', 'PROGRAMMING', 'John', 2019-12-08).
exam('426-1', 'MATH', 'John', 2019-12-18).
exam('426-3', 'ENG', 'Gabe', 2019-12-16).
exam('426-4', 'LITERATURE', 'Homer', 2019-12-11).
findByGroup(Number, Exams) :-
setof([Date, Number, Class, Name], exam(Number, Class, Name, Date), Exams).
findByTeacher(Name, Exams) :-
setof([Date, Number, Class, Name], exam(Number, Class, Name, Date), Exams).
This will provide terms with attributes in a slightly different order: [Date, Number, Class, Name]. You can either just work with that, or you could easily change it:
reformat_exam_list([Y-M-D, Number, Class, Name], [Number, Class, Name, D-M-Y]).
And then call maplist(reformat_exam_list, Exams, ReformattedExams).
If you can't change the date format in your original facts, you can pre-process them as part of your setof/3 call.
reformat_sorted_exam(exam(Y-M-D, Number, Class, Name), exam(Number, Class, Name, D-M-Y)).
findByGroup(Number, Exams) :-
setof(exam(Y-M-D, Number, Class, Name), exam(Number, Class, Name, D-M-Y), ExamList),
maplist(reformat_sorted_exam, SortedExams, Exams).
Here I also used an exam structure instead of a list to represent an exam. You get the idea...

Retrieve data from database without printing

How do I retrieve data from the database to use it in a condition, but I don't want to print it the console.
Problem I am doing is to retrieve a child from a database whose parents age differ by 15 years.
This is the code I am using which works and prints the year of both parents.
family(person(_,_,date(_,_,Year1),_),
person(_,_,date(_,_,Year2),_),
[person(Name,Surname,_,_)|Y]), abs(Year1-Year2) >= 15.
Define a predicate rule (in a source file) using the query as its body. For example:
child_with_parents_age_gap(Gap, Name, Surname) :-
family(
person(_,_,date(_,_,Year1),_),
person(_,_,date(_,_,Year2),_),
[person(Name,Surname,_,_)| _]
),
abs(Year1-Year2) >= Gap.

Prolog book database (how to get results grouped by author?)

I have a the following database of facts like:
...
book(title1, author1),
book(title2, author2),
book(title3, author3),
book(title4, author1),
book(title5, author2),
...
I need to write a prolog query or rule to get titles grouped by the author (author1, author2...). I can't change order of facts in database.
For now I have only query looking like this:
book(T1, A), book(T2, A), T1 \= T2.
but it returns same thing few times
thanks in advance

A prolog assignment which uses lists

I have an assignment that's driving me crazy.... Here's the assignment:
The following contacts for people in a company are kept as facts as the followings:
phones(joao, [home/217777777, mobile91/917777777]).
phones(maria, [work/218888888, mobile93/938888888, mobile91/918888888]).
phones(jose, [home/213333333, mobile91/914444444]).
Define predicates in Prolog that allow to answer the following question:
1) Which are the possible contacts availiable if you want to phone to any person in a group?
For example:
?- all_contacts([joao,jose],ListOfContacts).
ListOfContactss = [home/217777777, mobile91/917777777, home/213333333,mobile91/914444444]
What I've done is this:
all_contacts([],_):-[].
all_contactos([X|T],LIST):-phones(X,LIST),all_contacts(T,Y).
However if I do:
?- all_contactos([jose,maria],LIST_CONTACTS).
LIST_CONTACTS = [casa/213333333, movel91/914444444].
That is, I only get the contacts for the first person on the list.
Thanks for any help.
You can use the meta predicate findall to collect the phones for all contacts of an input list:
all_contacts(People, Contacts):-
findall(Contact,
( member(Person, People), % For each person
phones(Person, LContact), % get the list of contacts of person
member(Contact, LContact) % collect each contact from that list
), Contacts). % Finally gather all contacts in one list
For your example this yields:
all_contacts([jose,maria],LIST_CONTACTS).
LIST_CONTACTS = [home/213333333, mobile91/914444444, work/218888888, mobile93/938888888, mobile91/918888888].
If you don't want to use builtin predicate you can achieve the same thing using recursive predicates by iterating over all the people and then over every phone for each person, and building a list of contacts along the recursion:
all_contacts(People, Contacts):-
all_contacts(People, [], Contacts).
all_contacts([], Contacts, Contacts).
all_contacts([Person|People], Contacts, NContacts):-
phones(Person, LContacts),
all_contacts_person(LContacts, Contacts, MContacts),
all_contacts(People, MContacts, NContacts).
all_contacts_person([], Contacts, Contacts).
all_contacts_person([Contact|LContacts], Contacts, MContacts):-
all_contacts_person(LContacts, [Contact|Contacts], MContacts).

Resources