Definite Clause Grammars (DCGs) Example: expr --> atom, [+], expr

advertisement
Definite Clause Grammars (DCGs)
Example:
expr -->
atom, [+], expr
| atom, [-], expr
| atom.
atom -->
[X], { number(X) }
| [abs, ’(’], expr, [’)’].
The predicate phrase/2 is used to parse strings.
| ?- phrase(expr, [1, +, 2]).
yes
| ?- phrase(expr, [1, +, abs, ’(’, 4, +, 5, ’)’]).
yes
| ?- phrase(expr, [1, +]).
no
1
Compiling DCGs to Prolog
DCGs can be compiled into Prolog.
| ?- listing(expr).
expr(A, B) :(
atom(A, C),
’C’(C, +, D),
expr(D, B)
;
atom(A, E),
’C’(E, -, F),
expr(F, B)
;
atom(A, B)
).
yes
’C’/3 is defined as: ’C’([X|Y], X, Y).
’C’/3 is sometimes called connects/3.
2
Compiling DCGs to Prolog, cont.
expr(Input, Remainder) :(
atom(Input, C),
’C’(C, +, D),
expr(D, Remainder)
;
atom(Input, E),
’C’(E, -, F),
expr(F, Remainder)
;
atom(Input, Remainder)
).
Remember: ’C’([X|Y], X, Y).
3
Doing something useful
expr(E) -->
atom(A), [+], expr(E1), { E = A + E1 }
| atom(A), [-], expr(E1), { E = A - E1 }
| atom(A), {E = A}.
atom(A) -->
[X], { number(X), A = X }
| [abs, ’(’], expr(E), [’)’], { A = abs(E) }.
Example:
| ?- phrase(expr, [1, +, abs, ’(’, 4, +, 5, ’)’]).
E = 1+abs(4+5)
4
Search: Overview
• Search problems in Prolog
• Depth-first searching
• Breadth-first searching and other search
orders
5
Search problems
Many problems are encodable as search problems. Examples:
• Path finding: Can we get from a to f in
a graph, given edge(X,Y)?
• Planning: How can we get the missionaries and the cannibals across the river
without anyone getting eaten?
• Logic programming: Is there an SLDderivation, using the available clauses, from
our query to an empty goal (i.e. a refutation)?
All these share some basic structure.
6
Search problems in logic programming
Given a start state, a transition relation, and
a goal state (or states), can we reach a goal
state from the start state?
Components:
• A state space (vertices, states of the world,
logic programming goals...), represented
as Prolog objects.
• A transition relation, e.g. edge(X,Y) or
move(state(A1,B1),state(A2,B2)).
• Start state, goal condition (e.g. goal(State)
predicate).
• Search procedure.
7
Depth-first example
Failed attempt at path finding in a graph:
edge(a,b).
edge(b,a).
edge(a,c).
path(X,X).
path(X,Z) :- edge(X,Y), path(Y,Z).
Loops indefinitely on path(a,c).
loop detection.
We need
8
Loop detection
Solution: Keep a history of visited states.
path(X,Y) :- path(X,Y,[X],Answer).
path(X,X,Answer,Answer).
path(X,Z,Visited,Answer) :edge(X, Y),
\+member(Y,Visited),
path(Y,Z,[Y|Visited],Answer).
This lets us return the actual path, too. Note
the search order:
::::-
path(b,c).
path(b,c,[b], A).
path(a,c,[a,b], A).
path(c,c,[c,a,b],[c,a,b]).
If we’re picky, we can use reverse(Visited,Answer).
9
General depth-first search
Suppose that the following predicates are provided:
• init(State): Gives the initial state
• goal(State): State is a goal state
• action(State1,State2): There exists some
action (move, edge, etc) taking us from
State1 to State2
This is enough to write a general depth-first
problem solver (for problems with a finite
number of states). Very similar to path of
the previous slide.
10
%% df_search(Path): True if Path is a path taken
%% from S0 to a goal state
df_search(Path) :init(S0),
df_search([S0],Path).
%% df_search(Partial,Path): True if Partial can
%% be extended into a path Path to a goal state
df_search([S|Visited], [S|Visited]) :goal(S).
df_search([S1|Visited], Path) :action(S1,S2),
\+member(S2,[S1|Visited]),
df_search([S2,S1|Visited],Path).
Example, encoding of the previous problem:
init(a).
goal(c).
action(X,Y) :- edge(X,Y).
11
Maze example
Larger example: A small game, a maze with
locked doors and coloured keys.
State: Current room (a–e), carried keys (blue,
green, etc). Start with state(a,[no key]).
Actions: Pick up a key (if present), walk
through a door (if carrying the right key).
Predicates has key(Room, Key) and door(R1,
R2, Key) encodes the actual maze.
12
Possible description:
has_key(a,blue_key).
has_key(d,green_key).
has_key(c,red_key).
door(a,b,no_key).
door(b,d,blue_key).
door(a,c,green_key).
door(a,e,red_key).
init(state(a,[no_key])).
goal(state(e,_)).
%% Action: walk through door
action(state(X,Keys),state(Y,Keys)) :(door(X,Y,Key) ; door(Y,X,Key)),
member(Key,Keys).
%% Action: pick up key
action(state(X,Keys),state(X,[Key|Keys])) :has_key(X,Key),
\+member(Key,Keys).
13
Solution:
| ?- df_search(P).
P = [state(e,[red_key,green_key,blue_key,no_key]),
state(a,[red_key,green_key,blue_key,no_key]),
state(c,[red_key,green_key,blue_key,no_key]),
state(c,[green_key,blue_key,no_key]),
state(a,[green_key,blue_key,no_key]),
state(b,[green_key,blue_key,no_key]),
state(d,[green_key,blue_key,no_key]),
state(d,[blue_key,no_key]),
state(b,[blue_key,no_key]),
state(a,[blue_key,no_key]),
state(a,[no_key])] ?
14
Other search orders
With depth-first search, Prolog handles the
search order for us.
With other search strategies, we need to keep
track of this ourselves: Maintain a set of all
generated candidates (partial paths), and expand them one at a time. Pseudocode:
search([Solution|OtherPaths], Solution) :contains_goal(Solution).
search([FirstPath|OtherPaths], Solution) :find_all_moves(FirstPath, NewStates),
expand(FirstPath, NewStates, NewPaths),
insert_paths(NewPaths, OtherPaths, Paths),
search(Paths, Solution).
The order of insertion controls the search order (e.g., sort by heuristic for A*).
15
Breadth-first search
Breadth-first: Place all new candidates last.
bf_search([[Goal|Path]|Paths], [Goal|Path]) :goal(Goal).
bf_search([[State|Path]|Paths], Solution) :find_all_moves(State, NewStates),
expand([State|Path],NewStates, NewPaths),
append(Paths, NewPaths, AllPaths),
bf_search(AllPaths, Solution).
bf_search([[State|Path]|Paths], Solution) :no_moves(State),
bf_search(Paths, Solution).
% expand([b,a],[c,d], [[c,b,a],[d,b,a]])
expand(L1,L2,L3) :setof([X|L1],member(X,L2),L3).
Loop control can be added (for speed, or to
avoid infinite answers).
16
Breadth-first search in the maze
% bf_maze(Path): Path is a path from start to goal
bf_maze(Path) :init(S0),
bf_maze([[S0]],Path).
% bf_maze(Paths, FinalPath): Paths is a list of
% candidate branches, FinalPath is the solution
bf_maze([[S|Path]|_],[S|Path]) :- goal(S).
bf_maze([[S1|Path]|Partials],FinalPath) :setof(S2,action(S1,S2),NewStates),
expand([S1|Path],NewStates,NewPaths),
append(Partials,NewPaths,NewPartials),
bf_maze(NewPartials,FinalPath).
% Remove dead-end branches
bf_maze([[S1|_]|Partials],FinalPath) :\+action(S1,_),
bf_maze(Partials,FinalPath).
% expand([a,b],[c,d], [[c,a,b],[d,a,b]])
expand(L1,L2,L3) :setof([X|L1],member(X,L2),L3).
Breadth-first with loop detection is left as an
exercise. This variant expands 208 nodes!
17
Download