2
votes

I am new to Prolog

I am trying in Prolog a rule that gives me a given path from a node to another and also gives me the total weight of the path.

I have succeeded to get all the edges of the path but I am not able to show the weight of the path. I debbuged it and it is seen that variable S adds up to the whole weight of the path but in the way back, deletes all the elements. My idea is to add the total weight to P.

Code:

notIn(A,[]).
notIn(A,[H|T]):- A\==H,notIn(A,T).

path(X,X,_,[], S, P).
path(X,Y,[X|Cs], S, P) :-
    path(X,Y,[X],Cs, S, P), P is S+W.
path(X,Y,Visited,[Z|Cs], S, P) :-
    connection(X,Z,W),
    notIn(Z,Visited),
    path(Z,Y,[Z|Visited],Cs, S+W, P).

? path(ori, dest, X, 0, P).
2
Second path clause has wrong arity: it misses a list argument. So it's never called.CapelliC

2 Answers

4
votes

Your predicate almost works. There are only two issues and some details I'd like to address. Firstly it would aid readability greatly to separate predicates with different arities. So let's put the one rule of path/5 in front of the two rules of path/6 like so:

path(X,Y,[X|Cs], S, P) :-
    path(X,Y,[X],Cs, S, P),
    P is S+W.                          % <-(1)

path(X,X,_,[], S, P).
path(X,Y,Visited,[Z|Cs], S, P) :-
    connection(X,Z,W),
    notIn(Z,Visited),
    path(Z,Y,[Z|Visited],Cs, S+W, P).  % <-(2)

Looking at your example query path/5 seems to be the predicate you want to call to find paths. In the second goal of its single rule (marked as % <-(1)) you are using the built-in is/2 with the expression S+W on the right hand side. The variable W appears here for the first time and thus is unbound. This leads to an instantiation error as illustrated by the following example:

   ?- X is 1+W.
     ERROR!!
     INSTANTIATION ERROR- in arithmetic: expected bound value

However, since you are only using path/5 to call path/6 there is no need for that goal. Secondly, in the second rule of path/6, in the last goal you are passing S+W as argument instead of evaluating it first. To see what happens, let's remove the goal marked % <-(1) from path/5 and add an example graph to your code:

connection(ori,a,2).
connection(a,b,5).
connection(b,a,4).
connection(b,dest,1).

Now consider your example query with an additional goal:

   ?- path(ori, dest, X, 0, P), Weight is P.
P = 0+2+5+1,
Weight = 8,
X = [ori,a,b,dest] ? ;
no

As you see the argument S+W leads to the final weight being an expression rather than a value. Consider adding a goal S1 is S+W before the recursive goal and pass S1 as an argument. Thirdly you are using the built-in (\==)/2 in your predicate notIn/2. This comparison succeeds or fails without side effect or unification. This is fine as long as both arguments are bound to values but are problematic when used with unbound variables. Consider the following queries:

   ?- X=Y, X\==Y.
no

fails as expected but:

   ?- X\==Y, X=Y.
X = Y

succeeds as X\==Y has no effect to the variables, so they can be unified in the next goal. It is a good idea to use dif/2 instead:

   ?- X=Y, dif(X,Y).
no
   ?- dif(X,Y), X=Y.
no

Lastly, two minor suggestions: First, since you are using the 4th argument of path/5 to pass 0 as a start-value for the weight, you might as well do that in the single goal of the rule, thereby simplifying the interface to path/4. Second, it would be nice to have a more descriptive name for the predicate that reflects its declarative nature, say start_end_path_weight/4. So your code would then look something like this:

notIn(A,[]).
notIn(A,[H|T]):-
   dif(A,H),
   notIn(A,T).

start_end_path_weight(X,Y,[X|Cs], P) :-
   path(X,Y,[X],Cs, 0, P).

path(X,X,_,[], P, P).
path(X,Y,Visited,[Z|Cs], S, P) :-
    connection(X,Z,W),
    notIn(Z,Visited),
    S1 is S+W,
    path(Z,Y,[Z|Visited],Cs, S1, P).

With these modifications your example query looks like this:

   ?- start_end_path_weight(ori,dest,X,W).
W = 8,
X = [ori,a,b,dest] ? ;
no
2
votes

Here's how to improve upon @tas's answer by using for arithmetics instead of (is)/2:

:- use_module(library(clpfd)).

start_end_path_weight(X,Y,[X|Cs], P) :-
   path(X,Y,[X],Cs, 0, P).

path(X,X,_,[], P, P).
path(X,Y,Visited,[Z|Cs], S, P) :-
    connection(X,Z,W),
    notIn(Z,Visited)
    maplist(dif(Z),Visited),
    S1 is S+W
    S1 #= S+W, S1 #=< P, 
    path(Z,Y,[Z|Visited],Cs, S1, P).

Limiting the maximum costs? Piece of cake! Consider the following InterRail subset ...

enter image description here

... translated to Prolog ...

connection(X,Y,D) :- to_fro_dt(X,Y,D).
connection(X,Y,D) :- to_fro_dt(Y,X,D).

