4
votes

I have the following facts and rules:

% frequents(D,P) % D=drinker, P=pub
% serves(P,B) % B=beer
% likes(D,B)

frequents(janus, godthaab).
frequents(janus, goldenekrone).
frequents(yanai, goldenekrone).
frequents(dimi,  schlosskeller).

serves(godthaab, tuborg).
serves(godthaab, carlsberg).
serves(goldenekrone, pfungstaedter).
serves(schlosskeller, fix).

likes(janus, tuborg).
likes(janus, carlsberg).

count_good_beers_for_at(D,P,F) :- group_by((frequents(D,P), serves(P,B), likes(D,B)),[D,P],(F = count)).
possible_beers_served_for_at(D,P,B) :- lj(serves(P,B), frequents(D,R), P=R).

Now I would like to construct a rule that should work like a predicate returning "true" when the number of available "liked" beers at each pub that a "drinker" "frequents" is bigger than 0.

I would consider the predicate true when the rule returns no tuples. If the predicate is false, I was planning to make it return the bars not having a single "liked" beer.

As you can see, I already have a rule counting the good beers for a given drinker at a given pub. I also have a rule giving me the number of servable beers.

DES> count_good_beers_for_at(A,B,C)

{                                           
  count_good_beers_for_at(janus,godthaab,2)
}
Info: 1 tuple computed.          

As you can see, the counter doesn't return the pubs frequented but having 0 liked beers. I was planning to work around this by using a left outer join.

DES> is_happy_at(D,P,Z) :- lj(serves(P,B), count_good_beers_for_at(D,Y,Z), (Y=P))

Info: Processing:
  is_happy_at(D,P,Z) :-
    lj(serves(P,B),count_good_beers_for_at(D,Y,Z),Y = P).
{                                           
  is_happy_at(janus,godthaab,2),
  is_happy_at(null,goldenekrone,null),
  is_happy_at(null,schlosskeller,null)
}
Info: 3 tuples computed.

This is almost right, except it is also giving me the pubs not frequented. I try adding an extra condition:

DES> is_happy_at(D,P,Z) :- lj(serves(P,B), count_good_beers_for_at(D,Y,Z), (Y=P)), frequents(D,P)

Info: Processing:
  is_happy_at(D,P,Z) :-
    lj(serves(P,B),count_good_beers_for_at(D,Y,Z),Y = P),
    frequents(D,P).
{                                           
  is_happy_at(janus,godthaab,2)
}
Info: 1 tuple computed.

Now I somehow filtered everything containing nulls away! I suspect this is due to null-value logic in DES.

I recognize that I might be approaching this whole problem in a wrong way. Any help is appreciated.

EDIT: Assignment is "very_happy(D) ist wahr, genau dann wenn jede Bar, die Trinker D besucht, wenigstens ein Bier ausschenkt, das er mag." which translates to "very_happy(D) is true, iff each bar drinker D visits, serves at least 1 beer, that he likes". Since this assignment is about Datalog, I would think it is definitely possible to solve without using Prolog.

2
no idea, but i imagine if anyone cares about the datalog tag they are much more likely to help than people who care about tuples...andrew cooke
@andrewcooke: see edit history. i had the datalog tag on this question since yesterday until today when i tried a new batch of tags. sadly none of the relevant tags seems to have many subscribers.Janus Troelsen
ah, ok, sorry. long time since i used it, so can't help...andrew cooke

2 Answers

5
votes

I think that for your assignement you should use basic Datalog, without abusing of aggregates. The point of the question is how to express universally quantified conditions. I googled for 'universal quantification datalog', and at first position I found deductnotes.pdf that asserts:

An universally quantified condition can only be expressed by an equivalent condition with existential quantification and negation.

In that PDF you will find also an useful example (pagg 9 & 10).

Thus we must rephrase our question. I ended up with this code:

not_happy(D) :-
  frequents(D, P),
  likes(D, B),
  not(serves(P, B)).

very_happy(D) :-
  likes(D, _),
  not(not_happy(D)).

that seems what's required:

DES> very_happy(D)

{                                           
}
Info: 0 tuple computed.          

Note the likes(D, _), that's required to avoid that yanai and dimi get listed as very_happy, without explicit assertion of what them like (OT sorry my English really sucks...)

EDIT: I'm sorry, but the above solution doesn't work. I've rewritten it this way:

likes_pub(D, P) :-
  likes(D, B),
  serves(P, B).

unhappy(D) :-
  frequents(D, P),
  not(likes_pub(D, P)).

very_happy(D) :-
  likes(D, _),
  not(unhappy(D)).

test:

DES> unhappy(D)

{                                           
  unhappy(dimi),
  unhappy(janus),
  unhappy(yanai)
}
Info: 3 tuples computed.          

DES> very_happy(D)

{                                           
}
Info: 0 tuples computed.          

Now we add a fact:

serves(goldenekrone, tuborg).

and we can see the corrected code outcome:

DES> unhappy(D)

{                                           
  unhappy(dimi),
  unhappy(yanai)
}
Info: 2 tuples computed.          

DES> very_happy(D)

{                                           
  very_happy(janus)
}
Info: 1 tuple computed.          
2
votes

Maybe not the answer your are expecting. But you can use ordinary Prolog and easily do group by queries with the bagof/3 or setof/3 builtin predicates.

?- bagof(B,(frequents(D,P), serves(P,B), likes(D,B)),L), length(L,N).
D = janus,
P = godthaab,
L = [tuborg,carlsberg],
N = 2

The semantics of bagof/3 is such that it does not compute an outer join for the given query. The query is normally executed by Prolog. The results are first accumulated and key sorted. Finally the results are then returned by backtracking. If your datalog cannot do without nulls, then yes you have to filter.

But you don't need to go into aggregates when you only want to know the existence of a liked beer. You can do it directly via a query without any aggregates:

is_happy_at(D,P) :- frequents(D,P), once((serves(P,B), likes(D,B))).
?- is_happy_at(D,P).
D = janus,
P = godthaab ;
Nein

The once/1 prevents from unnecessary backtrack. Datalog might either automatically not do unnecessary backtracking when it sees the projection in is_happy_at/2, i.e. B is projected away. Or you might need to explicitly use what corresponds to SQL DISTINCT. Or eventually your datalog provides you something that corresponds to SQL EXISTS which most closely corresponds to once/1.

Bye