Computer Science 60
Principles of Computer Science
Fall 2012


Assignment 4: Prolog - true! [100 points]
Due Monday, October 8 by 11:59 PM


Submitting...    For this assignment, you'll submit the two files: lists.pl and logicpuzzle.pl. As may have become second nature, this assignment has a few (now more intricate) Java problems too. The extra-credit problem, if you'd like to try it, would be submitted in a file named twentyfour.pl. Starter versions of all of these prolog files are within this zip file:

If you'd like to browse the three starter files, they're linked from here in text format (to use these, you'd need to change their names so that their file extensions are .pl instead of .txt):

Formatting and commenting...

Please use the standard commenting convention of your name, file name, and date at the top of each file. In addition, be sure to include a comment for each predicate that you implement.

Finally, please be sure to name your files and functions as noted by the problems.... The grading procedure for this assignment is partially automated and files and functions whose spellings differ from those below will help the graders a lot!




Note on Prolog's output formatting

There are several settings that control how Prolog formats its output: the following describes a few of those settings you may want to change.

You may have noticed that when answer variables get bound to long lists, swipl shows the list with '...' after about 10 elements, which can be annoying. To see the entire list, simply type

  w
following the output. For example, if the following rules are defined:
range(L,H,[]) :- L >= H.
range(L,H,[L|R]) :- L < H, M is L+1, range(M,H,R).
and then you try the query
?- range(1, 50, X).
you will see the result
X = [1, 2, 3, 4, 5, 6, 7, 8, 9|...] 
By typing a w when it pauses, Prolog will write the whole list out (all 50 elements):
X = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 
     12, 13, 14, 15, 16, 17, 18, 19, 20, 
     21, 22, 23, 24, 25, 26, 27, 28, 29, 
     30, 31, 32, 33, 34, 35, 36, 37, 38, 
     39, 40, 41, 42, 43, 44, 45, 46, 47, 
     48, 49, 50]

To change the limit on the number of elements once and for all, copy the following at the top of your source file (we have done this for the source files this week).

:- set_prolog_flag(toplevel_print_options, [quoted(true), 
     portray(true), attributes(portray), max_depth(999), priority(699)]).
You may use any other value for max_depth, including 0, which will place no limit on the depth of the expression. The default value of max_depth is set at 10, with results as shown above. The values of the other attributes are just the default ones. More on prolog's flags is available at this link.





Part 1: Lists in Prolog

[60 points, pair or individual]


Here, you will write a few Prolog predicates that express relationships about lists (and trees and graphs, expressed as lists). Please submit these rules in one file called lists.pl. This link has a starting point with placeholders. As before, you'll need to change the extension from .txt to .pl.

You may create and use any helper rules that you like. In particular, some of the rules that we saw in class may be helpful here! For example, we wrote the member predicate:

member( X, [X|_] ).
member( X, [_|R] ) :- member( X, R ).
This is built in to some versions of prolog, but not others: feel free to include it if you need it.


The Prolog predicates to write in lists.pl :



Part 2: Einstein's logic-constraint puzzles (30 Points)

[30 points; pair or individual]

Note on spelling: Be careful with the various names in this problem -- you'll want to keep the spelling consistent. In particular, be sure collette is with two l's and two t's. (We've tried to be consistent ourselves; if you see any problems, let us know!)