to_fro_dt(aberdeen,edinburgh,140). to_fro_dt(amsterdam,berlin,370). to_fro_dt(amsterdam,brussels,113). to_fro_dt(amsterdam,cologne,158). to_fro_dt(amsterdam,copenhagen,675). to_fro_dt(ancona,igoumenitsa,900). to_fro_dt(athens,patras,215). to_fro_dt(athens,/* for consistency */piraeus,5). to_fro_dt(athens,thessaloniki,265). to_fro_dt(bar,belgrade,572). to_fro_dt(barcelona,madrid,170). to_fro_dt(barcelona,marseille,280). to_fro_dt(barcelona,sevilla,330). to_fro_dt(barcelona,valencia,175). to_fro_dt(bari,igoumenitsa,570). to_fro_dt(bari,rome,240). to_fro_dt(belfast,dublin,240). to_fro_dt(belgrade,bucharest,730). to_fro_dt(belgrade,budapest,450). to_fro_dt(belgrade,sarajevo,540). to_fro_dt(belgrade,skopje,525). to_fro_dt(belgrade,sofia,485). to_fro_dt(bergen,oslo,405). to_fro_dt(berlin,cologne,260). to_fro_dt(berlin,hamburg,95). to_fro_dt(berlin,munich,345). to_fro_dt(berlin,prague,275). to_fro_dt(berlin,warsaw,365). to_fro_dt(bern,frankfurt,235). to_fro_dt(bern,lyon,230). to_fro_dt(bern,milan,240). to_fro_dt(birmingham,edinburgh,265). to_fro_dt(birmingham,holyhead,245). to_fro_dt(birmingham,london,105). to_fro_dt(bologna,florence,37). to_fro_dt(bologna,milan,60). to_fro_dt(bordeaux,lyon,375). to_fro_dt(bordeaux,madrid,660). to_fro_dt(bordeaux,paris,180). to_fro_dt(bristol,london,105). to_fro_dt(brussels,cologne,107). to_fro_dt(brussels,frankfurt,190). to_fro_dt(brussels,london,140). to_fro_dt(brussels,paris,85). to_fro_dt(bucharest,budapest,830). to_fro_dt(bucharest,sofia,540). to_fro_dt(bucharest,zagreb,365). to_fro_dt(budapest,ljubljana,540). to_fro_dt(budapest,vienna,165). to_fro_dt(budapest,warsaw,680). to_fro_dt(budapest,zagreb,365). to_fro_dt(catania,naples,450). to_fro_dt(cologne,frankfurt,82). to_fro_dt(copenhagen,hamburg,270). to_fro_dt(copenhagen,oslo,520). to_fro_dt(copenhagen,stockholm,315). to_fro_dt(cork,dublin,165). to_fro_dt(dublin,holyhead,195). to_fro_dt(dublin,westport,210). to_fro_dt(edinburgh,glasgow,50). to_fro_dt(faro,lisbon,230). to_fro_dt(florence,rome,95). to_fro_dt(florence,venice,123). to_fro_dt(frankfurt,hamburg,220). to_fro_dt(frankfurt,munich,190). to_fro_dt(frankfurt,paris,235). to_fro_dt(hamburg,munich,350). to_fro_dt(helsinki,rovaniemi,570). to_fro_dt(helsinki,turku,110). to_fro_dt(heraklion,piraeus,390). to_fro_dt(igoumenitsa,patras,360). to_fro_dt(istanbul,sofia,775). to_fro_dt(istanbul,thessaloniki,720). to_fro_dt(kiruna,stockholm,960). to_fro_dt(lisbon,madrid,610). to_fro_dt(lisbon,porto,165). to_fro_dt(ljubljana,venice,540). to_fro_dt(ljubljana,zagreb,140). to_fro_dt(london,paris,135). to_fro_dt(london,penzance,305). to_fro_dt(lyon,marseille,100). to_fro_dt(lyon,paris,115). to_fro_dt(madrid,'málaga',165). to_fro_dt(madrid,pamplona,180). to_fro_dt(madrid,santander,270). to_fro_dt(madrid,santiago,425). to_fro_dt(madrid,sevilla,155). to_fro_dt(madrid,valencia,105). to_fro_dt(marseille,montpellier,140). to_fro_dt(marseille,nice,155). to_fro_dt(milan,munich,465). to_fro_dt(milan,nice,310). to_fro_dt(milan,venice,155). to_fro_dt(munich,prague,365). to_fro_dt(munich,venice,425). to_fro_dt(munich,vienna,250). to_fro_dt(naples,rome,70). to_fro_dt(oslo,stockholm,380). to_fro_dt(paris,rennes,120). to_fro_dt(piraeus,rhodes,710). to_fro_dt(prague,vienna,270). to_fro_dt(prague,warsaw,520). to_fro_dt(sarajevo,zagreb,550). to_fro_dt(skopje,sofia,540). to_fro_dt(skopje,thessaloniki,240). to_fro_dt(sofia,thessaloniki,400). to_fro_dt(split,zagreb,335). to_fro_dt(stockholm,/* added by hand */turku,725). to_fro_dt(stockholm,'östersund',420). to_fro_dt(trondheim,'östersund',230). to_fro_dt(venice,vienna,440). to_fro_dt(vienna,warsaw,450).

... let's find paths that

  • start in Vienna

  • include at least 2 other cities

  • and have a cumulative travel time of 10 hours (or less)!

?- W #=< 600, Path = [_,_,_|_], start_end_path_weight(vienna, _, Path, W).
W = 530, Path = [vienna,budapest,zagreb] ;
W = 595, Path = [vienna,munich,berlin] ;
W = 440, Path = [vienna,munich,frankfurt] ;
W = 522, Path = [vienna,munich,frankfurt,cologne] ;
W = 600, Path = [vienna,munich,hamburg] ;
W = 545, Path = [vienna,prague,berlin] ;
W = 563, Path = [vienna,venice,florence] ;
W = 600, Path = [vienna,venice,florence,bologna] ;
W = 595, Path = [vienna,venice,milan] ;
false.                                      % terminates universally fast