How to extract data to list in Prolog? - list

I have an array L of some type, I'm trying to extract the data to an array, for example:
L=[day(sunday),day(monday)]
to
Target=[sunday,monday]
Tried using forall and searched for related questions on Prolog lists.
extract_data_to_list(L,Target) :-
member(day(Day),L),
length(L, L1),
length(Target, L1),
member(Day,Target).
Current output:
?- extract_data_to_list([day(sunday),day(monday)],Target).
Target = [sunday, _5448] ;
Target = [_5442, sunday] ;
Target = [monday, _5448] ;
Target = [_5442, monday].
Desired output:
?- extract_data_to_list([day(sunday),day(monday)],Target).
Target=[sunday,monday]

This is an ideal problem for maplist:
day_name(day(DayName), DayName).
dates_daylist(Dates, DayList) :-
maplist(day_name, Dates, DayList).
Maplist applies day_name to each corresponding pair of elements in Dates and DayList.

This is an ideal problem for library(lambda) for SICStus|SWI:
maplist(\day(N)^N^true, Dates, Daylist).

I have a couple other ways you can do this, just in case you're wondering.
?- findall(D, member(day(D), [day(monday), day(tuesday)]), Days).
Days = [monday, tuesday].
The trick here is that you can use findall/3 to drive a simple loop, if the Goal (argument 2) uses member/2. In this case, we're unifying day(D) with each item in the list; no further work really needs to happen besides the unification, so we're able to "tear off the wrapping" just with member/2 but you could drive a more complex loop here by parenthesizing the arguments. Suppose you wanted to change day to day-of-week, for instance:
?- findall(DoW, (member(day(D),
[day(monday), day(tuesday)]), DoW=day_of_week(D)),
Days).
Days = [day_of_week(monday), day_of_week(tuesday)].
Making the goal more complex works, in other words, as long as you parenthesize it.
The second trick is specific to SWI-Prolog (or Logtalk, if you can use that), which is the new library(yall):
?- maplist([Wrapped,Day]>>(Wrapped=day(Day)),
[day(monday),day(tuesday)], X).
X = [monday, tuesday].
library(yall) enables you to write anonymous predicates. [Wrapped,Day]>>(Wrapped=day(Day)) is sort of like an inline predicate, doing here exactly what #lurker's day_name/2 predicate is doing, except right inside the maplist/3 call itself without needing to be a separate predicate. The general syntax looks something like [Variables...]>>Goal. This sort of thing was previously available as library(lambda) and has been a feature of Logtalk for many years.

Related

Prolog add certain items to list

