Prolog simple assignment not simple for me - prolog

I must say that I don't understand this language at all but I want to learn it by example so please understand me. I have to write out all persons
that have borrowed at least two items (by id and letter of these persons).
Here is simple database :)
person(1,"A").
person(2,"B").
person(3,"C").
person(4,"D").
person(5,"E").
person(6,"F").
borrowed(1001,1).
borrowed(1002,1).
borrowed(2001,1).
borrowed(1004,2).
borrowed(1005,4).
borrowed(2003,4).
borrowed(2005,5).
borrowed(2006,5).

Assuming that your person/2 is defined as person( unique-id , name ) and that your borrowed/2 represents borrowed( id-of-borrowed-item , person-id-of-borrower ), something like this ought to do you:
borrower_by_count( Name , N ) :-
person(Id,Name) , % find the person
findall(X,borrowed(X,Id),Xs) , % find all the items they've borrowed
length(Xs,N). % check the length:
This will work for any value of N. Something like borrower_by_count( Name , 3 ). should return
Name = "A"
and borrower_by_count( Name , 2 ). should return (as you backtrack into it):
Name = "D"
Name = "E'
It also works the other way: borrowed( "A" , N ) should return
N = 3.
And if both arguments are unbound, it should successively enumerate each person and the number of item's they've borrowed.

Related

How do I assign return variable in prolog?

