2
course(cmput325).
course(cmput175).
course(cmput201).
course(cmput204).
prerequisite(cmput204, cmput325).
prerequisite(cmput175, cmput201).
prerequisite(cmput175, cmput204).

I need to write a new predicate, which is

can_take(+L,?C).

Definition:

L is a given list of courses that a student has already taken. If C is also given,then the predicate should check whether the student has all the required courses for C. If C is a variable, then with backtracking, the predicate should produce one course at a time that the student can take now. Courses can be in any order, but each course should be generated only once, and you should not return any courses that the student has already taken.

Example:

?- findall(C, can_take([cmput175], C), L).
should return

L = [cmput201, cmput204].

Here is my predicate:

can_take(L,C) :- prerequisite(L,C).
can_take([L|List],C) :- prerequisite(L,C),can_take(List,C).

This predicate didn't return the correct result, and it just returned false. I think it is because I didn't determine the condition when L is empty, however, if I tried to add L \== [] in either of them. It still gave me error...what should I do so that this predicate will stop and give me result?

-------update-------

pre(X,C) :- prerequisite(X,C).   
pre(X,C) :- prerequisite(X,Y), pre(Y,C).

pre2(C,L) :- findall(L1,pre(L1,C),L).

required(C,L) :- pre2(C,L1),sort(L1,L).

can_take([],_).
can_take(L,C) :- required(C,L).
can_take([L|List],C) :- prerequisite(L,C),can_take(List,C).

Here is my code test:

?- required(cmput325,L).
L = [cmput175, cmput204].

?- required(cmput204,L).
L = [cmput175].

?- can_take([cmput175],X).
X = cmput201 ;
X = cmput204 ;

?- findall(C, can_take([cmput175], C), L).
L = [cmput201, cmput204].


?- can_take([cmput204],cmput325).
false. (this one is OK)

?- can_take([cmput175,cmput204],cmput325).
true ;
false. (this one is OK)

?- can_take([cmput175],cmput204).
true ;
true ;
false.

the last one is not ok because I don't want it to return two true statements...so what I want is just let it stop when either second or last line returns true. For my assignment, I am not allowed to use cut operator !, is there any other way to do it?

Code Vanessa
  • 169
  • 6

1 Answers1

3

(I will assume that you can take a course a second time, even if you have taken it already. That at least are the rules I know of.)

You can take a course, provided you have taken all required courses already.

There is no direct "all" in Prolog. But you can formulate this differently

You can take a course, provided there is no required course that you have not taken already.

can_take(Takens, Next) :-
    course(Next),
    iwhen( ground(Takens), 
           \+ ( prerequisite(Required, Next), \+ member(Required, Takens) ) ).

This uses iwhen/2 to guard against cases where Takens is not fully instantiated.

Note that there is a slight difference to your examples:

?- findall(C, can_take([cmput175], C), L).
   L = [cmput175, cmput201, cmput204].
%       ^^^^^^^^

Disclaimer
Your problem is inherently non-monotonic: By adding further facts for requirements you are reducing the number of possible courses you may take. As a beginner, rather stick to problems that are monotonic in nature. It is on this side where Prolog really excels.

false
  • 10,264
  • 13
  • 101
  • 209
  • uh, actually the correct result for ?- findall(C, can_take([cmput175], C), L). is L = [cmput201, cmput204]. – Code Vanessa Mar 14 '19 at 21:15
  • @CodeVanessa: That's not what you stated. – false Mar 14 '19 at 21:17
  • @CodeVanessa it is easy to fix. First, by stating that `L` is required to be fully ground, you don't need to guard against it being not fully ground, then you just add a member call to it, to get: `can_take(Takens, Next) :- course(Next), \+ ( prerequisite(Required, Next), \+ member(Required, Takens) ), \+ member( Next, Takens).`. by *starting* with `course(Next)`, it guarantees there won't be any duplicates, too. *generate* a possible candidate, then test its suitability. – Will Ness Mar 15 '19 at 19:13
  • in e.g. C they don't care if a caller misuses some function; they call it "undefined behavior" and move on. – Will Ness Mar 16 '19 at 16:42
  • @Will: Code exposing undefined behavior in C is not recommended here on SO. – false Mar 16 '19 at 16:49
  • @false thanks for the reference! (I believed you the first time, too :) ). two more follow-ups: a. why not simply `ground(Takens) -> course(Next), \+ (...) ; error(....)`, and b. both (a.) and your `when` use cut internally, while OP stated *no cuts*. So maybe while they are learning, and not writing production code, the less clean code is unavoidable. ... or maybe your `if_/3` can help... – Will Ness Mar 17 '19 at 08:46
  • ad a) `iwhen/2` is the strict brother of `when/2` which does coroutining. So everything is thus expressed in the same way. Also, in your case it is possible to add further conditions that are not only testing, think of `ground(Takens), Next = cs123` which is no longer valid. And, the error handling is something you would have to handle explicitly. b the cut in `iwhen/2` after `var/1` is a green cut to permit fast indexing. Enfin, a really green cut! – false Mar 17 '19 at 16:48