COSC 1030 Introduction to Computer Science II
Assignment 4: More on Recursion
Due: Wednesday, April 9, 1997, 10:30 am (SHARP)

1.1 Learning Objectives:

1.2 What is given

1.3 What to hand in

I would recommend reading over and thinking about the entire assignment before starting any of it.


2 Multiple Access and Garbage Collection

In some applications, the same object will be used in a number of different contexts (eg., in the polynomial (x+y)*z + y/(x+y), the sub-polynomial (x+y) appears twice). Space can be saved by not having many copies of this object. Instead, each context will have a pointer to this same object in memory. However, problems arise if one context wants to change or delete the object when the others do not. We will not allow an object to be changed once it is created. Therefore, the only problem is with deletions.

The problem is as follows. Suppose that a context is pointing at an object and no longer needs it. It cannot free the object for fear that another context is using it. On the other hand, it cannot drop the object for fear that if no other context is pointing at it, a memory leak will be caused. Garbage collection solves this problem by keeping track of whether the object is being pointed at and freeing the memory space only when no pointer is pointing at it.OOT does not provide garbage collection. This is your first task. Before reading on, think. What is the minimal information that needs to be maintained about an object to know when it can be freed? Is it required to keep a full list of who is pointing at it?

You are to modify the class Node in treeNode.tu in the following ways:

  1. Add the variable nPointingAtMe that is to always contain the number of pointers pointing at the Node object. What value does this variable need to be initialized to (ie. required value when the object is first created)?
  2. Write a routine that allows the user to point another pointer at this same object.
    Instead of using:
        new p
        q := p
    the user must use:
        new p
        q := p->PointAtMe()
  3. Write a routine that sets a pointer to nil that previously pointed at a Node object.
    Instead of using
        free p
    the user must use:
        p := p->FreeMe()
    Besides setting p to nil, the routine will free the object if no other pointer is pointing at it. Note that FreeMe is supposed to free itself (suicide). My first attempt at this was with the OOT line "free self". Recall "self" is a pointer to the object itself. This however did not work. Why doesn't it work? My second attempt worked as follows.
        var p : ^Node := self
        free p
  4. Use a flag to indicate whether debug statements should be printed. When the flag is set to true, the code on the left should have the results on the right. (See the file circNode.dem)
  5. var p,q,r : ^Node
    new p
    p->info := "fun"
    q := p->PointAtMe()
    r := q->PointAtMe()
    p := p->FreeMe()
    q := q->FreeMe()
    put r->info
    r := r->FreeMe()
    if p=nil & q=nil & r=nil then
        put "ok"
    end if
    put "New count is ", Debug.count0
    put "Free count is ", Debug.count1
    assert(Debug.count0 - Debug.count1 = 0)
    put "#'new = #'free"



    Pointing: nPointingAtMe = 2
    Pointing: nPointingAtMe = 3
    Dropping: nPointingAtMe = 2
    Dropping: nPointingAtMe = 1
    fun
    Freeing: nPointingAtMe = 0
    ok


    New count is 1
    Free count is 1

    #'new = #'free
  6. If your program works, you do not have to hand in this output. If your program does not work, indicate this.

Note: With PolyCircuits memory management becomes more difficult than with PolyTrees. The Debug module now contains 10 counters. Use the counter0 to count the number of times a new Node is created, and counter1 to count the number of times a Node is freed. The difference, counter0 - counter1, should be zero after all PolyCircuits have been finalized. By printing the values of counter0, counter1 and their difference, you have useful information about how your program manages memory, and how compact is the representation of your circuits.


3 Circuits Instead of Trees.

