prolog depth first iterative deepening - prolog

I am trying to implement a depth first iterative deepening search of a state space graph.
I have a graph with three vertices and their are two activating edges and two inhibition edges. Each node has a binary value, collectively this is the state of the graph. The graph can transition to a new state by seeing if one of the nodes is above a threshold or below a threshold (calculated from summing all the incoming nodes). At most one node will change at each transition. As their are three nodes, their are three state transition edges leaving each state in the state transition graph.
I think my state_change/3 works correctly, for instance I can query:
?-g_s_s(0,1,1,Begin),node(Arc),state_change(g_s(Begin),Second,Arc).
And it gives me the three correct answers:
Begin = [node(v1, 0), node(v2, 1), node(v3, 1)],
Arc = v1,
Second = g_s([node(v1, 1), node(v2, 1), node(v3, 1)]) ;
Begin = [node(v1, 0), node(v2, 1), node(v3, 1)],
Arc = v2,
Second = g_s([node(v1, 0), node(v2, 0), node(v3, 1)]) ;
Begin = [node(v1, 0), node(v2, 1), node(v3, 1)],
Arc = v3,
Second = g_s([node(v1, 0), node(v2, 1), node(v3, 0)])
I am trying to use the predicate id_path given in Bratkos Prolog for A.I book, the solution to question 11.3 but I am having problems using/adapting it. I want to create a path from a start node to the other nodes, with out getting into loops- I don't want it to have repeat elements or to get stuck when a path does not exist. I want the path to say the starting state, then a succession of states you can visit from the start state. If there is a self loop I want this to be included once for every way of getting there. Ie I want to keep track of the way that I got to the state space and make this unique not just that the state space is unique in the path.
For instance from 011 I want all three paths of length one to be found with the arcs.
?-id_path(g_s([node(v1,0),node(v2,1),node(v3,1)],Last,[Temp],Path).
Path = [[node(v1,0),node(v2,1),node(v3,1)],to([node(v1,1),node(v2,1),node(v3,1)],v1)];
Path =[[node(v1,0),node(v2,1),node(v3,1)], to([node(v1,0),node(v2,0),node(v3,1)],v2)];
Path=[[node(v1,0),node(v2,1),node(v3,1)],to([node(v1,1),node(v2,1),node(v3,0)],v3)];
and then at the next level all the paths with three nodes, showing the two arcs it needs to get to the nodes, then at the next level all the paths with fours nodes showing the three arcs it needs etc
I have also put my code in SWISH if this is helpful? (Trying this for the first time?!)
http://pengines.swi-prolog.org/apps/swish/p/HxBzEwLb.pl#&togetherjs=xydMBkFjQR
a(v1,v3). %a activating edge
a(v3,v1).
i(v1,v2). %a inhibition edge
i(v2,v3).
nodes([v1,v2,v3]).
node(X):- nodes(List),member(X,List). %to retrieve a node in graph a) or an arc in graph b)
g_s_s(X,Y,Z,g_s([node(v1,X),node(v2,Y),node(v3,Z)])). %graph_state_simple - I use this to simply set a starting graph state.
sum_list([], 0).
sum_list([H|T], Sum) :-
sum_list(T, Rest),
Sum is H + Rest.
invert(1,0).
invert(0,1).
state_of_node(Node,g_s(List),State):-
member(node(Node,State),List).
%all activating nodes in a graph state for a node
all_a(Node,As,Ss,g_s(NodeList)):-
findall(A, a(A,Node),As),
findall(S,(member(M,As),member(node(M,S),NodeList)),Ss).
%all inhibiting nodes in a graph state for a node
all_i(Node,Is,Ss,g_s(NodeList)):-
findall(I, i(I,Node),Is),
findall(S,(member(M,Is),member(node(M,S),NodeList)),Ss).
%sum of activating nodes of a node in a state
sum_a(Node,g_s(NodeList),Sum):-
all_a(Node,_As,Ss,g_s(NodeList)),
sum_list(Ss,Sum).
%sum of inhibiting nodes of a node in a state
sum_i(Node,g_s(NodeList),Sum):-
all_i(Node,_Is,Ss,g_s(NodeList)),
sum_list(Ss,Sum).
above_threshold(Threshold,Node,g_s(NodeList),TrueFalse):-
sum_a(Node,g_s(NodeList),Sum_A),
sum_i(Node,g_s(NodeList),Sum_I),
TrueFalse = true,
Threshold < (Sum_A-Sum_I),
!.
above_threshold(Threshold,Node,g_s(NodeList),TrueFalse):-
sum_a(Node,g_s(NodeList),Sum_A),
sum_i(Node,g_s(NodeList),Sum_I),
TrueFalse = false,
Threshold >= (Sum_A-Sum_I).
%arc needs to be instantiated
state_change(g_s(State1),g_s(State1),Arc):-
above_threshold(0,Arc,g_s(State1),true),
state_of_node(Arc,g_s(State1),1).
state_change(g_s(State1),g_s(State2),Arc):-
above_threshold(0,Arc,g_s(State1),false),
state_of_node(Arc,g_s(State1),1),
my_map(State1,State2,Arc).
state_change(g_s(State1),g_s(State2),Arc):-
above_threshold(0,Arc,g_s(State1),true),
state_of_node(Arc,g_s(State1),0),
my_map(State1,State2,Arc).
state_change(g_s(State1),g_s(State1),Arc):-
above_threshold(0,Arc,g_s(State1),false),
state_of_node(Arc,g_s(State1),0).
%
my_map([],[],_).
my_map([X|T],[Y|L],Arc):-
X= node(Node,Value1),
Node =Arc,
invert(Value1,Value2),
Y = node(Node,Value2),
my_map(T,L,Arc).
my_map([X|T],[Y|L],Arc):-
X= node(Node,Value1),
Node \= Arc,
Y = node(Node,Value1),
my_map(T,L,Arc).
%this is the def in the book which I can not adapt.
path(Begin,Begin,[start(Begin)]).
path(First, Last,[First,Second|Rest]):-
state_change(First,Second,Arc),
path(Second,Last,[Second|Rest]).
%this is the def in the book which I can not adapt.
id_path(First,Last,Template,Path):-
Path = Template,
path(First,Last,Path)
; copy_term(Template,P),
path(First,_,P),
!,
id_path(First,Last,[_|Template],Path).

