Reputation: 1137
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)?
Upvotes: 0
Views: 1044
Reputation: 40778
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 N
2.
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:
N0
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.
;-)
Upvotes: 1
Reputation: 1137
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.
Upvotes: 0