A circuit is defined to be a collection of gates (in this case +, -, *, and / gates) in which the output of one gate can feed into any number of other gates. This is in contrast to a formula in which the circuit is a tree. You are to modify the class PolyTree in polyTree.tu from assignment 3 to be the class PolyCircuit in polyCirc.tu.

  1. Globally change the word "Tree" to "Circuit".
  2. Use your modified Node class by replacing the appropriate code with q := p->PointAtMe() and
    p := p->FreeMe(). At this point the program should work as a polyTree.
  3. Change the routine Operator so that h->Operand(f,"+",g) does not initialize f and g as the empty polynomial. Instead, f and g will continue to point at the same polynomial that they did. The result is shown in the left side of the following diagram. The result of h->Operand(f,"+",f) is shown on the right side of the following diagram. Be sure to handle the cases h->Operand(h,"+",f), h->Operand(f,"+",h), and h->Operand(h,"+",h).
  4.        h
            \_____
             | + |
     f       -----       g
      \_____/     \_____/
       |   |       |   |
       -----       -----    
    
           h
            \_____
             | + |
             -----  
            /     \
          f \_____/
           \_|   |
             -----    
    
  5. Change the routine Copy so that it does not create new nodes, but only copies the pointer, called a shallow copy. The original Copy procedure was a deep copy.
  6. The recursion for the Finalize operation must be moved to FreeMe. Suppose for example that node u is pointed to by many pointers and that node v is pointed to by node u, but by no other pointers. Think about what should happen when FreeMe is called for u. After FreeMe has been called for u enough times so that the counter nPointAtMe goes to zero the node must freed. What then happens to node v?
  7. Think. What else needs to be changed (if anything)? If something else needs to be changed then remove it from polyCirc.tu and put it into poly2.tu.
  8. Test the program to make sure that it works. You can use the tests from assignment 3. Again, if it works, no need to hand in these tests. If it doesn't work hand in the tests and indicate what does not work.


4 Questions


5 Recursive Programs

Add the following routines to PolyCircuit. Like the recursive programs in assignment 3, each of these routines will require a recursive helper routine. The helper routine is passed a pointer to the root Node of the PolyCircuit.

  1. Postfix notation has the advantage of not requiring brackets to specify order of operations. Write a routine Postfix() that prints the polynomial self on a single line using postfix notation. The following table shows the correspondence between postfix notation and infix notation.
  2. Infix notation
    x+y
    (x+y)*(a+b)
    ((a+b)*c)/d
    a+(b*(c/d))
    Postfix notation
    x y +
    x y + a c + *
    a b + c *d /
    a b c d / * +
  3. Write a routine Derivative( f : ^PolyCircuit, variable : string ) that initializes the polynomial self to be the derivative of f with respect to the indicated variable, eg ff->Derivative( f, "x") gives ff := df/dx. The poly f should remain unchanged. The program will consider the following cases:
  4. The polynomial created by Derivative will not be in the simplest form. For example, the derivative of x*y with respect to x will be computed to be 1*y + x*0. This should be simplified to y. Write a routine Simplify() that will make these simplifications. It should recursively apply the following rules.
    1. 1*f = f      f*1 = f      0*f = 0     f*0 = 0
      0+f = f      f+0 = f      f-0 = f
      f/1 = f      0/f = 0
      

    The following is an example of how the program works.

    Polynomial to simplify
            _ * _
           /     \
        _ + _   _ * _    =  (yz + 0x) * (0x * z)
       /     \ /     \
      *       *       z
     / \     / \
    y   z   0   x
    
    Recursively simplify the left subpoly, giving
          ___ * _
         /       \
        /       _ * _    =  (yz) * (0x * z)
       /       /     \
      *       *       z    (Note that only one link to 0x was freed)
     / \     / \
    y   z   0   x
    
    Recursively simplify the right subpoly, giving
          _ * _
         /     \
        *       0        =  (yz) * (0)
       / \   
      y   z
    
    Check to see if any of the three rules applies. In this case the
    second one does. Use FinalizeRec to recursively free the left
    subtree. Use FreeMe to free the root "*" node. Return a pointer to the
    0 node.  (Recall that a node cannot be changed once it is
    created. See section 2.)
    


6 Running the Demo Program

Create your own example demonstration input file in mysample.in. It should be approximately the same length as sample.in.

Copy polyCircuit.dem, sample.in and rundemo to your directory containing your PolyCircuit program. Execute the shell script program rundemo. Make sure you have set its protection mode to executable. The script runs the demonstration program polyCircuit.dem on sample.in and outputs a comparison of your output with sample.out. It then runs polyCircuit.dem on your test file mysample.in. The output is put into the file rundemo.out.