Since the state space is finite, there will be only finitely many minimal loops or terminal paths. The following options come to mind to represent minimal loops in a graph.
- Rational Terms: Some Prolog systems support rational terms, so a repeating path [0,1,2,2,2,...] can be represented as X = [0,1|Y], Y=[2|Y].
- Non-Rational Terms: You could of course represent a repeating path also as a pair. The previous example would then be ([0,1], [2]).
Detecting a loop pattern and find not only whether something is loop, but also the part that is looping, can be archived by the following code. The append predicate will do the search:
?- append(X, [2|Y], [0,1,2,3]).
X = [0, 1],
Y = [3]
So we know that when we have already found a path [0,1,2,3], and when we see a node 2, that we have found a loop, and we can expressed the found loop with an Omega word as follows [0,1] [2,3]ω. Here is a simple backtracking code:
path(P, P).
path((C,[]), P) :-
last(C, X), edge(X, Y),
extend(C, Y, A, B), path((A,B), P).
extend(C, Y, A, [Y|B]) :-
append(A, [Y|B], C), !.
extend(C, Y, A, []) :-
append(C, [Y], A).
Here is an example run:
?- path(([s(0,1,1)],[]), X).
X = ([s(0,1,1)],[]) ;
X = ([s(0,1,1),s(0,1,0)],[]) ;
X = ([s(0,1,1),s(0,1,0),s(0,0,0)],[]) ;
X = ([s(0,1,1),s(0,1,0)],[s(0,0,0)]) ;
X = ([s(0,1,1)],[s(0,1,0)]) ;
X = ([s(0,1,1),s(1,1,1)],[]) ;
...

Related

Heuristic function prolog