For this problem, your task is to write a set of prolog rules that will solve the following logic puzzle (in the spirit of Einstein's Zebra Puzzle):

/*
 * algird, bruno, collette, dino, and edwina are each from different
 * one of five colleges: pomona, pitzer, hmc, cmc, and scripps.
 *
 * Each one brings a different snack:  jots, chocolate, donuts, pez, and spam.
 * They are all on the train together in seats 1 through 5.
 *
 * We want to know which student is in each seat, what college does each
 * student attend, what did each student bring for a snack?
 * Determine whether there is a solution, and if so, whether it is unique.
 *
 * 1.  bruno and dino sat in the end seats.
 * 2.  algird sat next to the student from hmc.
 * 3.  collette sat next to friends with chocolate and donuts.
 * 4.  The hmc student brought spam as a snack and sat in the middle seat.
 * 5.  chocolate was immediately to the left of pez.
 * 6.  bruno, dino, and algird do not go to Scripps.
 * 7.  The pomona student sat between the persons with jots and spam.
 * 8.  dino did not sit next to the person with donuts.
 * 9.  The cmc student did not sit next to edwina.
 *
 * Note that negation constraints don't generate values -- they can
 * only check for consistency of already-instantiated values. Thus,
 * they must be tested _after_ variables are instantiated with values.
 *
 * Here is the solution (which is helpful to have for debugging!)
 *
 * Seats =
   [[bruno,cmc,jots],          % Seat 1
    [algird,pomona,donuts],    % Seat 2
    [collette,hmc,spam],       % Seat 3
    [edwina,scripps,chocolate],% Seat 4
    [dino,pitzer,pez]]         % Seat 5
 *
 */

Be sure to format your output in an easy-to-read manner.

In particular, you need to have a solve predicate that generates and prints the solution to the five-colleges logic puzzle neatly -- modeled from the solve predicate in the einstein.pl example.

That solve predicate first generates the solution to the puzzle, and then it prints the results in a reasonable fashion:

solve :-
  einstein( [ H1, H2, H3, H4, H5 ] ),
  write( ' first house: '), write(H1), nl,
  write( 'second house: '), write(H2), nl,
  write( ' third house: '), write(H3), nl,
  write( 'fourth house: '), write(H4), nl,
  write( ' fifth house: '), write(H5), nl.

This zero-argument predicate solve is called without parentheses at all, and it produces results as follows:

?- solve.

 first house: [norwegian, cat, dunhill, water, yellow]
second house: [dane, horse, marlboro, tea, blue]
 third house: [brit, bird, pallmall, milk, red]
fourth house: [german, zebra, rothmans, coffee, green]
 fifth house: [swede, dog, winfield, beer, white]

Yes

Submitting...    Be sure to submit the two files: lists.pl and logicpuzzle.pl to the usual place on the submission site



Part 3:    Java!    (10 points)

This week in Java adventures: Arrays!
[10 points; pair or individual]



Extra credit: The Game of 24! (up to +11 points)

This totally optional problem asks you to use Prolog to provide a solver for a generalized version of the game "Twenty-Four". In the original game, players view a card with four numbers on it and try to make an arithmetic combination of the numbers using the operators +, -, *, / so that the result is 24 (parentheses are allowed but are implicit). Each number must be used exactly once. Each operator can be used any number of times.

In our generalization of the game, the fixed number 24 is replaced with a variable, the set of operators is specified in a list, and the list of numbers can have any length, not just 4. (By definition, no result can be made if the list is empty.)

Define a 4-ary predicate solve such that

     solve(Ops, Values, Result, Tree)
  
will solve for an arithmetic tree named Tree using operators Ops among the integers in Values to give the final value Result. For example,
 solve(['+', '*', '-'], [2, 3, 4, 5], 24, Tree).

 Tree = [*,[+,[-,3,2],5],4] 
 

This means that we have found a solution Tree using operators +, *, and - such that the solution combines the numbers in the list [2, 3, 4, 5] to yield 24.

You may assume that the set of operators will always be a subset of ['+', '*', '-', '/'] and that '/' denotes integer division. In prolog, integer division is performed using // (which is why 1-line comments in prolog are prefaced by % and not //).

Here is an eval predicate (for evaluating trees of opreations) that will be helpful in solving this problem. You may copy this code and use it freely. Note that eval can handle numbers and it can handle trees that are bound to some value (using the nonvar predicate) Thus, the eval predicate can not generate trees. Allowing eval to generate trees can lead to infinite recursion, as it looks in the (unbounded) space of trees for operations possibly leading to R.

    eval(R, R) :- number(R).
    eval(['+', A, B], R) :- nonvar(A), nonvar(B), eval(A, AR), eval(B, BR), R is AR + BR.
    eval(['*', A, B], R) :- nonvar(A), nonvar(B), eval(A, AR), eval(B, BR), R is AR * BR.
    eval(['-', A, B], R) :- nonvar(A), nonvar(B), eval(A, AR), eval(B, BR), R is AR - BR.
    eval(['/', A, B], R) :- nonvar(A), nonvar(B), eval(A, AR), eval(B, BR), BR\==0, R is AR // BR.
 
You may use any of Prolog's built-in rules and any helper rules that you like (including those that we wrote in class).

Here are some other examples of solve in action. Warning: You may especially want to check the second of these. In particular, a common partially working solution to this problem handles the first example below, but not the second example. Note how the list of values is being split into sublists in the second case. Be careful to only allow non-empty splits; otherwise, you'll end up with an infinite recursion!

  solve(['+', '*', '-'], [1, 2, 3, 4], 24, Tree).

  Tree = [*,1,[*,2,[*,3,4]]]    % other solutions are certainly possible!



  solve(['+','-','*','/'], [2,8,22,424], 42, Tree).  % only one solution possible!

  Tree = [-,[/,424,8],[/,22,2]]



  solve(['+'], [1, 1, 1, 1], 24, Tree).   % no solution

  No.



  solve(['+'], [24], 24, Tree).    % base case

  Tree = 24
 
Notice that a plain integer is the simplest possible solution tree (not the one-element list, [24]). Also, there are many more answers to the first example above. Be careful, too, that your splitting of the input values (numbers) allows for rearrangements -- otherwise the middle example, above, will not work!

For +5 points, your solve predicate should work as above, generating trees (with a value provided for Result). For the final +6 points, your predicate should also be able to generate the Result value (as well as the tree):

?- solve(['+', '*', '-'], [1, 10,100, 1000], N, Tree).

N = 1111,
Tree = [+,1,[+,10,[+,100,1000]]] ;

N = 1110,
Tree = [*,1,[+,10,[+,100,1000]]] ;

N = -1109,
Tree = [-,1,[+,10,[+,100,1000]]] ;

N = 11001,
Tree = [+,1,[*,10,[+,100,1000]]]   % and many, many more...
Note that the eval predicate given can check or generate the result value R.

Please submit this extra-credit part of the assignment in a file called twentyfour.pl.