8.9 KiB
Homework 6: Search as a Programming Technique
CSci 2041: Advanced Programming Principles, Spring 2017
Due: 11:59pm Monday, April 17
Introduction
You will solve two searching problems in this assignment. The first is a tautology checker, the second is a maze runner.
Part 1: Tautology Checker
Getting Started
Your task for this homework is to write tautology checker for logical
formulas. You should be familiar with the code we wrote for the
subsetsum
problem in lecture before starting this assignment. If
you don't understand that code, then the work below will be very
difficult.
Below are a few type definitions and functions that you will want to
use. As we've done before, place these in a file named tautology.ml
in a directory named Hwk_06
. This Hwk_06
directory should be
in your individual repository.
Our formulas are represented by the following OCaml type:
type formula = And of formula * formula
| Or of formula * formula
| Not of formula
| Prop of string
| True
| False
You will implement the tautology checker in the style that uses exceptions, specifically the style that raises an exception to indicate that the search process should continue.
Thus we will use the following exception for that.
exception KeepLooking
Our tautology checker will search for substitutions for the
propositions in the formula for which the formula will evaluate to
false
. The substitutions are similar in structure to the
environments that we had in homework 4.
We'll introduce a name for them, subst
, with the following type
definition.
type subst = (string * bool) list
Here is a function for converting substitutions to strings. We'll use this in printing our results.
let show_list show l =
let rec sl l =
match l with
| [] -> ""
| [x] -> show x
| x::xs -> show x ^ "; " ^ sl xs
in "[ " ^ sl l ^ " ]"
let show_string_bool_pair (s,b) =
"(\"" ^ s ^ "\"," ^ (if b then "true" else "false") ^ ")"
let show_subst = show_list show_string_bool_pair
You might also find these helpful
let is_elem v l =
List.fold_right (fun x in_rest -> if x = v then true else in_rest) l false
let rec explode = function
| "" -> []
| s -> String.get s 0 :: explode (String.sub s 1 ((String.length s) - 1))
let dedup lst =
let f elem to_keep =
if is_elem elem to_keep then to_keep else elem::to_keep
in List.fold_right f lst []
Formula evaluation
In the process of searching for substitutions for a formula that indicate that the formula is not a tautology, we will want to evaluate a formula for a given substitution.
This is very much like the expression evaluators that we created for homework 4, except now the environment (or substitution as we've called it here) is a part of the input of the evaluation process.
This is because our formulas have variables, here called propositions, but they don't have let-clauses that would introduce binding into an environment. So that complete environment/substitution must be passed in.
Write a function named eval
with the type
formula -> subst -> bool
that evaluates the input formula using
the input substitution to return a value of true
or false
.
In case a proposition occurs in the formula that does not appear in the substitution, just raise an exception. The tests run on your code will only provide proper substitutions (no missing binding) to evaluations.
For example:
eval (And ( Prop "P", Prop "Q")) [("P",true); ("Q",false)]
will evaluate to false
and
eval (And ( Prop "P", Prop "Q")) [("P",true); ("Q",true)]
will evaluate to true
.
"Freevars" for formulas
The search space for our tautology checker is the set of substitutions for the propositions in a formula. Thus we must determine what variables are used in a formula.
This is reminiscent of the freevars
function we wrote for
expressions in homework 4.
Write a function freevars
with the type formula -> string list
that returns the names of all the propositions in the formula. Be
sure to remove duplicates since when we create substitutions based on this
list we cannot have more than one binding for the same propositional
variable.
Here are a few evaluations.
freevars (And ( Prop "P", Prop "Q"))
evaluates to ["P"; "Q"]
and
freevars (And ( Prop "Q", Prop "P"))
evaluates to ["Q"; "P"]
. But order doesn't matter.
Here are a few examples of possible tests that may be run on your solution:
List.exists ( (=) "P" ) (freevars (And ( Prop "P", Prop "Q"))) = true
List.length (freevars (And ( Prop "P", Or (Prop "Q", Prop "P")))) = 2
Tautology Checker
Given the types and functions defined above we can now get to writing our tautology checker.
The function will be named is_tautology
and will have the type
formula -> (subst -> subst option) -> subst option
.
The first argument is, not surprisingly, the formula we wish to check.
The second argument is a function similar to the process_solution
functions we used in the implementations of subsetsum
. This
function is called when the search process finds a substitution for
which the formula evaluates to false
. The function gets this
substitution as input and can decide to accept it (perhaps by
interacting with the user) and in that case returns that substitution
in a Some
value.
If the function does not accept the solution, it should raise the
KeepLooking
exception.
We could then use is_tautology
in some interesting ways, just
like we did for subsetsum
in lecture. First,
we could define the following to return the first substitution that is
found:
let is_tautology_first f = is_tautology f (fun s -> Some s)
Second, we could define the following to print out all substitutions
and always raise the KeepLooking
exception. It will then print
all substitutions that make the formula evaluate to false
and the
finally return None
as the value of the call to
is_tautology_print_all
.
let is_tautology_print_all f =
is_tautology
f
(fun s -> print_endline (show_subst s);
raise KeepLooking)
For the formula
Or (Prop "P", Not (Prop "P"))
your tautology checker should find that it is a tautology and never generate a substitution.
For the formula
Or (Prop "P", Prop "Q")
your tautology checker should find that it is not a tautology and generate (among others) the substitution
[ ("Q",false); ("P",false) ]
or
[ ("P",false); ("Q",false) ]
since the order in which binding appear in the substitution does not matter.
For the formula
And ( Prop "P", Prop "Q")
your tautology checker should find that it is not a tautology and generate 3 substitutions (if they are all demanded) that make the formula false.
Try
is_tautology_print_all (And (Prop "P", Prop "Q"))
and make sure you see the proper 3 substitutions.
Your functions will also be checked that they conform to the proper style and use exceptions as described in class to implement backtracking search.
Part 2: Maze Runner
Consider the maze shown below, where "S" marks the start position and "G" marks the two goal positions.
Your task is to write a function maze
that returns the first
path from an "S" position to one of the "G" positions that is found.
To keep this computation from happening when we load the file into
utop, the function should take ()
the unit value as input and
return a list of pairs on integer representing a path, wrapped up in
an option
type.
This function will have some things in common with the wolf-goat-cabbage problem that we solved in class, so make sure you understand that before attempting this one.
It is suggested that you write a function maze_moves
that takes a
position as input with the type int * int
and then returns a list
of positions, with type (int * int) list
that are all the
positions that could be moved to, while respecting the boundaries and
lines in the maze. For example, one cannot move from position
(3,1)
to position (4,1)
.
You can implement this with a rather large match
expression that
just hard-codes the moves seen in the image above.
For example, your solution may execute the search in an order so that
maze ()
evaluates to
Some [ (2,3); (1,3); (1,2); (2,2); (3,2); (3,3); (3,4); (4,4); (4,5); (3,5) ]
Or, your maze
function might instead return the path
Some [ (2,3); (1,3); (1,2); (2,2); (3,2); (3,3); (4,3); (5,3); (5,2); (5,1) ]
Thus, the type of maze
must be
unit -> (int * int) list option
Your maze runner solution should use the same KeepLooking
exception to indicate when a deadend in the search has been reached or
when some other reason exists to keep looking for solutions.
Your functions will also be checked that they conform to the proper style and use exceptions as described in class to implement backtracking search.