As always, you need to document predicate modes for
every predicate you
write, and test
your code adequately. You are still NOT allowed to use library
predicates other than the ones you could use in the previous homework.
However, unlike the previous assignment, you are allowed to use
extralogical constructs such as cut and not (of course, only in
appropriate contexts). The general lesson to draw for Prolog
programming is that you should think purely declaratively, but can use
non-declarative constructs when absolutely needed AFTER spending some
time thinking about whether you really need them.
Problem 1
In class we started the wolf-goat-cabbage problem as follows:
%
The state is a list
[F,W,G,C], representing the banks of the farmer, wolf, goat,
% and cabbage,
respectively.
unsafe([B1,B2,B2,_])
:- otherbank(B1,B2).
...
moveone([B,B,Bg,Bc],[B2,B2,Bg,Bc])
:-
otherbank(B,B2).
...
otherbank(e,w).
otherbank(w,e).
solve :-
move([e,e,e,e],[w,w,w,w]).
move(State1,State2)
:-
moveone(State1,State2),
!.
move(State1,State2)
:-
moveone(State1,State3),
not(unsafe(State3)),
move(State3,State2).
As we mentioned in class, there are 2 problems with this code: 1) it
cycles between states [e,e,e,e] and [w,w,e,e], and 2) it only gives a
yes or no answer while we want a path to be returned.
Modify the above code to eliminate these 2 problems. DO NOT
write some other implementation of the puzzle.
Problem 2
Consider the function f(n)=n if n<6 or f(n-1)*f(n-3)+2*f(n-3)
if
n>5. Such functions are notoriously inefficient in
non-imperative languages since they recompute smaller cases many times.
For this problem, you will explore two common ways to
address
this problem in the functional and logic paradigms, and evaluate the
resulting performances. First write 3 programs to implement
the
predicate f(+N,-Result), calling them f1, f2 and f3:
- The direct recursive implementation
- Use accumulator variables to pass as many 'previous' values
as
needed.
- Dynamically insert f(N,F) into the database as soon as you
compute it, using one of prolog's assert/1
predicates (you should look through the documentation
to
pick the correct assert predicate). Note that this
essentially
prevents you from ever reducing the same predicate/arguments twice.
This is called memoization (also, tabling). Some compilers
for
logic programming languages do it automatically; however, most
imperative languages are too unclean for compilers to do safe
memoization.
After getting your programs to work, compare their performances using
prolog's
time/1
predicate. Write a brief 1-paragraph summary (in clearly labeled
comments) comparing the
performances of the direct, accumulator, and memoization
approaches.
Also explain whether or not a compiler in a language without
referential transparency can implement memoization? A 1 or 2
sentence explanation suffices.
Extra note: The use of
assert
and
retract
are considered
VERY bad
prolog style and to be avoided at all costs since they are essentially
state variables. In this course (and qualifiers), their use
is
absolutely illegal except where otherwise stated. They are also
often
highly
inefficient, since Prolog compiler optimization techniques often can't
be applied in the presence of these.
Problem 3
Create a small database with father/2 and mother/2 facts (make the
first argument the parent and the 2nd argument the child). There
are no stepparents/etc. in this world. Make sure
your database is sufficient to test the predicates you will write
below. Write the following predicates (making non-specified modes as
general as you can):
- onlyChild(+Person) succeeds if Person is an only child.
- siblings(+Person,Siblings) succeeds if Siblings is the set of
Person's siblings.
- cousinNthRemoved(+Person,N,Rem,Cousin) succeeds if Cousin is
Person's Nth cousin Rem-removed.
- allGenCousins(+Person,Gen,Cousins) succeeds if Cousins is the set
of all cousins of Person in generation Gen (including 1st, 2nd, 3rd,...
cousins). We define Person's generation to be generation 0.
You may wish to check
here
for useful
definitions.
You may use Prolog's built-in higher-order predicates (bagof,
setof, findall) for this problem. See SWI's help to learn about these.
Note that you can achieve the equivalent effect using accumulators,
though that makes the code ugly.
Submission
The programs should be submitted electronically to the grader and cc'd
to me. All programs should
be in one file,
named
<firstname>_<surname>_hw9.pl.
Hints/Clarifications/Corrections
- You can get help on any built-in predicate by typing
"help(<predicate>)" from the swi prompt.
- My implementation of the wolf-goat-cabbage puzzle ran essentially
instantaneously, and it
really shouldnt take much longer since there are only 16 possible
states. SWI Prolog includes a profiler you can use if yours takes too
long - type "profile(solve).".
- You may need to use dynamic/1 and multifile/1 when
dynamically
changing the database. Use SWI's help (be careful to follow
exactly what
it says syntactically).
- If you are working with large structures, Prolog may just
display part of it, with a "..." replacing other parts.
Prolog needs to do this since structures may be infinite (and to
avoid flooding your screen even for finite structures). If
you want to see the entire structure, you can do one of two things:
- use print/1 or write/1 either in the query (e.g.
query(...),print(X)) or in the
program itself.
- Modify prolog's default print depth. To view the
current
print depth, type prolog_flag(toplevel_print_options,X). To
change the print depth, type
set_prolog_flag(toplevel_print_options,<modified X>), where you
have modified X to the appropriate print depth.