[UMN logo]

CSCI 5106: Programming Languages
Spring 2006, University of Minnesota
Assignment 8
Comments on Grading


General Remarks

This assignment was graded out of 20. The break up was as follows: Problem 1 carried 2 points, Problem 2 carried 4 points (2 points for each part), Problem 3 carried 6 points, Problem 4 carried 3 points and Problem 5 carried 5 points. The highest score on this homework was 19.9, the mean was 13.79, and the standard deviation was 4.39.


Problem 1

This question was intended to get you into the relational way of thinking: it is only append that is being used, but there are still so many different ways to use it! In case you had difficulty with this, lets work through the first part, i.e., Problem 11.2(b). Let L be the list whose last element we want to find. Then the following query would allow us to find this element:
  ?- append(_,[X],L).
We ask here what (anonymous) list when appended to the list with X as its sole element would yield L and through this process get a binding for X. Another way to think of this is the following: suppose we want to define the relation last_element between an element and a list. Here is how we might do it:
  last_element(X,L) :- append(_,[X],L).
If you were not able to get this solution earlier but understand the comments here, try your hand again at Problem 11.2(e) from the book.


Problem 2

Two points were reserved for each predicate definition.

Defining permutation should not be too difficult once we have select. Clearly the only permutation of the empty list is the empty list itself. Now, when is the list Y a permutation of the (non-empty) list X? Obviously when the first element of Y is some selected element of X and the rest of Y is a permutation of the remaining elements of X. Once you have reasoned this way, it is easy to write down the definition of permutation:

  permutation([],[]).
  permutation(X,[E|Z]) :- select(E,X,Rest), permutation(Rest,Z).

If you understand the way things work in this case, it should not be too difficult to also construct a definition of mergelists. Incidentally, at least one student wondered what the difference was between mergelists and append. What mergelists requires is that the order of elements of each of the given lists be preserved. The predicate append imposes a stronger requirement: not only must the relative order within each list be preserved, but also every element of the first list must precede the elements of the second list. When looked at this way, we see that mergelists potentially has many more solutions than does append for the same lists given as input. This is also illustrated by one of the examples on the HyperNews page for hw8.


Problem 3

This problem was intended to get you to see the similarity between first-order terms and the representations of data that become possible in ML through datatype declarations. You simply had to designate a function symbol to represent each of the logical symbols. For instance, using lor, land and lnot to represent the obvious logical symbols, we can encode the formula shown in Problem 2 of HW7 by the term land(lor(pv("p"),pv("q")),lnot(pv("q"))). We have chosen to represent propositional variables using the function symbol pv together with a string corresponding to the name of the variable. Such a choice of representing propositional variables can lead to programmability that can be useful in other symbol manipulation tasks. However, for this simple example, it would have been fine to use constants of, so to speak, the same name. Thus, we might use the constant p to represent the variable p in the encoding of expressions in propositional logic.

To represent a truth assignment, we might use a list of pairs of two components: a propositional variable and the truth value for it. Thus, the list [pr(pv("p"),ltrue), pr(pv("q"),lfalse)] might represent a truth assignment relevant to the logical expression whose encoding we have just described.

The definition of istrue should not be difficult to write: we have discussed how to define a predicate or function on recursively defined data a few times now. Assuming we have this definition in hand, then defining isnottaut is not difficult: we have to collect all the propositional variables in the given expression, generate one truth assignment after another for these variables till we find one that leads to a failure with istrue. Thus, we get the definition

  isnottaut(F) :- 
      prop_vars(F,PVs),assignment(PVs,L),not(istrue(F,L)).
Think of defining prop_vars and assignment to complete the definition of isnottaut. A hint for defining assignment: you may find it useful to add the following clauses to your program:
  assign_one(pv(PV),lfalse).
  assign_one(pv(PV),ltrue).
This predicate allows us to pick a truth assignment for any given propositional variable.

Once you have a definition of isnottaut you can define istaut in the obvious way:

  istaut(F) :- not(isnottaut(F)).


Problem 4

We have talked about the relevance of the order of goals in class, and so you should now know the difference between the two goals shown in this problem. The first query fails: X is instantiated to [1,2,3] and then member(a,[1,2,3]) fails. The second query runs endlessly. The reason for this is that member(a,X) has infinitely many solutions---each characterized by the location of occurrence of a in the list bound to X---each of which will be explored but none of which can unify with [1,2,3]. At some point Prolog would have called too many procedures for the available stack space and a stack overflow error would result.

While many of you got the general spirit of what is going on, very few of you really understood how to draw a search tree. This is described quite clearly in the book and you would do well to check this out before the exam.


Problem 5

It was pleasant to see that many of you got a correct answer to this problem. In some cases where you had not got it right, you would have known as much if you had only looked at the result of test runs: testing is useless if you do not also look at the outcome of tests!

For the first part of this problem, you had to define enumerate_interval and safe. The following could serve as a definition of the first predicate:

enumerate_interval(Low,Hi,[]) :- Low > Hi.
enumerate_interval(Low,Hi,[Low|Rest]) :- 
         Low =< Hi, N is Low + 1, enumerate_interval(N,Hi,Rest).
In defining this predicate, some of you wrote rather convoluted Prolog code. For example, you wrote a definition that worked down from Hi and used append to add this element at the end of the list. This is quite inefficient as we specifically noted in conjunction with naive reverse. We were somewhat generous in grading in this case, this being the last homework. You should note, however, that writing well-structured, readable and efficient definitions is not an incidental especially in a programming lanuages course. That some code "works" is often secondary to the other attributes mentioned.

We will let you ponder the definition of safe and nqueens_aux (for the other solution to the N-Queens problem). Something to note regarding safe: the representation we have chosen for the board position already ensures that no two queens can be in the same column. Thus, it is not really necessary to check this property explicitly in the definition of safe.

Some of you are having difficulty with the list notation and you should practice this a bit to get the wrinkle out. For example, [X|[]] is a rather complicated way of writing [X]. There are a few more things of this sort we would have noted in the hard copy you turned in and you should make sure you understand the comments.


Last updated on May 10, 2006 by gopalan atsign cs.umn.edu and xqi atsign cs.umn.edu