How to assert new data dynamically in Prolog - prolog

I have previously define a few facts dynamically as below.
% declare dynamic facts
:- dynamic title/2.
:- dynamic author/2.
:- dynamic publisher/2.
:- dynamic price/2.
:- dynamic call_number/2.
:- dynamic edition/2.
:- dynamic data_disk/2.
and assert these facts every time the program runs
:- assert(title(book1, 'Elementary Statistics')).
:- assert(title(book2, 'Statistics for Engineers')).
:- assert(title(book3, 'Statistics for Engineers and Scientists')).
:- assert(title(book4, 'IT in Language Learning')).
:- assert(author(book1, 'Patricia Wilson')).
:- assert(author(book2, 'James Mori')).
:- assert(author(book3, 'James Mori')).
:- assert(author(book4, 'O Ivan')).
:- assert(publisher(book1, 'Addison Wesley')).
:- assert(publisher(book2, 'World Scientific')).
:- assert(publisher(book3, 'World Scientific')).
:- assert(publisher(book4, 'Universal Press')).
:- assert(price(book1, 75)).
:- assert(price(book2, 125)).
:- assert(price(book3, 125)).
:- assert(price(book4, 5)).
:- assert(call_number(book1, 'QA373')).
:- assert(call_number(book2, 'QA673')).
:- assert(call_number(book3, 'QA674')).
:- assert(call_number(book4, 'QA007')).
:- assert(edition(book1, 1)).
:- assert(edition(book2, 3)).
:- assert(edition(book3, 2)).
:- assert(edition(book4, 1)).
:- assert(data_disk(book1, 'No')).
:- assert(data_disk(book2, 'Yes')).
:- assert(data_disk(book3, 'Yes')).
:- assert(data_disk(book4, 'No')).
As you can see the facts are in a certain order
book1
book2
book3
book4
How can I get the last X, where X is bookX, and increment by 1 so that the new book to be inserted will always be (X+1)?

You found one solution (i.e., count the existing facts, and add 1), which works but has one major drawback: It makes the run time of adding a single new fact proportional to the number of already asserted facts. This means that asserting a series of N facts takes time proportional to N2.
Ideally, we would like to have a situation where asserting a single fact is in 𝒪(1), and asserting N facts is thus in 𝒪(N).
One way to achieve this is to reconsider your initial representation of books.
For example, suppose that you present your books like this (some data omitted for brevity):
book([title('Elementary Statistics'),
author('Patricia Wilson'),
price(75)]).
book([title('Statistics for Engineers'),
author('James Mori'),
publisher('World Scientific')]).
Note that this representation allows us to omit fields that are only present in some of the books. Other representations would also make sense.
We can easily fetch all these facts with findall/3:
?- findall(Book, book(Book), Books).
That's linear in the number of such facts.
Further, let us define assert_book_/3 as follows:
assert_book_(Book, N0, N) :-
memberchk(title(Title), Book),
memberchk(author(Author), Book),
assertz(title(N0,Title)),
assertz(author(N0,Author)),
N #= N0 + 1.
For the sake of example, I am focusing on the title and author. I leave extending this as an exercise.
The arguments of this predicate are:
the book to be asserted, represented as a list of attributes
the current index N0
the next index N1, which is simply one greater than N0.
Now the main point: These arguments are in a suitable order to fold the predicate over a list of books, using the meta-predicate foldl/4:
?- findall(Book, book(Book), Books),
foldl(assert_book_, Books, 1, _).
After running this query, we have:
?- title(N, T).
N = 1,
T = 'Elementary Statistics' ;
N = 2,
T = 'Statistics for Engineers'.
And similar facts for author/2 in the database:
?- author(N, T).
N = 1,
T = 'Patricia Wilson' ;
N = 2,
T = 'James Mori'.
Thus, we have used foldl/4 to implicitly keep track of the running index we need, and achieved a solution that has the desired running time.
Note that there is also a wise-cracking solution for your task:
assert_title(Book, Title) :-
atom_concat(book, N0, Book),
atom_number(N0, N),
assertz(title(N, Title)).
This is obviously not what you looking for, but would work for the example you show, if you use for example:
:- assert_title(book1, 'Elementary Statistics').
:- assert_title(book2, 'Statistics for Engineers').
Now we have again:
?- title(N, Title).
N = 1,
Title = 'Elementary Statistics' ;
N = 2,
Title = 'Statistics for Engineers'.
The joke here is that you have actually entered the running index already, and we can use atom_concat/3 to obtain it:
?- atom_concat(book, N0, book1),
atom_number(N0, N).
N0 = '1',
N = 1.
;-)