I have a problem which is [1,2,3,0,4,5,6] and the goal is [4,5,6,0,1,2,3] and the Heuristic function
is to calculate misplaced for 4,5,6 tile in position of 1,2,3 tile so when I try to add the condition for head >3 it always false
getHeuristic([], 0, []):-!.
% here it is calculated as number of misplaced numbers for 4,5,6 only
% ( so if Head = Head and value greater than 3 its correct so
% don't count it)
getHeuristic([H|T1],V,[H|T2]):-!,
H>3,
getHeuristic(T1,V, T2).
getHeuristic([_|T1],H,[_|T2]):-
getHeuristic(T1,TH, T2),
H is TH + 1.
A heuristic is a quick way to estimate how close the current state is to the goal state (in the state space).
Let h*(S) be the cost of an optimal path from current state S to a goal state G. Then, a heuristic function h(S) is admissible if, and only, if:
0 ≤ h(S) ≤ h*(S), and
h(G) = 0.
In other words, an admissible heuristic function must be always optimistic!
For example, from the following current state, you need at least two "moves" to reach the goal state:
Current state = [6,4,5,0,1,2,3]
Swap 6 and 4 : [4,6,5,0,1,2,3]
Swap 5 and 6 : [4,5,6,0,1,2,3] = Goal state
Notice that, for example, a heuristic function that estimates the cost of an optimal path to reach the goal state [4,5,6,0,1,2,3] from the current state [5,4,6,0,1,2,3] as at least 2 is not admissible (since a unique move - swap 5 and 4 - is sufficient to correct both positions).
Thus, I think you can do something like this:
heuristic(State, Goal, Value) :-
heuristic(State, Goal, 0, Value).
heuristic([], [], A, A).
heuristic([X|Xs], [Y|Ys], A, V) :-
( X < Y
-> A1 is A + 1
; A1 is A ),
heuristic(Xs, Ys, A1, V).
Examples:
?- heuristic([1,2,3,0,4,5,6], [4,5,6,0,1,2,3], V).
V = 3.
?- heuristic([6,4,5,0,1,2,3], [4,5,6,0,1,2,3], V).
V = 2.
?- heuristic([5,4,6,0,1,2,3], [4,5,6,0,1,2,3], V).
V = 1.
?- heuristic([4,5,6,0,1,2,3], [4,5,6,0,1,2,3], V).
V = 0.
Remark: The "moves" counted by the heuristic function do not necessarily correspond to the actual moves that cause transitions from states to their successors in the state space (i.e., they are relaxed moves).
If you want to compute a "distance" from the "current state" to the "target state" by counting the number of nonmatching positions:
% nmpcount(+CurrentState,+TargetState,?Count)
nmpcount(CurrentState,TargetState,Count) :-
nmpcount2(CurrentState,TargetState,0,Count).
% nmpcount2(+CurrentState,+TargetState,+CountSoFar,?CountFinal)
nmpcount2([],[],Count,Count).
nmpcount2([X|MoreCS],[X|MoreTS],CountIn,FinalCount) :-
nmpcount2(MoreCS,MoreTS,CountIn,FinalCount).
nmpcount2([X|MoreCS],[Y|MoreTS],CountIn,FinalCount) :-
dif(X,Y),
CountNext is CountIn+1,
nmpcount2(MoreCS,MoreTS,CountNext,FinalCount).

Why does my implementation of Dijkstra's algorithm fail under this undefined case?

I am trying to write an implementation of Dijkstra's Algorithm in Prolog (specifically, GNU Prolog). To begin, I made a fact connection that describes the connected vertices of the graph below.
It now seems like my implementation works for around half of my test cases, namely these:
s, k
g, h
e, c
But for these,
h, g (this one has an empty Tentative_Connections)
c, e (this one too)
i, j (this one finds the incorrect path of [i, k, j])
it fails.
I based my program on the description given on the algorithm's wikipedia page. Here is some rough psuedocode:
for a current node, find its connecting nodes
remove the ones that are visited
make a new list of nodes that factors in a tentative distance
delete the current node from the list of unvisited nodes
if the destination node is not in the unvisited list, the current built-up path is final
otherwise, recur on the node that is the closest regarding the tentative distance
But no matter what, my code keeps failing (with the errors above). To elaborate on that, here is the failure for the h, g case:
Unchartered = []
Tentative = []
Destination not reached
No tentative connections!
Closest = _1232
Closest = _1232
Next_Path = [h,b,s,c,l,i,k,j]
uncaught exception: error(instantiation_error,(is)/2)
| ?-
It seems like at one point, there are no tentative points left to search. I do not know what to do from here, as Wikipedia does not elaborate on this issue. Does anyone who knows this algorithm well know what I can do to fix my code?
% https://www.youtube.com/watch?v=GazC3A4OQTE
% example graph
% cl; gprolog --consult-file dijkstra.pl --entry-goal main
connection(s, c, 3).
connection(c, l, 2).
connection(l, i, 4).
connection(l, j, 4).
connection(i, j, 6).
connection(i, k, 4).
connection(j, k, 4).
connection(k, e, 5).
connection(e, g, 2).
connection(g, h, 2).
connection(h, f, 3).
connection(f, d, 5).
connection(d, a, 4).
connection(b, d, 4).
connection(b, a, 3).
connection(b, s, 2).
connection(b, h, 1).
connection(a, s, 7).
get_vertices(_, Vertex_Count, Traversed, Traversed) :-
length(Traversed, Vertex_Count).
get_vertices([], _, Traversed, Traversed).
get_vertices([Vertex | Vertices], Vertex_Count, Traversed, Result) :-
get_vertices(Vertex, Vertex_Count, Traversed, More_Traversed),
get_vertices(Vertices, Vertex_Count, More_Traversed, Result).
get_vertices(Vertex, _, Traversed, Traversed) :-
atom(Vertex), member(Vertex, Traversed).
get_vertices(Vertex, Vertex_Count, Traversed, Result) :-
atom(Vertex),
findall(Connected, are_connected(Vertex, Connected, _), Vertices),
get_vertices(Vertices, Vertex_Count, [Vertex | Traversed], Result).
are_connected(A, B, S) :- connection(A, B, S) ; connection(B, A, S).
keep_unvisited([], _, []).
keep_unvisited([Connection | Connections], Unvisited, Result) :-
keep_unvisited(Connections, Unvisited, Tail_Result),
[Connection_Name, _] = Connection,
(member(Connection_Name, Unvisited) ->
Result = [Connection | Tail_Result];
Result = Tail_Result).
w_tentative_distances([], _, []).
w_tentative_distances([Connection | Connections], Node_Val, Result) :-
w_tentative_distances(Connections, Node_Val, Tail_Result),
[Connection_Name, Connection_Val] = Connection,
Tentative_Distance is Connection_Val + Node_Val,
(Connection_Val > Tentative_Distance ->
New_Distance = Tentative_Distance; New_Distance = Connection_Val),
Result = [[Connection_Name, New_Distance] | Tail_Result].
closest_node_([], Closest, Closest).
closest_node_([Node | Rest], Closest, Result) :-
[_, Node_Val] = Node,
[_, Closest_Val] = Closest,
(Node_Val < Closest_Val ->
closest_node_(Rest, Node, Result)
;
closest_node_(Rest, Closest, Result)).
closest_node([Node | Rest], Result) :-
closest_node_(Rest, Node, Result).
dijkstra([Node_Name, Node_Val], Unvisited, Dest_Node, Path, Final_Path) :-
findall([Connected, Dist], are_connected(Node_Name, Connected, Dist), Connections),
keep_unvisited(Connections, Unvisited, Unchartered_Connections),
w_tentative_distances(Unchartered_Connections, Node_Val, Tentative_Connections),
% trace,
delete(Unvisited, Node_Name, New_Unvisited),
format('Path = ~w\nNode_Name = ~w\nNode_Val = ~w\n', [Path, Node_Name, Node_Val]),
format('Unvisited = ~w\nNew_Unvisited = ~w\n', [Unvisited, New_Unvisited]),
% notrace,
format('Connections = ~w\n', [Connections]),
format('Unchartered = ~w\n', [Unchartered_Connections]),
format('Tentative = ~w\n', [Tentative_Connections]),
(member(Dest_Node, Unvisited) -> % destination has not been reached
write('Destination not reached\n'),
(closest_node(Tentative_Connections, Closest); write('No tentative connections!\n')),
format('Closest = ~w\n', [Closest]),
append(Path, [Node_Name], Next_Path),
format('Closest = ~w\nNext_Path = ~w\n', [Closest, Next_Path]),
dijkstra(Closest, New_Unvisited, Dest_Node, Next_Path, Final_Path);
write('The end was reached!\n'),
Final_Path = Path).
dijkstra_wrapper(Start, End, Vertex_Count, Path) :-
get_vertices(Start, Vertex_Count, [], Unvisited),
dijkstra([Start, 0], Unvisited, End, [], Path).
main :-
dijkstra_wrapper(h, g, 13, Path),
write(Path), nl.
The non-working examples have an ever-growing Unvisited list (for some recursive cases)
Path = [h,b,s,c,l,i,k]
Node_Name = j
Node_Val = 4
Unvisited = [g,e,j,a,d,f]
New_Unvisited = [g,e,a,d,f]
Connections = [[k,4],[l,4],[i,6]]
Unchartered = []
Tentative = []
Destination not reached
What do I do if there are no paths to travel down?
Another one.
It uses library(assoc) association lists to store the path associated to each vertex. This avoids having to code a list scan. The list of "vertex that shall be visited next" is still a vanilla list, but we need to eliminate duplicated vertex and retain those vertexes with minimal distances only after each visit, then sort the list by vertex distance so that the next vertex that shall be visitited is at the head.
(SWI-Prolog has a built-in associative data structure, the SWI-Prolog dict, which I'm not using here. Does library(assoc) exist in GNU Prolog? It should...)
?- main.
VisitThese is currently: [0-a]
Neighbors of vertex a : [s-7,d-4,b-3]
Dirty visitations : [7-s,4-d,3-b], Clean visitations : [3-b,4-d,7-s]
VisitThese is currently: [3-b,4-d,7-s]
Neighbors of vertex b : [d-4,a-3,s-2,h-1]
Dirty visitations : [5-s,4-h,4-d,7-s], Clean visitations : [4-d,4-h,5-s]
VisitThese is currently: [4-d,4-h,5-s]
Neighbors of vertex d : [a-4,f-5,b-4]
Dirty visitations : [9-f,4-h,5-s], Clean visitations : [4-h,5-s,9-f]
VisitThese is currently: [4-h,5-s,9-f]
Neighbors of vertex h : [f-3,g-2,b-1]
Dirty visitations : [7-f,6-g,5-s,9-f], Clean visitations : [5-s,6-g,7-f]
VisitThese is currently: [5-s,6-g,7-f]
Neighbors of vertex s : [c-3,b-2,a-7]
Dirty visitations : [8-c,6-g,7-f], Clean visitations : [6-g,7-f,8-c]
VisitThese is currently: [6-g,7-f,8-c]
Neighbors of vertex g : [h-2,e-2]
Dirty visitations : [8-e,7-f,8-c], Clean visitations : [7-f,8-c,8-e]
VisitThese is currently: [7-f,8-c,8-e]
Neighbors of vertex f : [d-5,h-3]
Dirty visitations : [8-c,8-e], Clean visitations : [8-c,8-e]
VisitThese is currently: [8-c,8-e]
Neighbors of vertex c : [l-2,s-3]
Dirty visitations : [10-l,8-e], Clean visitations : [8-e,10-l]
VisitThese is currently: [8-e,10-l]
Neighbors of vertex e : [g-2,k-5]
Dirty visitations : [13-k,10-l], Clean visitations : [10-l,13-k]
VisitThese is currently: [10-l,13-k]
Neighbors of vertex l : [i-4,j-4,c-2]
Dirty visitations : [14-i,14-j,13-k], Clean visitations : [13-k,14-i,14-j]
VisitThese is currently: [13-k,14-i,14-j]
Neighbors of vertex k : [e-5,i-4,j-4]
Dirty visitations : [14-i,14-j], Clean visitations : [14-i,14-j]
VisitThese is currently: [14-i,14-j]
Neighbors of vertex i : [j-6,k-4,l-4]
Dirty visitations : [14-j], Clean visitations : [14-j]
VisitThese is currently: [14-j]
Neighbors of vertex j : [k-4,l-4,i-6]
Dirty visitations : [], Clean visitations : []
Vertex a is at distance 0 via [a-0]
Vertex b is at distance 3 via [a-0,b-3]
Vertex c is at distance 8 via [a-0,b-3,s-5,c-8]
Vertex d is at distance 4 via [a-0,d-4]
Vertex e is at distance 8 via [a-0,b-3,h-4,g-6,e-8]
Vertex f is at distance 7 via [a-0,b-3,h-4,f-7]
Vertex g is at distance 6 via [a-0,b-3,h-4,g-6]
Vertex h is at distance 4 via [a-0,b-3,h-4]
Vertex i is at distance 14 via [a-0,b-3,s-5,c-8,l-10,i-14]
Vertex j is at distance 14 via [a-0,b-3,s-5,c-8,l-10,j-14]
Vertex k is at distance 13 via [a-0,b-3,h-4,g-6,e-8,k-13]
Vertex l is at distance 10 via [a-0,b-3,s-5,c-8,l-10]
Vertex s is at distance 5 via [a-0,b-3,s-5]
As computed by:
% =========
% Graph definition
% =========
% ---------
% "Asymmetric connection relation"
% ---------
% "Connection relation" between vertices. Each edge is labeled with
% a "distance" (cost)
%
% connection(?VertexName1,?VertexName2,?Cost).
%
% This also indirectly defines the set of vertices which are simply
% given by their names, which are atoms.
%
% This relation is not symmetric. We make it symmetric by defining
% a symmetric relations "on top". Improvement: This relation could
% be made "canonical" in that a unique representation would be
% enforced by demanding that VertexName1 #=< VertexName (i.e. the
% vertex names would appear sorted by the standard order of terms).
connection(s, c, 3).
connection(c, l, 2).
connection(l, i, 4).
connection(l, j, 4).
connection(i, j, 6).
connection(i, k, 4).
connection(j, k, 4).
connection(k, e, 5).
connection(e, g, 2).
connection(g, h, 2).
connection(h, f, 3).
connection(f, d, 5).
connection(d, a, 4).
connection(b, d, 4).
connection(b, a, 3).
connection(b, s, 2).
connection(b, h, 1).
connection(a, s, 7).
% ---------
% "Symmetric connection relation"
% ---------
sym_connection(Vertex1,Vertex2,Cost) :- connection(Vertex1,Vertex2,Cost).
sym_connection(Vertex1,Vertex2,Cost) :- connection(Vertex2,Vertex1,Cost).
% =========
% Start measuring paths and their cost from vertex 'a'.
%
% Initially we only know about vertex 'a' itself
%
% - It is the only member in the list of vertices to be visited next,
% at distance/cost 0. This is represented by a single pair in list
% VisitThese: it is a list containing only [0-a].
% - We have a path to 'a' with overall distance/cost 0, containing only
% vertex 'a' found at cost 0: the path is [a-0].
% PathContainerIn maps 'a', the destination vertex, to that path
% [[a-0]].
% The container is implemented by an "association list" (a "map")
% from library(assoc); other abstract data types are possible, in
% particular SWI-Prolog's "dict" if this were running in SWI-Prolog.
% =========
main :-
list_to_assoc([a-[a-0]],PathContainerIn),
VisitThese=[0-a],
% Do it!
dijsktra(VisitThese,PathContainerIn,PathContainerOut),
% We are done! we just need to print out...
assoc_to_list(PathContainerOut,Pairs),
print_list(Pairs).
print_list([]).
print_list([Vertex-Path|More]) :-
reverse(Path,ReversedPath),
Path=[_-Dist|_],
format("Vertex ~q is at distance ~d via ~q~n",[Vertex,Dist,ReversedPath]),
print_list(More).
% =========
% dijsktra([CurCost-CurVertex|More],PathContainerIn,PathContainerOut).
%
% - The first argument is the "list of vertexes to be visited next", named
% "VisitThese" (i.e. the "boundary" of the search), sorted by the distance/cost
% of their path from the initial vertex, ascending (so we always need
% to just grab the head of "VisitThese" to find the vertex which is guaranteed
% nearest the start vertex on visit).
% - The second argument is the "container of the best-path-known-so-far to
% vertexes already seen (all of those visited or tentatively visited in
% handle_neighbors/7, for which a best-path-known-so-far could be determined)
% Note that a cheaper representation than keeping the full path would be
% to just keep the last edge of the path.
% The container is used to create a new container, accumulator-style, which
% is the third argument, which contains all the best paths to all the vertices
% at success-time.
% =========
dijsktra([],PathContainer,PathContainer) :- !.
dijsktra(VisitThese,PathContainerIn,PathContainerOut) :-
VisitThese = [CurCost-CurVertex|MoreToVisit],
format("VisitThese is currently: ~q ~n",[VisitThese]),
% bagof/3 fails if no neighbors, but that's only the case if the initial vertex is unconnected
bagof(LocalNeighbor-LocalCost,
sym_connection(CurVertex,LocalNeighbor,LocalCost),
Neighbors),
format("Neighbors of vertex ~q : ~q ~n",[CurVertex,Neighbors]),
get_assoc(CurVertex,PathContainerIn,CurPath),
% format("Found path for current vertex ~q: ~q~n",[CurVertex,CurPath]),
handle_neighbors(CurVertex,CurPath,CurCost,Neighbors,PathContainerIn,PathContainer2,VisitTheseToo),
append(VisitTheseToo,MoreToVisit,Dirty),
clean_visit_these_list(Dirty,Clean),
format("Dirty visitations : ~q, Clean visitations : ~q~n",[Dirty,Clean]),
dijsktra(Clean,PathContainer2,PathContainerOut).
% =========
% "Tentatively visit" all the neighbors of "CurVertex" ("CurVertex" can be reached through
% "CurPath" at cost "CurCost"), determining for each a path and overall cost
%
% We may reach a neighbor of "CurVertex":
%
% - for the first time if no path to it is stored in PathContainer yet.
% Then a new path is stored in PathContainer and the vertex is added to
% the list of vertexes-to-be-visted-next, "VisitThese" (which will have
% to be sorted by overall path cost before the recursive call to dijkstar/3)
% - for a not-the-first time if a path to it is stored in PathContainer yet
% (so the neighbor has been visited (and its old path is - by construction -
% cheaper and it shall not be visited again) or it has been only
% tentatively visited (and its old path *may* be costlier and thus demand
% replacement by the new path as well as addition of a cheaper
% entry in the list of vertexes to be visited next)
%
% Note that the list "Neighbors" also contains the neighboring vertex on
% the path "CurPath" through which "CurVertex" was reached in the first place.
% But there is no need to handle that case in a special way. This is just a
% vertex that has been visited previously and the old path is cheaper.
%
% handle_neighbors(+CurVertex,+CurPath,+CurCost,
% +Neighbors,
% +PathContainerIn,-PathContainerOut,
% -VisitThese)
%
% =========
% case of "all neighbor vertices handled, we are done"
handle_neighbors(_CurVertex,_CurPath,_CurCost,[],PathContainer,PathContainer,[]).
% case of "neighbor vertex already has a path but the new path is costlier
handle_neighbors(CurVertex,CurPath,CurCost,Neighbors,PathContainerIn,PathContainerOut,VisitThese) :-
Neighbors=[Vertex-LocalCost|More], % grab the next neighbor
get_assoc(Vertex,PathContainerIn,[Vertex-BestCostSoFar|_]), % grab its known path, if it exists (fails if not)
NewCost is CurCost+LocalCost,
NewCost >= BestCostSoFar, % the new path is costlier
!, % do nothing, move to next neighbor
handle_neighbors(CurVertex,CurPath,CurCost,More,PathContainerIn,PathContainerOut,VisitThese).
% case of "neighbor vertex already has a path but the new path is cheaper; note that it is added to the "VisitThese" list
handle_neighbors(CurVertex,CurPath,CurCost,Neighbors,PathContainerIn,PathContainerOut,[NewCost-Vertex|VisitThese]) :-
Neighbors=[Vertex-LocalCost|More], % grab the next neighbor
get_assoc(Vertex,PathContainerIn,[Vertex-BestCostSoFar|_]), % grab its known path, if it exists (fails if not)
NewCost is CurCost+LocalCost,
NewCost < BestCostSoFar, % new path is cheaper
!, % replace path, retain neighbor as "to be visited"
put_assoc(Vertex,PathContainerIn,[Vertex-NewCost|CurPath],PathContainer2),
handle_neighbors(CurVertex,CurPath,CurCost,More,PathContainer2,PathContainerOut,VisitThese).
% case of "neighbor vertex has no path yet"; note that it is added to the "VisitThese" list
handle_neighbors(CurVertex,CurPath,CurCost,Neighbors,PathContainerIn,PathContainerOut,[NewCost-Vertex|VisitThese]) :-
Neighbors=[Vertex-LocalCost|More], % grab the next neighbor
\+ get_assoc(Vertex,PathContainerIn,_), % vertex has no path yet
!, % the cut is not really needed
NewCost is CurCost+LocalCost,
put_assoc(Vertex,PathContainerIn,[Vertex-NewCost|CurPath],PathContainer2),
handle_neighbors(CurVertex,CurPath,CurCost,More,PathContainer2,PathContainerOut,VisitThese).
% ---
% Transfrom list of elements "Cost-Vertex"
%
% ... which is the list of vertexes and their "best cost yet" that
% shall be expanded by the Dijkstra algorithm, but in which several
% entries for the same "Vertex" may appear
% ... into a new list where there is always only exactly one entry for
% any "Vertex" appaearing in the original list, and the "Cost"
% associated to it is the minimum cost for that "Vertex".
% ---
clean_visit_these_list(Dirty,SortedByCostAsc) :-
predsort(sort_pairs,Dirty,SortedByVertexFirstCostSecond),
keep_best(SortedByVertexFirstCostSecond,DuplicatesRemovedMinCostRetained),
keysort(DuplicatesRemovedMinCostRetained,SortedByCostAsc).
sort_pairs(Order,Cost1-Vertex,Cost2-Vertex) :-
!, % Same vertex - order depends on associated cost
compare(Order,Cost1,Cost2).
sort_pairs(Order,_-Vertex1,_-Vertex2) :-
Vertex1 \== Vertex2,
!, % Different vertex - Sort lexicographically (cut is not needed actually)
compare(Order,Vertex1,Vertex2).
keep_best([Cost-Vertex,_-Vertex|More],Out) :-
!, % Vertex appears twice - retain only first entry (with smallest cost)
keep_best([Cost-Vertex|More],Out).
keep_best([Cost1-Vertex1,Cost2-Vertex2|More],[Cost1-Vertex1|Out]) :-
Vertex1 \== Vertex2,
!, % Different vertex - Retain first vertex and its cost, move on
keep_best([Cost2-Vertex2|More],Out).
keep_best([X],[X]). % Termination case #1
keep_best([],[]). % Termination case #2
% ---
% Test clean_visit_these_list/2
% ---
:- begin_tests(clean_visit_these_list).
test(1,true(Result == [])) :- clean_visit_these_list([],Result).
test(2,true(Result == [3-b])) :- clean_visit_these_list([3-b],Result).
test(2,true(Result == [3-b])) :- clean_visit_these_list([4-b,3-b,5-b],Result).
test(3,true(Result == [3-b,8-v,9-a,10-w])) :- clean_visit_these_list([8-v,10-w,9-a,3-b],Result).
test(4,true(Result == [2-w,3-b,8-v,9-a])) :- clean_visit_these_list([8-v,10-w,9-a,4-w,3-b,2-w,12-v],Result).
:- end_tests(clean_visit_these_list).
The main idea of dijkstras algorithm is to create a boundary between visited and unvisited nodes. Everything happens in the boundary. The boundary are simply pairs of nodes and their distances from the start node. In each step the node from the unvistied set with the smallest total distance from the start is transfered to the vistied set. To do this all "connections" from any visited nodes to unvisited nodes are checked: distance to the vistited node + connection value is the total distance. This way you can get the minimal distance from start to goal. Please note that this (mostly deterministic) algorithm works with those two sets rather than a concrete path. However if you want to have a path you have to keep track of the source node for each node in the visited set as well.
Your code seems to have multiple issues and I found it way easier just to implement my own solution. One of the problems is that the start node appears in the unvistied list. You can use the following code as a reference to debug your code:
minmember([A],A).
minmember([(E,N,S)|T],R):-
minmember(T,(Etmp,Ntmp,Stmp)),
( N>Ntmp
-> R=(Etmp,Ntmp,Stmp)
; R=(E,N,S)
).
boundary(Visited, (Connected, Dist, Node_Name), Unvisited):-
member((Node_Name, Value_to, _),Visited),
are_connected(Node_Name, Connected, Dist0),
member(Connected,Unvisited),
Dist is Dist0+Value_to.
boundary2path(B,Sink,[(Sink,Dist)]):-
member((Sink,Dist,start),B),
!.
boundary2path(B,Sink,[(Sink,Dist)|R]):-
member((Sink,Dist,Source),B),
boundary2path(B,Source,R).
dijkstra(Boundary, _, Dest_Node, Path):-
Boundary = [(Dest_Node,_,_)|_],
!,
format('Hit the end :) \n'),
boundary2path(Boundary,Dest_Node,Path),
format('Path found = ~w\n', [Path]).
dijkstra(Visited, Unvisited, Dest_Node, Path) :-
findall(E, boundary(Visited, E, Unvisited), Connections),
format('Connections = ~w\n', [Connections]),
minmember(Connections,(Node_Name,Dist,Source)),
format('Choosen = ~w\n', [(Node_Name,Dist,Source)]),
append(U1,[Node_Name|U2],Unvisited),
append(U1,U2,New_Unvisited),
format('New_Unvisited = ~w\n', [New_Unvisited]),
format('Visited = ~w\n', [[(Node_Name,Dist,Source)|Visited]]),
dijkstra([(Node_Name,Dist,Source)|Visited], New_Unvisited, Dest_Node, Path).
Now the output for:
?- dijkstra([(h, 0, start)], [b, g, e, k, j, i, l, c, s, a, d, f], g, L).
Connections = [(f,3,h),(g,2,h),(b,1,h)]
Choosen = b,1,h
New_Unvisited = [g,e,k,j,i,l,c,s,a,d,f]
Visited = [(b,1,h),(h,0,start)]
Connections = [(d,5,b),(a,4,b),(s,3,b),(f,3,h),(g,2,h)]
Choosen = g,2,h
New_Unvisited = [e,k,j,i,l,c,s,a,d,f]
Visited = [(g,2,h),(b,1,h),(h,0,start)]
Hit the end :)
Path found = [(g,2),(h,0)]
L = [(g,2), (h,0)] ;
false.
For the goal j no unvisited nodes are left:
?- dijkstra([(h, 0, start)], [b, g, e, k, j, i, l, c, s, a, d, f], j, L).
...
Visited = [(i,12,l),(k,9,e),(l,8,c),(c,6,s),(d,5,b),(a,4,b),(e,4,g),(f,3,h),(s,3,b),(g,2,h),(b,1,h),(h,0,start)]
Connections = [(j,18,i),(j,13,k),(j,12,l)]
Choosen = j,12,l
New_Unvisited = []
Visited = [(j,12,l),(i,12,l),(k,9,e),(l,8,c),(c,6,s),(d,5,b),(a,4,b),(e,4,g),(f,3,h),(s,3,b),(g,2,h),(b,1,h),(h,0,start)]
Hit the end :)
Path found = [(j,12),(l,8),(c,6),(s,3),(b,1),(h,0)]
L = [(j,12), (l,8), (c,6), (s,3), (b,1), (h,0)] ;
false.
​For the last query I made a sketch for the border:

Given a set of lattice points, how many group of points are there?

The idea is that given a set of lattices points in the plane, determine how many "group" of points there are in the given set.
A group of points is defined as follow:
Given a set S of lattice points,
it is said that these points form a group of points if and only if:
for each a of S the distance of the nearest point(s) is 1
The function must return a list with all the groups of points existing.
input: --> point: list
output: --> group: list
If it's possible to get a better algorithm beacause I'm not sure if this code work for every set of points.
My code look like this
def walk_point_path(points):
groups = []
points_x = sorted(points, key=lambda x: x[1])
visited = [points_x[0]]
q = [points_x[0]]
while points_x:
while q:
x, y = q.pop()
for x, y in (x, y - 1), (x, y + 1), (x - 1, y), (x + 1, y):
if [x, y] not in visited and [x, y] in points_x:
q.append([x, y])
visited.append([x, y])
groups.append(visited)
for point in visited:
points_x.remove(point)
if len(points_x) > 0:
q = [points_x[0]]
visited = [points_x[0]]
return groups
Consider some good implementation of connected-components labeling algorithm.
Your current approach exploits flood-fill algorithm (One component at a time kind) to get points in group.

Sliding tile puzzle with varying tile size using logic programming

So I am trying to solve this Booth arrangement problem given here. It is basically a sliding tile puzzle where one (booth)tile has to reach a target spot and in the end all other (booths)tiles should be in their original location. Each tile/booth has a dimension and following are the input fact and relation descriptions:
One fact of the form room(W,H), which specifies the width W and
height H of the room (3 ≤ W, H ≤ 20).
One fact booths(B), which
specifies the number of booths (1 ≤ B ≤ 20).
A relation that consists
of facts of the form dimension(B, W, H), which specifies the width W
and height H of booth B.
A relation consisting of facts of the form
position(B, W, H), specifying the initial position (W, H) of booth B.
One fact target(B, W, H), specifying the destination (W, H) of the
target booth B.
An additional fact horizon(H) gives an upper bound on
the number of moves to be performed.
The program is supposed to read input facts from a file but I am just trying to do the solving so I have just copy pasted one possible input for now, and I have written some basic clauses:
room(3, 3).
booths(3).
dimension(1, 2, 1).
dimension(2, 2, 1).
dimension(3, 1, 1).
position(1, 0, 1).
position(2, 1, 2).
position(3, 0, 0).
target(3, 0, 2).
horizon(10).
xlim(X) :- room(X,_).
ylim(X) :- room(_,X).
sum(X,Y,Z) :- Z is X+Y .
do(position(B,X,Y),movedown,position(B,X,Z)) :- Y > 0 , sum(Y,-1,Z) .
do(position(B,X,Y),moveup,position(B,X,Z)) :- ylim(L), Y < L , sum(Y,1,Z) .
do(position(B,X,Y),moveleft,position(B,Z,Y)) :- X > 0 , sum(X,-1,Z) .
do(position(B,X,Y),moveright,position(B,Z,Y)) :- xlim(L), X < L, sum(X,1,Z) .
noverlap(B1,B2) :-
position(B1,X1,Y1),
position(B2,X2,Y2),
ends(Xe1,Ye1,B1),
ends(Xe2,Ye2,B2),
( Xe1 < X2 ;
Xe2 < X1 ;
Ye1 < Y2 ;
Ye2 < Y1 ).
ends(Xe,Ye,B) :-
dimension(B,W,H),
position(B,X,Y),
Xe is X+W-1,
Ye is Y+H-1.
between(X,Y,Z) :-
X > Y ,
X < Z .
validMove(M,B) :- do(position(B,X,Y),M,position(B,Xn,Yn)) .
I am new to Prolog and I am stuck on how to go from here, I have the no_overlap rule so I can test if a move is valid or not but I am not sure how with the current clauses that I have. My current clauses for moves do/3 probably needs some modification. Any pointers?.
You need to express the task in terms of relations between states of the puzzle. Your current clauses determine the validity of a single move, and can also generate possible moves.
However, that is not sufficient: You need to express more than just a single move and its effect on a single tile. You need to encode, in some way, the state of the whole puzzle, and also encode how a single move changes the state of the whole task.
For a start, I recommend you think about a relation like:
world0_move_world(W0, M, W) :- ...
and express the relation between a given "world" W0, a possible move M, and the resulting world W. This relation should be so general as to generate, on backtracking, each move M that is possible in W0. Ideally, it should even work if W0 is a free variable, and for this you may find clpfd useful: Constraints allow you to express arithmetic relations in a much more general way than you are currently using.
Once you have such a relation, the whole task is to find a sequence Ms of moves such that any initial world W0 is transformed to a desired state W.
Assuming you have implemented world0_move_world/3 as a building block, you can easily lift this to lists of moves as follows (using dcg):
moves(W0) --> { desired_world(W0) }.
moves(W0) --> [M], { world0_move_world(W0, M, W) }, moves(W).
You can then use iterative deepening to find a shortest sequence of moves that solves the puzzle:
?- length(Ms, _), initial_world(W0), phrase(moves(W0), Ms).

