Prolog does not compute the variable for a list

Viewed 66

I have this assignment in my programming course where I find train connections with their stopovers. That's why I give two variables, X for the arrival time and Y for the list of stopovers. But I only get X back. How do I get Y back too?

station(a).
station(b).
station(c).
station(d).

train(a, b, 8.03, 9.15).
train(b, c, 9.18, 10.26).
train(c, d, 10.28, 11.02).

% circular connection
train(c, d, 11.20, 12.44).
train(d, c, 13.02, 14.34).

% When changing trains, departure time > arrival time applies.
% The train should not go in circles, therefore the Y should not contain
% any station more than once.

connection(Start, Destination, TDeparture, TArrival, _) :- 
    train(Start, Destination, Departure, TArrival),  Departure > TDeparture.


connection(Start, Destination, TDeparture, TArrival, Stops) :-
    station(Start),
    station(Destination),
    station(Stop),    
    train(Start, Stop, Departure, Arrival),
    Departure > TDeparture,
    connection(Stop, Destination, Arrival, TArrival, [Stop | Stops]).


% Query connection from a to d, starting after 8.00
% X = Arrival time
% Y = List of stops

% connection(a, d, 8.00, X, Y).

% Should get me:
% X = 11.02
% Y = [c, b];
% X = 12.44
% Y = [c, b]

% but i got only this:
% X = 11.02 ;
% X = 12.44
2 Answers

Your base case has a free variable, so it doesn't change whatever is passed to it. Since your recursive case never binds a value to Stops, it's always a free variable. Hence Y is just Y and isn't displayed.

The answer to your actual question is at the end, but before that I show the easier ways to do it.

Easy solution without tail-call optimization

This starts with an empty list at the base case and builds it up on the return rather than pushing a growing list till the base-case. This results in the order being reversed. You could do an append to get the order right.


% was connection(Start, Destination, TDeparture, TArrival, _)
connection(Start, Destination, TDeparture, TArrival, []) :-  
    train(Start, Destination, Departure, TArrival),  Departure > TDeparture.

% was connection(Start, Destination, TDeparture, TArrival, Stops)
connection(Start, Destination, TDeparture, TArrival, [Stop | Stops]) :-
    station(Start),
    station(Destination),
    station(Stop),    
    train(Start, Stop, Departure, Arrival),
    Departure > TDeparture,
% was connection(Stop, Destination, Arrival, TArrival, [Stop|Stops])
    connection(Stop, Destination, Arrival, TArrival, Stops).

Tail-call optimization using an extra accumulator

Easiest way to achieve tail-call optimization is with a separate accumulator argument. (Or I hope it achieves it). The order is wrong again here, but you could still fix it with an append/3.

% Query with connection(a, d, 8.00, X, [], Y).
% Or define wrapper 
%    connection(a, d, 8.00, X, Y):- connection(a, d, 8.00, X, [], Y).
connection(Start, Destination, TDeparture, TArrival, StopsSoFar, StopsSoFar) :- 
    train(Start, Destination, Departure, TArrival),  Departure > TDeparture.


connection(Start, Destination, TDeparture, TArrival, StopsSoFar, FinalStops) :-
    station(Start),
    station(Destination),
    station(Stop),    
    train(Start, Stop, Departure, Arrival),
    Departure > TDeparture,
    connection(Stop, Destination, Arrival, TArrival, [Stop | StopsSoFar], FinalStops).
% If you want it in the right order, 
    %   append(Stop, StopsSoFar, NextStopsSoFar),
    %   connection(Stop, Destination, Arrival, TArrival, NextStopsSoFar, FinalStops).

The actual answer

If I'm allowed to guess what you're trying to do, You want to pass down a free tail so that it can keep growing till the base-case( [a,b,c, ...|T] )

The difficulty with this is figuring out a way to efficiently separate the head from T and modify T ( T=[d|U] ). Here's my attempt at that with safe_split/3. I've not put too much thought into it.

connection(Start, Destination, TDeparture, TArrival, Stops) :-
    safe_split(_,T, Stops), T= [],
    train(Start, Destination, Departure, TArrival),  Departure > TDeparture.


connection(Start, Destination, TDeparture, TArrival, Stops) :-
    station(Start),
    station(Destination),
    station(Stop),    
    train(Start, Stop, Departure, Arrival),
    Departure > TDeparture,
    safe_split(_,T, Stops), 
    T = [Stop|_],
    connection(Stop, Destination, Arrival, TArrival, Stops).

safe_split(H,T,Stops):-
    append(H,T,Stops), var(T), % lots of backtracking because generate & test.
    !. % Prevent free variables appended to the head.

Appendix:

To demonstrate what happens without the cut

?- L=[a,b,c|_], append(X,Y,L), var(Y).
L = [a, b, c|Y],
X = [a, b, c] ;

L = [a, b, c, _5368|Y],
X = [a, b, c, _5368] ;

L = [a, b, c, _5368, _5380|Y],
X = [a, b, c, _5368, _5380]

% ... and so on.

Thanks for the fun OP, Sorry if I threw too much at you :)

1- The start Predicate takes the Start Node, End Node, Time, and gives X (Arrival Time), and Y (The connecting nodes in between).

2- The connection Predicate checks the connecting nodes, and if Time2 is greater than Time1 then push the node into the List. This List is used for Y. The List gives us the entire path [a,b,c,d], so we further remove the first and last element in the List using removeHead and removeTail predicates. In the end we reverse the List to get Y (This is optional).

3- arrivalTime Predicate gives us all possible values of X. But we check the limitations using @<. @< checks alphabets that the left hand side alphabet should come before the right hand side alphabet (For example a@<b is true, f@<e is false). When checking the train fact with XN value, we see that it's value isn't an alphabet that comes after the EndN alphabet. (i.e. d@<c is wrong when checking b till c). In the other case where for example our circular connection it checks the other arrivalTime Predicate and check that XN come after EndN (d@>c should be true). Observe the @> is different for the second arrivalTime Predicate.

station(a).
station(b).
station(c).
station(d).

train(a, b, 8.03, 9.15).
train(b, c, 9.18, 10.26).
train(c, d, 10.28, 11.02).

% circular connection
train(c, d, 11.20, 12.44).
train(d, c, 13.02, 14.34).


removeHead([_|T],T).
removeTail(L1,L2):-
    append(L2,[_],L1).

connection(Node,Node,_,[Node]):-!.
connection(StartN,EndN,Time1,[StartN|List]):-
    train(StartN,NextN,Time2,_),
    Time2>Time1,
    connection(NextN,EndN,Time1,List).

arrivalTime(StartN,EndN,ArrivalTime):-
    train(XN,EndN,_,ArrivalTime),
    StartN@<EndN, XN@<EndN.
arrivalTime(StartN,EndN,ArrivalTime):-
    train(XN,EndN,_,ArrivalTime),
    StartN@>EndN, XN@>EndN.

start(StartN,EndN,Time,X,Y):-
    connection(StartN,EndN,Time,Y1),
    removeHead(Y1,Y2),
    removeTail(Y2,Y3),!,
    arrivalTime(StartN,EndN,X),
    reverse(Y3,Y).

Example:

?-start(a,d,8.00,X,Y)
X = 11.02,
Y = [c, b]
X = 12.44,
Y = [c, b]

?-start(a,c,9.00,X,Y)
false

?-start(a,c,8.00,X,Y)
X = 10.26,
Y = [b]
false

?-start(b,c,8.00,X,Y)
X = 10.26,
Y = []
false

?- start(d,c,8.00,X,Y).
X = 14.34,
Y = []
Related