I'm unable to figure out why my code isn't working despite looking through answers to similar questions. I'm too new at Prolog to properly name things, but I hope you can see what I'm trying to get at.
I am defining a timetable roughly based on this program and am struggling with getting a list of the Classes that Mike teaches for a given result (Next step will be to declare that only results where both Mike and Phil teach 2 should be returned, but I want to work through it so that I can see and understand what's going on).
I imagine this should be simple but any combinations of the addToList(List,C) predicate never work. I know there is the append predicate but I hear it's inefficient, and would like to learn the 'raw' way. I don't know how many variations I've attempted and can't get my head around the way Prolog works in this regard and don't know on what level I'm going wrong - it's all a bit of a black box mystery working with it.
var program =
:- use_module(library(lists)).
prefers(may,a).
prefers(may,b).
prefers(may,c).
prefers(may,d).
prefers(bob,a).
prefers(bob,b).
prefers(bob,c).
prefers(pete,a).
prefers(pete,b).
prefers(pete,c).
prefers(pete,d).
prefers(tom,a).
prefers(tom,b).
prefers(tom,c).
prefers(tom,d).
teacher_pref(mike,a).
teacher_pref(mike,b).
teacher_pref(mike,c).
teacher_pref(phil,b).
teacher_pref(phil,c).
teacher_pref(phil,d).
addToList([C|List],C):- addToList(List,C).
timetable([a,[C1,S1,T1],b,[C2,S2,T2],c,[C3,S3,T3],d,[C4,S4,T4]],List1):-
teacher_pref(T1,C1),
teacher_pref(T2,C2),
teacher_pref(T3,C3),
teacher_pref(T4,C4),
prefers(S1,C1),
prefers(S2,C2),
S1\\=S2,
prefers(S3,C3),
S1\\=S3,
S2\\=S3,
prefers(S4,C4),
S1\\=S4,
S2\\=S4,
S3\\=S4,
addToList(List1,C):-
teacher_pref(mike,C).
session.consult( program );
session.query('timetable([C1,[a,S1,T1],C2,[b,S2,T2],C3,[c,S3,T3,L3],C4,[d,S4,T4]],List1).')
If I understand correctly you have:
teacher_pref(mike,a).
teacher_pref(mike,b).
teacher_pref(mike,c).
And you want to get a list of these classes, which would be:
[a, b, c]
In Prolog we have some higher-order predicates that are for occasions like this:
% (What to find, the goal to call, all the results)
?- findall(Class, teacher_pref(mike, Class), Classes).
Classes = [a, b, c].
In the Tau-Prolog docs they're under All Solutions, in SWI-Prolog there's a couple more.
To make this into a more generic predicate:
teacher_prefs(Teacher, Prefs) :-
findall(Pref, teacher_pref(Teacher, Pref), Prefs).

Can we create a fact from inside the Prolog code?

My aim is to break the given list of lists in such a way that I am able to access the individual lists by the same name. I have the following list:-
mylist([[1,2],[2,3],[3,4],[4,6]]).
I want to break the list into [1,2], [2,3], [3,4], [4,6] so that I can access the items (like [1,2]) individually.
For that, can I create a new fact from the separated list elements? I am able to separate the elements into individual lists. But, I want to convert those individual lists into facts. Like:-
mylist([[1,2],[2,3],[3,4],[4,6]]).
should become the following:-
node([1,2]).
node([2,3]).
node([3,4]).
node([4,6]).
And then I should be able to access each and every list using "node".
The other answer is fine (esp. the forall solution), but here is what you could do if you knew your list at compile time, and wanted to add the node/1 facts to the database at compile time.
This code is simplified from the example available at the very bottom of this page:
In your file (I will call it nodes.pl):
term_expansion(nodes_list(NL), Nodes) :-
maplist(to_node, NL, Nodes).
to_node(X, node(X)).
nodes_list([[1,2],[2,3],[3,4],[4,6]]).
When I consult the file, I get:
?- [nodes].
true.
?- listing(node).
node([1, 2]).
node([2, 3]).
node([3, 4]).
node([4, 6]).
true.
Two details:
The expanded predicate, here nodes_list/1, is not going to be in the database.
The clause of term_expansion/2 must come before the definition of nodes_list/1 in the source file.
A great answer (maybe the best) recommended(in the comments) by CapelliC is:
?-forall(member(X,[[1,2],[2,3],[3,4]]),assertz(node(X))).
You could also write:
my_list(L):- member(X,L),assertz(node(X)).
Example:
?- my_list([[1,2],[2,3],[3,4]]).
true ;
true ;
true.
?- node([1,2]).
true ;
false.
Another way thanks to CapelliC's recommendation would be:
?- maplist(X>>assertz(node(X)), [[1,2],[2,3],[3,4]]).

How to add item to list in prolog

I have a list in prolog that contains several items. I need to 'normalized' the content of this list and write the result to a new list. But I still have problem in doing it.
The following code shows how I did it:
normalizeLists(SourceList, DestList) :-
% get all the member of the source list, one by one
member(Item, SourceList),
% normalize the item
normalizeItem(Item, NormItem),
% add the normalize Item to the Destination List (it was set [] at beginning)
append(NormItem, DestList, DestList).
The problem is in the append predicate. I guess it is because in prolog, I cannot do something like in imperative programming, such as:
DestList = DestList + NormItem,
But how can I do something like that in Prolog? Or if my approach is incorrect, how can I write prolog code to solve this kind of problem.
Any help is really appreciated.
Cheers
Variables in Prolog cannot be modified, once bound by unification. That is a variable is either free or has a definite value (a term, could be another variable). Then append(NormItem, DestList, DestList) will fail for any NormItem that it's not an empty list.
Another problem it's that NormItem it's not a list at all. You can try
normalizeLists([], []).
normalizeLists([Item|Rest], [NormItem|NormRest]) :-
% normalize the item
normalizeItem(Item, NormItem),
normalizeLists(Rest, NormRest).
or (if your Prolog support it) skip altogether such definition, and use an higher order predicate, like maplist
...
maplist(normalizeItem, Items, Normalized),
...

Using Prolog Lists in Recursion

So I am trying to use a recursive method to find a path between two people. Here is the quick background:
I define some facts in(X,Y). That show who is related, ie. in(person1,project1), in(person2,project1), etc etc. Now any two people are related if they were in the same project as each other, or there is a linking path of people between them. For example p1 worked on A p2 worked on A and B and p3 worked on B therefore there is a path from p1 to p3 through p2. These paths can be any length.
I am trying to solve this recursively (don't see any other way), but there is an annoying problem:
related(A,B) :-
in(A,X),
in(B,X),
not(A=B).
chain(A,B) :-
related(A,B).
chain(A,B) :-
related(A,Y),
chain(Y,B).
The issue is that the path can repeat itself. It can go from p1 to p2 back to p1 endless times. A person should not be in the path more than 1 time.
I tried to fix this with a list that I add to. If a person is already in the list, they can't be added again:
related(A,B,L) :-
in(A,X),
in(B,X),not(A=B).
chain(A,B,L) :-
related(A,B,L).
chain(A,B,L) :-
related(A,Y,L),
not(member(Y,L)),
append(L,[Y],Q),
chain(Y,B,Q).
And it sort of worked, but caused a ton of random errors, repeating some people multiple times, some only once, and then failing. Does this approach look right? Am I totally using lists wrong?
Thank You.
First improvement. Are you looking for all the chains of relations or do you want to check if there is one chain of relation? In the first case add a cut.
chain(A,B) :-
related(A,B), !.
chain(A,B) :-
related(A,Y),
chain(Y,B).
In the second case, Prolog does exactly what it's asked to do, that is finding all the possible chains.
Please post a query that causes problems so that we can reason together on it and improve the solution.
Here is an alternative way, maybe less efficient but rather general, based on fixpoint computation.
connected(Found, Connected) :-
collect(Found, [], Ext),
( Ext == Found
-> Connected = Found
; connected(Ext, Connected)
).
collect([], Set, Set).
collect([E|Es], Set, Fix) :-
extend(E, Set, Ext),
collect(Es, Ext, Fix).
extend(E, Set, Ext) :-
directly(E, DirectConn),
ord_union(DirectConn, Set, Ext).
directly(A, DirectConn) :-
setof(B, P^(in(A, P), in(B, P)), DirectConn).
We must call connected(Found, Connected) with a sorted list, and it loops until the set cannot be extended. For instance, with this test data
in(anna, project1).
in(bob, project1).
in(bob, project2).
in(chris, project2).
in(dan, project3).
?- connected([bob],L).
L = [anna, bob, chris].
?- connected([dan],L).
L = [dan].
I allow on purpose directly/2 get identity, i.e.
?- directly(X,Y).
X = anna,
Y = [anna, bob] ;
...
X = dan,
Y = [dan].
I think I was never perfectly clear enough, but I ended up solving this myself. I put the code below.
What really mattered was an effective notchain predicate and then making sure I did the appends correctly. I also created a notsame predicate to replace the not(A=B). The code is below. Most of the answer was making sure that there were [] around what was being appended to the list. Not having the correct [] around what was being appended caused errors.
notchain(X,L) :-
member(X,L),!,fail.
notchain(X,L).
And then:
chain(A,B,L) :-
related(A,B),
append(L,[A],Q),
append(Q,[B],Z),
write(final),writeln(Z).
chain(A,B,L) :-
notchain(A,L),
append(L,[A],Q),
related(A,Y),
chain(Y,B,Q).
This was used in related:
notsame(A,B) :-
(A=B),!,fail.
notsame(A,B).

Prolog Program for a recordings database

I have three types of facts:
album(code, artist, title, date).
songs(code, songlist).
musicians(code, list).
Example:
album(123, 'Rolling Stones', 'Beggars Banquet', 1968).
songs(123, ['Sympathy for the Devil', 'Street Fighting Man']).
musicians(123, [[vocals, 'Mick Jagger'], [guitar, 'Keith Richards', 'Brian Jones']].
I need to create these 4 rules:
together(X,Y) This succeeds if X and Y have played on the same album.
artistchain(X,Y) This succeeds if a chain of albums exists from X to Y;
two musicians are linked in the chain by 'together'.
role(X,Y) This succeeds if X had role Y (e.g. guitar) ever.
song(X,Y) This succeeds if artist X recorded song Y.
Any help?
I haven't been able to come up with much but for role(X,Y) I came up with:
role(X,Y) :- prole(X,Y,musicians(_,W)).
prole(X,Y,[[Y|[X|T]]|Z]).
prole(X,Y,[[Y|[H|T]]|Z]) :- prole(X,Y,[[Y|T]|Z]).
prole(X,Y,[A|Z]) :- prole(X,Y,Z).
But that doesn't work. It does work if I manually put in a list instead of musicians(_,W) like [[1,2,3],[4,5,6]].
Is there another way for me to insert the list as a variable?
As for the other rules I'm at a complete loss. Any help would really be appreciated.
You have a misconception about Prolog: Answering a goal in Prolog is not the same as calling a function!
E.g.: You expect that when "role(X,Y) :- prole(X,Y,musicians(_,W))." is executed, "musicians(_,W)" will be evaluated, because it is an argument to "prole". This is not how Prolog works. At each step, it attempts to unify the goal with a stored predicate, and all arguments are treaded either as variables or grounded terms.
The correct way to do it is:
role(X,Y) :- musicians(_, L), prole(X,Y,L).
The first goal unifies L with a list of musicians, and the second goal finds the role (assuming that the rest of your code is correct).
Little Bobby Tables is right, you need to understand the declarative style of Prolog. Your aim is to provide a set of rules that will match against the set of facts in the database.
Very simply, imagine that I have the following database
guitarist(keith).
guitarist(jim).
in_band('Rolling Stones', keith).
in_band('Rolling Stones', mick).
Supposed I want to find out who is both a guitarist and in the Rolling Stones. I could use a rule like this
stones_guitarist(X):-
guitarist(X),
in_band('Rolling Stones', X).
When a variable name is given within a rule (in this case X) it holds its value during the rule, so what we're saying is that the X which is a guitarist must also be the same X that is in a band called 'Rolling Stones'.
There are lots of possible ways for you to arrange the database. For example it might be easier if the names of the musicians were themselves a list (e.g. [guitar,[keith,brian]]).
I hope the following example for song(X,Y) is of some help. I'm using Sicstus Prolog so import the lists library to get 'member', but if you don't have that it's fairly easy to make it yourself.
:- use_module(library(lists)).
song(ARTIST,SONG):-
album(CODE,ARTIST,_,_),
songs(CODE,TRACKS),
member(SONG,TRACKS).