Solve Cannibals/Missionaries using breadth-first search (BFS) in Prolog?

I am working on solving the classic Missionaries(M) and Cannibals(C) problem, the start state is 3 M and 3 C on the left bank and the goal state is 3M, 3C on the right bank. I have complete the basic function in my program and I need to implemet the search-strategy such as BFS and DFS.
Basically my code is learn from the Internet. So far I can successfuly run the program with DFS method, but I try to run with BFS it always return false. This is my very first SWI-Prolog program, I can not find where is the problem of my code.
Here is part of my code, hope you can help me find the problem of it
solve2 :-
bfs([[[3,3,left]]],[0,0,right],[[3,3,left]],Solution),
printSolution(Solution).
bfs([[[A,B,C]]],[A,B,C],_,[]).
bfs([[[A,B,C]|Visisted]|RestPaths],[D,E,F],Visisted,Moves) :-
findall([[I,J,K],[A,B,C]|Visited]),
(
move([A,B,C],[I,J,K],Description),
safe([I,J,K]),
not(member([I,J,K],Visited)
),
NewPaths
),
append(RestPaths,NewPaths,CurrentPaths),
bfs(CurrentPaths,[D,E,F],[[I,J,K]|Visisted],MoreMoves),
Moves = [ [[A,B,C],[I,J,K],Description] | MoreMoves ].
move([A,B,left],[A1,B,right],'One missionary cross river') :-
A > 0, A1 is A - 1.
% Go this state if left M > 0. New left M is M-1
.
.
.
.
.
safe([A,B,_]) :-
(B =< A ; A = 0),
A1 is 3-A, B1 is 3-B,
(B1 =< A1; A1 =0).
I use findall to find all possible path before go to next level. Only the one pass the safe() will be consider as possible next state. The state will not use if it already exist. Since my program can run with DFS so I think there is nothing wrong with move() and safe() predicate. My BFS predicate is changing base on my DFS code, but its not work.
There is a very simple way to turn a depth-first search program into a breadth-first one, provided the depth-first search is directly mapped to Prolog's search. This technique is called iterative deepening.
Simply add an additional argument to ensure that the search will only go N steps deep.
So a dfs-version:
dfs(State) :-
final(State).
dfs(State1) :-
state_transition(State1, State2),
dfs(State2).
Is transformed into a bfs by adding an argument for the depth. E.g. by using successor-arithmetics:
bfs(State, _) :-
final(State).
bfs(State1, s(X)) :-
state_transition(State1, State2),
bfs(State2, X).
A goal bfs(State,s(s(s(0)))) will now find all derivations requiring 3 or less steps. You still can perform dfs! Simply use bfs(State,X).
To find all derivations use natural_number(X), bfs(State,X).
Often it is useful to use a list instead of the s(X)-number. This list might contain all intermediary states or the particular transitions performed.
You might hesitate to use this technique, because it seems to recompute a lot of intermediary states ("repeatedly expanded states"). After all, first it searches all paths with one step, then, at most two steps, then, at most three steps... However, if your problem is a search problem, the branching factor here hidden within state_transition/2 will mitigate that overhead. To see this, consider a branching factor of 2: We only will have an overhead of a factor of two! Often, there are easy ways to regain that factor of two: E.g., by speeding up state_transition/2 or final/1.
But the biggest advantage is that it does not consume a lot of space - in contrast to naive dfs.
The Logtalk distribution includes an example, "searching", which implements a framework for state space searching:
https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/searching
The "classical" problems are included (farmer, missionaries and cannibals, puzzle 8, bridge, water jugs, etc). Some of the search algorithms are adapted (with permission) from Ivan Bratko's book "Prolog programming for artificial intelligence". The example also includes a performance monitor that can give you some basic stats on the performance of a search method (e.g. branching factors and number of state transitions). The framework is easy to extend, both for new problems and new search methods.
If anyone still interested in this for a python solution please find the following.
For the simplification, count of Missionaries and Cannibals on left is only taken to the consideration.
This is the solution tree.
#M #missionaries in left
#C #cannibals in left
# B=1left
# B=0right
def is_valid(state):
if(state[0]>3 or state[1]>3 or state[2]>1 or state[0]<0 or state[1]<0 or state[2]<0 or (0<state[0]<state[1]) or (0<(3-state[0])<(3-state[1]))):
return False
else:
return True
def generate_next_states(M,C,B):
moves = [[1, 0, 1], [0, 1, 1], [2, 0, 1], [0, 2, 1], [1, 1, 1]]
valid_states = []
for each in moves:
if(B==1):next_state = [x1 - x2 for (x1, x2) in zip([M, C, B], each)]
else:next_state = [x1 + x2 for (x1, x2) in zip([M, C, B], each)]
if (is_valid(next_state)):
# print(next_state)
valid_states.append(next_state)
return valid_states
solutions = []
def find_sol(M,C,B,visited):
if([M,C,B]==[0,0,0]):#everyne crossed successfully
# print("Solution reached, steps: ",visited+[[0,0,0]])
solutions.append(visited+[[0,0,0]])
return True
elif([M,C,B] in visited):#prevent looping
return False
else:
visited.append([M,C,B])
if(B==1):#boat is in left
for each_s in generate_next_states(M,C,B):
find_sol(each_s[0],each_s[1],each_s[2],visited[:])
else:#boat in in right
for each_s in generate_next_states(M,C,B):
find_sol(each_s[0],each_s[1],each_s[2],visited[:])
find_sol(3,3,1,[])
solutions.sort()
for each_sol in solutions:
print(each_sol)
Please refer to this gist to see a possible solution, maybe helpful to your problem.
Gist: solve Missionaries and cannibals in Prolog
I've solved with depth-first and then with breadth-first, attempting to clearly separate the reusable part from the state search algorithm:
miss_cann_dfs :-
initial(I),
solve_dfs(I, [I], Path),
maplist(writeln, Path), nl.
solve_dfs(S, RPath, Path) :-
final(S),
reverse(RPath, Path).
solve_dfs(S, SoFar, Path) :-
move(S, T),
\+ memberchk(T, SoFar),
solve_dfs(T, [T|SoFar], Path).
miss_cann_bfs :-
initial(I),
solve_bfs([[I]], Path),
maplist(writeln, Path), nl.
solve_bfs(Paths, Path) :-
extend(Paths, Extended),
( member(RPath, Extended),
RPath = [H|_],
final(H),
reverse(RPath, Path)
; solve_bfs(Extended, Path)
).
extend(Paths, Extended) :-
findall([Q,H|R],
( member([H|R], Paths),
move(H, Q),
\+ member(Q, R)
), Extended),
Extended \= [].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% problem representation
% independent from search method
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
initial((3,3, >, 0,0)).
final((0,0, <, 3,3)).
% apply a *valid* move
move((M1i,C1i, Bi, M2i,C2i), (M1f,C1f, Bf, M2f,C2f)) :-
direction(Bi, F1, F2, Bf),
who_move(MM, CM),
M1f is M1i + MM * F1, M1f >= 0,
C1f is C1i + CM * F1, C1f >= 0,
( M1f >= C1f ; M1f == 0 ),
M2f is M2i + MM * F2, M2f >= 0,
C2f is C2i + CM * F2, C2f >= 0,
( M2f >= C2f ; M2f == 0 ).
direction(>, -1, +1, <).
direction(<, +1, -1, >).
% valid placements on boat
who_move(M, C) :-
M = 2, C = 0 ;
M = 1, C = 0 ;
M = 1, C = 1 ;
M = 0, C = 2 ;
M = 0, C = 1 .
I suggest you to structure your code in a similar way, with a predicate similar to extend/2, that make clear when to stop the search.
If your Prolog system has a forward chainer you can also solve
the problem by modelling it via forward chaining rules. Here
is an example how to solve a water jug problem in Jekejeke Minlog.
The state is represented by a predicate state/2.
You first give a rule that filters duplicates as follows. The
rule says that an incoming state/2 fact should be removed,
if it is already in the forward store:
% avoid duplicate state
unit &:- &- state(X,Y) && state(X,Y), !.
Then you give rules that state that search need not be continued
when a final state is reached. In the present example we check
that one of the vessels contains 1 liter of water:
% halt for final states
unit &:- state(_,1), !.
unit &:- state(1,_), !.
As a next step one models the state transitions as forward chaining
rules. This is straight forward. We model emptying, filling and pouring
of vessels:
% emptying a vessel
state(0,X) &:- state(_,X).
state(X,0) &:- state(X,_).
% filling a vessel
state(5,X) &:- state(_,X).
state(X,7) &:- state(X,_).
% pouring water from one vessel to the other vessel
state(Z,T) &:- state(X,Y), Z is min(5,X+Y), T is max(0,X+Y-5).
state(T,Z) &:- state(X,Y), Z is min(7,X+Y), T is max(0,X+Y-7).
We can now use the forward chaining engine to do the job for us. It
will not do iterative deeping and it will also not do breadth first.
It will just do unit resolution by a strategy that is greedy for the
given fact and the process only completes, since the state space
is finite. Here is the result:
?- post(state(0,0)), posted.
state(0, 0).
state(5, 0).
state(5, 7).
state(0, 7).
Etc..
The approach will tell you whether there is a solution, but not explain
the solution. One approach to make it explainable is to use a fact
state/4 instead of a fact state/2. The last two arguments are used for
a list of actions and for the length of the list.
The rule that avoids duplicates is then changed for a rule that picks
the smallest solution. It reads as follows:
% choose shorter path
unit &:- &- state(X,Y,_,N) && state(X,Y,_,M), M<N, !.
unit &:- state(X,Y,_,N) && &- state(X,Y,_,M), N<M.
We then get:
?- post(state(0,0,[],0)), posted.
state(0, 0, [], 0).
state(5, 0, [fl], 1).
state(5, 7, [fr,fl], 2).
state(0, 5, [plr,fl], 2).
Etc..
With a little helper predicate we can force an explanation of
the actions that lead to a path:
?- post(state(0,0,[],0)), state(1,7,L,_), explain(L).
0-0
fill left vessel
5-0
pour left vessel into right vessel
0-5
fill left vessel
5-5
pour left vessel into right vessel
3-7
empty right vessel
3-0
pour left vessel into right vessel
0-3
fill left vessel
5-3
pour left vessel into right vessel
1-7
Bye
Source Code: Water Jug State
http://www.xlog.ch/jekejeke/forward/jugs3.p
Source Code: Water Jug State and Path
http://www.xlog.ch/jekejeke/forward/jugs3path.p

Resources