I cleared my mind at the nearest Starbucks and came up with the simplest answer.
add_book :-
aggregate_all(count, title(_,_), Count),
NewCount is Count + 1,
atom_concat('book', NewCount, NewBook).
The aggregate_all function will count number of title predicates that's available in my knowledge base and some calculation will be performed.
I am open to better suggestion though, do reply if you have a better approach.

Related

How to improve this code that looks for a specific number in a list?

I'm writing prolog code that finds a certain number; a number is the right number if it's between 0 and 9 and not present in a given list. To do this I wrote a predicate number/3 that has the possible numbers as the first argument, the list in which the Rightnumber cannot be present and the mystery RightNumber as third argument:
number([XH|XT], [H|T], RightNumber):-
member(XH, [H|T]), !,
number(XT, [H|T], RightNumber).
number([XH|_], [H|T], XH):-
\+ member(XH, [H|T]).
so this code basically says that if the Head of the possible numbers list is already a member of the second list, to cut of the head and continue in recursion with the tail.
If the element is not present in the second list, the second clause triggers and tells prolog that that number is the RightNumber. It's okay that it only gives the first number that is possible, that's how I want to use it.
This code works in theory, but I was wondering if there's a better way to write it down? I'm using this predicate in another predicate later on in my code and it doesn't work as part of that. I think it's only reading the first clause, not the second and fails as a result.
Does anybody have an idea that might improve my code?
sample queries:
?- number([0,1,2,3,4,5,6,7,8,9], [1,2], X).
X = 3
?- number([0,1,2,3,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,0], X).
X = 9
First, the code does not work. Consider:
?- number(Xs, Ys, N).
nontermination
This is obviously bad: For this so-called most general query, we expect to obtain answers, but Prolog does not give us any answer with this program!
So, I first suggest you eliminate all impurities from your program, and focus on a clean declarative description of what you want.
I give you a start:
good_number(N, Ls) :-
N in 0..9,
maplist(#\=(N), Ls).
This states that the relation is true if N is between 0 and 9, and N is different from any integer in Ls. See clpfd for more information about CLP(FD) constraints.
Importantly, this works in all directions. For example:
?- good_number(4, [1,2,3]).
true.
?- good_number(11, [1,2,3]).
false.
?- good_number(N, [1,2,3]).
N in 0\/4..9.
And also in the most general case:
?- good_number(N, Ls).
Ls = [],
N in 0..9 ;
Ls = [_2540],
N in 0..9,
N#\=_2540 ;
Ls = [_2750, _2756],
N in 0..9,
N#\=_2756,
N#\=_2750 .
This, with only two lines of code, we have implemented a very general relation.
Also see logical-purity for more information.
First of all, your predicate does not work, nor does it check all the required constraints (between 0 and 9 for instance).
Several things:
you unpack the second list [H|T], but you re-pack it when you call member(XH, [H|T]); instead you can use a list L (this however slightly alters the semantics of the predicate, but is more accurate towards the description);
you check twice member/2ship;
you do not check whether the value is a number between 0 and 9 (and an integer anyway).
A better approach is to construct a simple clause:
number(Ns, L, Number) :-
member(Number, Ns),
integer(Number),
0 =< Number,
Number =< 9,
\+ member(Number, L).
A problem that remains is that Number can be a variable. In that case integer(Number) will fail. In logic we would however expect that Prolog unifies it with a number. We can achieve this by using the between/3 predicate:
number(Ns, L, Number) :-
member(Number, Ns),
between(0, 9, Number),
\+ member(Number, L).
We can also use the Constraint Logic Programming over Finite Domains library and use the in/2 predicate:
:- use_module(library(clpfd)).
number(Ns, L, Number) :-
member(Number, Ns),
Number in 0..9,
\+ member(Number, L).
There are still other things that can go wrong. For instance we check non-membership with \+ member(Number, L). but in case L is not grounded, this will fail, instead of suggesting lists where none of the elements is equal to Number, we can use the meta-predicate maplist to construct lists and then call a predicate over every element. The predicate we want to call over every element is that that element is not equal to Number, so we can use:
:- use_module(library(clpfd)).
number(Ns, L, Number) :-
member(Number, Ns),
Number in 0..9,
maplist(#\=(Number), L).

CLP in Prolog involving consecutive sums in a list

Example of my CLP problem (this is a small part of a larger problem which uses the clpfd library):
For a list of length 5, a fact el_sum(Pos,N,Sum) specifies that the N consecutive elements starting from position Pos (index from 1) have sum equal to Sum. So if we have
el_sum(1,3,4).
el_sum(2,2,3).
el_sum(4,2,5).
Then [1,2,1,4,1] would work for this example since 1+2+1=4, 2+1=3, 4+1=5.
I'm struggling with how to even start using the el_sum's to find solutions with an input list [X1,X2,X3,X4,X5]. I'm thinking I should use findall but I'm not really getting anywhere.
(My actual problem is much bigger than this so I'm looking for a solution that doesn't just work for three facts and a small list).
Thanks!
You are mixing here the monotonic world of constraints with some non-monotonic quantification. Don't try to mix them too closely. Instead, first transform those facts into, say, a list of terms.
el_sums(Gs) :-
G = el_sum(_,_,_),
findall(G, G, Gs).
And then, only then, start with the constraint part that will now remain monotonic. So:
?- el_sums(Gs), length(L5,5), maplist(l5_(L5), Gs).
l5_(L5, el_sum(P, N, S)) :-
length([_|Pre], P),
length(Cs, N),
phrase((seq(Pre),seq(Cs),seq(_)), L5),
list_sum(Cs,S).
seq([]) --> [].
seq([E|Es]) --> [E], seq(Es).
Not sure this will help, I don't understand your workflow... from where the list do come ? Anyway
:- [library(clpfd)].
el_sum(Pos,N,Sum) :-
length(L, 5),
L ins 0..100,
el_sum(Pos,N,Sum,L),
label(L), writeln(L).
el_sum(P,N,Sum,L) :-
N #> 0,
M #= N-1,
Q #= P+1,
el_sum(Q,M,Sum1,L),
element(N,L,T),
Sum #= Sum1 + T.
el_sum(_P,0,0,_L).
yields
?- el_sum(1,2,3).
[0,3,0,0,0]
true ;
[0,3,0,0,1]
true ;
...

How do I skip an element that already have been printing prolog

Im trying to do an exercise in Prolog like this: i introduce this list [10,20, 10, 20, 30], and the program shows :
10 - 2 time; 20 - 2 times; 30 - 1 times.
Here is my code:
conta(_,[], 0).
conta(X, [X|T], N) :- conta(X,T, N2), N is N2 + 1 .
conta(X, [Y|T], N) :- X \= Y, conta(X,T,N).
aux([],[]).
aux([X|L],L1):-conta(X,L1,C),write(X),write(C), write('vezes'),aux(L,L1).
But the result is this:
10 - 2times 20 -2time 10-2times 20-2times 30-1 time
false.
He shows the element the number of time that the element is in the list.
Any help, Please!!
In your problem statement you are interspersing the pure relation with side-effects. While you might solve the problem in this manner you will see only very few of Prolog's interesting properties. Instead, try to formulate your problem as a pure relation. So imagine, you have it already implemented, and formulate some queries for it:
?- list_vezes([10,20, 10, 20, 30], [10-2,20-2,30-1]).
true.
The following solution counts and removes the corresponding elements. It has some n2 runtime.
list_vezes([], []).
list_vezes([E|Es1], [E-N|Vezes]) :-
n_occ(E, Es1,Es2, 1,N),
list_vezes(Es2, Vezes).
n_occ(_, [],[], N,N).
n_occ(E, [E|Es0],Es, N0,N) :-
N1 is N0+1,
n_occ(E, Es0,Es, N1,N).
n_occ(E, [F|Es0],[F|Es], N0,N) :-
dif(F,E),
n_occ(E, Es0,Es, N0,N).
In many Prolog systems prolog-dif is a built-in. See the link what to do if you do not have it.
Now, if you still want to print out the text you said, you might do this with this new list:
printitem(E-N) :-
writeq(E-N), write(' times\n').
compter_et_imprimer(L) :-
list_vezes(L, Vezes),
maplist(printitem, Vezes).

How do add degree of separation?

In Prolog, how would I make a rule that checks how many people come in one persons network and then query it on degree of separation?
For example, if in Facebook my name is John; and I have one friend Tom, and Tom has one friend Lucy, and Lucy has one friend Ben, and Ben has one friend Josh, and Josh has one friend Nancy.
I want to create a rule in Prolog that tests how many people are in my network and also tell Prolog to return names up to a given number of degree of separation.
e.g. if I query something like;
?- mynetwork(josh,2).
Prolog should return
John
Tom
Lucy
or
John is friends with Tom
Tom is friends with Lucy
Welcome to Prolog!
First you're going to need some facts:
friend(john, tom).
friend(tom, lucy).
friend(lucy, ben).
...
For simplicity, let's consider the case where friendships are directed: I can friend you, but that doesn't mean you friend me.
Let's say we're friends of degree 1 if I have friended you. That would look like this:
network(Person, 1, Friend) :- friend(Person, Friend).
Now the inductive case is one in which we've found a friend through a friend. That's going to look like this:
network(Person, N1, FoaF) :-
N1 > 1,
N0 is N1-1,
network(Person, N0, Friend),
network(Friend, 1, FoaF).
Using is/2 you can be sure the predicate will be ill-behaved. For instance, if you omit the > 1 constraint, you will be able to ask questions and get N back that you won't if you include it. But you'll also get errors about being out of local stack. So if you can afford to, bring in clpfd now:
:- use_module(library(clpfd)).
network(Person, 1, Friend) :- friend(Person, Friend).
network(Person, N1, FoaF) :-
N1 #> 0,
N0 #= N1-1,
network(Person, N0, Friend),
network(Friend, 1, FoaF).
This should work for all input cases you care to try, though it still has no way to know when you're out of friendship levels.
?- network(john, N, X).
N = 1,
X = tom ;
N = 2,
X = lucy ;
N = 3,
X = ben ;
^CAction (h for help) ? abort
% Execution Aborted
?- network(john, 3, X).
X = ben ;
false.
?- network(john, 2, X).
X = lucy ;
false.
Edit Let me answer your questions out of order.
Where is the print statement?
By design, I haven't used one. Until here, we're just using the Prolog REPL (read-eval-print loop) to do our I/O. This is the natural way to work with Prolog. You'll save yourself a lot of heartache later if you go to some trouble to separate predicates that do I/O and user presentation from predicates concerned with meaning. This is just a petite application of model-view separation. You also benefit from keeping side-effects quarantined in their own predicates. As long as the pure logical part of your program is self-contained, you will always be able to build and compose with it.
How would you print upto a given number of people. e.g. if you type in network(john, 3, X). then it should print out upto N=3 X=ben
I would be inclined to call this predicate show_network/2 instead and keep the separation going. You could do it on the cheap with a failure-driven loop like so:
show_network(Person, Max) :-
between(1, Max, N),
network(Person, N, Friend),
format('~w is friends with ~w\n', [Person, Friend]),
fail.
show_network(_, _).
This will work like so:
?- show_network(john, 3).
john is friends with tom.
john is friends with lucy.
john is friends with ben.
true.
There are other approaches too, for instance, you could use forall/2:
show_network(Person, Max) :-
forall(
(between(1, Max, N), network(Person, N, Friend)),
format('~w is friends with ~w\n', [Person, Friend])).
The relationship between the failure-driven loop and that one should be pretty clear. You could also manually get the list and then process it with maplist/2 or something:
show_network(Person, Max) :-
findall(friend(Person,Friend),
(between(1, Max, N), network(Person, N, Friend)),
Friends),
maplist(show_friend, Friends).
show_friend(friend(Person, Friend)) :-
format('~w is friends with ~w\n', [Person, Friend]).

Limit Printing of the prolog facts to THREE times?

Say I have 10 facts path(X) in the database.
How do I limit it to printing the first 3 from the database?
I have tried using recursive technique but it seems that it will only unify with the first one.
All helps appreciated.
There are two possibilities. Both need to utilize extra-logical properties of Prolog.
(1) You collect all facts in a list and then print the first N items of this list using recursion. This approach is clear, but it could be inefficient when there are many facts (e.g. thousands) and only few of them will be written (e.g. 3).
print1(N) :-
findall(path(X), path(X), List),
print1(List, N).
print1([], N) :- !.
print1([H|T], N) :-
writeln(H),
N1 is N - 1, N1 > 0,
print1(T, N1).
?- print1(3).
(2) You can use a retractable counter and a failure driven loop. This approach is less elegant than the first one but will be more efficient in the case there are many facts and only few of them are written.
:- dynamic count/1.
print2(N) :-
assert(count(N)), !,
path(X), writeln(path(X)),
retract(count(K)),
K1 is K - 1,
(K1 > 0 -> assert(count(K1)); true, !),
fail.
?- print2(3).
Addition: Printing first N "smallest" facts in ascending order:
print_sorted(N) :-
findall(path(X), path(X), List),
msort(List, SortedList),
print1(SortedList, N).
If you are sure, that there are at least 3 facts and you don't mind if all Solutions are created:
three_results(A,B,C) :- findall(X,path(X),[A,B,C|_]).
You can also use something like:
n_results(0,A,A) :- !.
n_results(N,A,R) :- path(X), (member(X,A) -> fail; true), N1 is N - 1, n_results(N1,[X|A],R).
three_results(X) :- n_results(3,[],X).

Resources