I'm totally new to Prolog so I might be really off here.
I'm trying to solve the following problem for the past week:
tweeted(anne, tweet1).
tweeted(anne, tweet5).
tweeted(fred, tweet2).
tweeted(fred, tweet7).
tweeted(fred, tweet8).
findTweets(Name, TweetsByName) :- findall(X, tweeted(Name, X), TweetsByName).
tweets([], _).
tweets([Name|Names], Result) :-
findTweets(Name, Result),
/* how do I append Result to Result ? */
tweets(Names, Result).
If I call with single variable, there's only one recursion call and I get result:
?- tweets([fred], R).
R = [tweet2, tweet7, tweet8].
But I'm struggling to understand how can I append Result value from findTweets call to Result in order to return accumulative result?
I tried using append function but I had no luck...
for example:
tweets([], _).
tweets([Name|Names], Result) :-
findTweets(Name, Tweets),
append(Tweets, Result, Temp),
Result is Temp,
tweets(Names, Result).
But I get this error:
?- tweets([fred], R).
ERROR: Type error: `character' expected, found `tweet2' (an atom)
ERROR: In:
ERROR: [11] _25712 is [tweet2,tweet7|...]
ERROR: [10] tweets([fred],_25752) at /home/kimchi/git-repos/bbk/Programming-Language-Paradigms/logic-programming-Pavel-Durov/relationships.pl:33
ERROR: [9] toplevel_call(user:user: ...) at /snap/swi-prolog/43/usr/lib/swipl/boot/toplevel.pl:1117
Thanks in advance :)
First, a few notes:
is is only for arithmetic computations on numbers. You cannot use it on lists, you will get an error.
You can never "re-assign" variables in Prolog. You always need to use a new variable if you want to want a name for a new term.
Apart from that, you are on the right track with append. My solution is close to yours, it just needed to be rearranged a bit:
persons_tweets([], []).
persons_tweets([Person | Persons], AllTweets) :-
person_tweets(Person, ThisPersonTweets),
persons_tweets(Persons, OtherPersonsTweets),
append(ThisPersonTweets, OtherPersonsTweets, AllTweets).
I renamed your findTweets to person_tweets to make clearer that it is a relation between one person and a collection of tweets. Similarly, persons_tweets is a relation between a collection (list) of persons and tweets.
Example:
?- persons_tweets([fred, anne], Tweets).
Tweets = [tweet2, tweet7, tweet8, tweet1, tweet5].
Note that the non-recursive case is persons_tweets([], []). You cannot use the anonymous variable _ here. If you have an empty list of persons, you really want to have an empty list of tweets, not just any tweets. For instance, this should fail, but with your version it would succeed:
?- persons_tweets([], [some_tweet]).
false.
I think you want:
?- Names = [fred], findall(Tweet, (member(Name, Names), tweeted(Name, Tweet)), Tweets).
Names = [fred],
Tweets = [tweet2,tweet7,tweet8].
?- Names = [anne, fred], findall(Tweet, (member(Name, Names), tweeted(Name, Tweet)), Tweets).
Names = [anne,fred],
Tweets = [tweet1,tweet5,tweet2,tweet7,tweet8].
This is listing all the tweets that were tweeted by the Names.
There's no reason to use recursion here. You can do it just with findall/3. Try something like this:
tweet( anne , tweet1 ).
tweet( anne , tweet5 ).
tweet( fred , tweet2 ).
tweet( fred , tweet7 ).
tweet( fred , tweet8 ).
tweets( Ps , Ts ) :- findall(T,desired_tweet(Ps,T),Ts).
desired_tweet(Ps,T) :- tweet(P,T), member(P,Ps).
So tweets([fred],Ts) yields the expected Ts = [tweet1, tweet5, tweet2, tweet7, tweet8].
But it fails horribly if the list of authors is a variable. And it's nice if prolog predicates behave symmetrically. So lets add a couple of helpers here:
This lets the user specify a single name as an atom (a likely common use case):
tweets( P , Ts ) :- atom(P) , ! , tweets([P],Ts) .
And this lets the user leave the persons list as an unbound variable, which will results in retrieving all the tweets (and generating a list of authors):
tweets( Ps , Ts ) :- var(Ps) , ! , setof(P,T^tweet(P,T),Ps) , tweets(Ps,Ts) .
[Note: the expression in the setof/3 invocation, T^tweet(P,T) is an existential quantifier. It tells setof/3 to ignore the values of T in determining the set, so you get the distinct set of P rather than the distinct set of [P,T] pairs.
If you put it all together,
tweet( anne , tweet1 ).
tweet( anne , tweet5 ).
tweet( fred , tweet2 ).
tweet( fred , tweet7 ).
tweet( fred , tweet8 ).
tweets( P , Ts ) :- atom(P) , ! , tweets([P],Ts) .
tweets( Ps , Ts ) :- var(Ps) , ! , setof(P,T^tweet(P,T),Ps) , tweets(Ps,Ts) .
tweets( Ps , Ts ) :- findall(T,desired_tweet(Ps,T),Ts).
desired_tweet(Ps,T) :- tweet(P,T), member(P,Ps).
You can says tweets(anne,Ts) and get the expected Ts = [tweet1, tweet5].
And, similarly, you can say tweets(Ps,Ts) and get
Ps = [anne, fred],
Ts = [tweet1, tweet5, tweet2, tweet7, tweet8]

prolog replacing elements in list

I need help with understanding how to work with lists like that [a(b,c),a(x,d)]
change(S,K,R) changes first list [a,c,b] with values given in second list [c(a,x),c(b,y)]
?- change([a,c,b],[k(a,x),k(b,y)],R).
R = [x,c,y].
% my program but it works with second list that is of wrong list elements type k(a,x) but like [a,x] and works kinda poorly cause return True instead of R= x,c,y, if i print R value it is [y,c,x|_2826]
i call my code with ?- change([a,c,b],[a,x,b,y],R).
change([],[],[]):-!.
change([],[],R):-write(R),!.
change([H1|T1],[],[H1|R]):-change(T1,[],R),!.
change([H1|T1],[H2,H3|T2],R) :-
( ( H1==H2 , change(T1,T2,[H3|R]) )
; ( H1\==H2, change(T1,[H2,H3|T2],[H1|R]) )
).
Looks like you should use Association lists.
see SWI-Prolog manual
and the online doc

Using cuts in Prolog

I am trying to find all of the distinct entries where a person's name is john, peter or fred.
However, if there were, for example, two people called peter, I only want to display one occurrence of the name.
My code so far is as follows:
searchpeople(X) :-
people(_,[X|_]),
X=john; X=peter; X=fred.
I understand that the solution is probably something to do with cuts (having read other posts), but I cannot find an example where cuts are used when trying to retrieve X OR Y OR Z (In my case john, peter or fred).
Thanks in advance.
The problem is that you're confusing operator precedence. Just like more conventional programming languages where writing something like this
if ( A and B OR C OR D )
...
is almost certainly going to get you in trouble, your code has the exact same problem. Operator precedence and associativity causes
searchpeople(X) :-
people(_,[X|_]) ,
X=john ;
X=peter ;
X=fred .
to be parsed as if written
searchpeople(X) :-
( people(_,[X|_]) ,
X = john
) ;
( X = peter ;
X = fred
) .
Which is probably not what you intended.
While you could use parenthesis to get the effect you most likely want:
searchpeople(X) :-
people(_,[X|_]) ,
( X = john ;
X = peter ;
X = fred
) .
You would be better off splitting things up a bit:
search_people(X) :-
people(_,[X|_]) ,
desired_person(X).
desired_person(john).
desired_person(peter).
desired_person(fred).
It makes your intent clearer and easier to understand. It's also easier to debug and extend.

Getting specific data from Fact

I know that the Title ,does not mean anything , however , how to get 'calculas1' word and put it in list from this fact using prolog :
hasMark(calculas1 , 78 , 110).
In Prolog, you can query a fact this way:
hasMark(Subject, X, Y).
Which, in the case of your example, would yield:
Subject = calculus1
X = 78
Y = 110
The easiest way to collect them would be using the builtin, findall:
findall(Subject, hasMark(Subject, _, _), Subjects).
This will yield:
Subjects = [calculus1, ...]
The ... are other subjects, if you have them. Notice that I used _ for two of the parameters. That means I don't care what those values are. Any variable that starts with a _, including that character by itself, means "don't care".
You can also use a predicate like this:
add_subject( MyList, [Subject | MyList] ) :-
hasMark(Subject, _, _).
So you can query:
add_subject( [], List ).
And get:
List = [calculus1].
Note that a naming convention with underscores and lower case is more commonly used in Prolog than "camelCase". So it would be preferred to name your facts has_mark.

add tuple to a list without using pl list library in Prolog

this showName query will gives set of names:
?- showName(SName,Fname).
SName = 'McBrien',
FName = 'Alex' ;
SName = 'Gardner',
FName = 'Daniel' ;
SName = 'Phillips',
FName = 'Abbas' ;
SName = 'Pietzuch',
FName = 'Paul'
and so on as I keep pressing ; it will gives more names.
I need write another function called
nameList (List). %which will put all names by query showName into List as a tuple
((SName1,FName1),(SName2,FName2), ... )
I want try it without using Prolog library(list).
Thanks..
Assuming your facts database is something like
person('Alex', 'McBrien', male).
person('Daniel', 'Gardner', male).
person('Abbas', 'Phillips', male).
person('Paul', 'Pietzuch', male).
without duplicates, you can do it without using findall/3 by means of an accumulator, although with extra computational cost. You have to add an item to the accumulator only if its not already a member of it:
nameList(List):-
nameList([], List).
nameList(IList, List):-
(
call(person(FName, SName, _)),
\+ (member((SName, FName), IList))
)-> nameList([(SName, FName)|IList], List) ; List=IList.
Procedure nameList/1 just calls nameList/2 with an empty accumulator.
Then procedure nameList/2 will call every person from the facts database and check whether the person is in the accumulator list. If it finds one such person then it recursively calls itself adding this person to the accumulator. If it does not find any person not in the input list then it unifies this accumulator with the output List of persons.

Resources