University of Virginia, Department of Computer Science
CS201J: Engineering Software, Fall 2002

Notes: Tuesday 17 September 2002
Assignments Due

Programming Advice

Test as you go. Instead of writing all your code and then trying to compile and test it, you should be writing a little bit of code and then compiling and testing it. This way you'll know you are making progress, and discover any serious problems early on. One reason to implement toString first is because it works well with this strategy. For example, you could develop StringTable by first implementing the constructor and toString and then compiling your code and running a simple test case that creates and prints out the empty table.

Implement the normal cases first. Implement and test the normal cases before trying the abnormal cases. For example, the specification for addName is:

    public void addName (/*@non_null@*/ String key, double value) 
       // requires: The parameter name is not null.  (This is what the
       //    ESC/Java /*@non_null@*/ annotation means.)
       // modifies: this   
       // effects:  If key matches the value of String in this, replaces the value associated
       //    with that key with value.  Otherwise, inserts <key, value> into this.
       //           e.g., if this_pre = {<s0, d0>, <s1, d1>, <s2, d2>}
       //                     and s0, s1 and s2 are all different from key
       //                 then this_post = {<s0, d0>, <s1, d1>, <s2, d2>, <key: double>}.
       //                 if this_pre = {<s0, d0>, <s1, d1>, <s2, d2>}
       //                     and s1 is the same string as key
       //                 then this_post = {<s0, d0>, <s1, value>, <s2, d2>}
       //                 
       //@modifies numEntries
       //@ensures  numEntries >= \old(numEntries);
       { }  
Before worrying about the case where the key is already in the table, implement and test the normal case where it is not.

Think about your design first. A good choice of a representation will save you lots of frustration later on. Before writing and of the code, think about how difficult it will be to write all the methods with your selected representation. Consider if there are properties you could add to your rep invariant that would make it considerably easier to implement some of the methods.

If you're stuck, try something else. Its no use getting frustrated staring at the same line of code for hours on end. Switch to a different part of the program or go take a walk outside. Explaining your code to someone else also helps (usually you will discover the problem by explaining what your code is supposed to do, and the listener doesn't have to do anything!)

Today's Task

Today we will work on implementing a Graph data abstraction (introduced in Thursday's class):

public class Graph {
   // OVERVIEW: 
   //      A Graph is a mutable type that represents an undirected
   //      graph.  It consists of nodes that are named by Strings,
   //      and edges that connect a pair of nodes.
   //      A typical Graph is: < Nodes, Edges >
   //       where
   //         Nodes = { n1, n2, , nm }
   //       and 
   //         Edges = { {from_1, to_1}, ..., {from_n, to_n} }

   // Creator
   public Graph () 
      // EFFECTS: Initializes this to a graph
      //      with no nodes or edges: < {}, {} >.

   // Mutators
   public void addNode (String name)
      // REQUIRES: name is not the name of a node in this
      //  MODIFIES: this
      // EFFECTS: adds a node named name to this:
      //     this_post = < this_pre.nodes U { name }, this_pre.edges >

   public void addEdge (String fnode, String tnode)
      // REQUIRES: fnode and tnode are names of nodes in this.
      // MODIFIES: this
      // EFFECTS: Adds an edge from fnode to tnode to this:
      //       this_post = < this_pre.nodes, this_pre.edges U { {fnode, tnode} } >

   // Observers
   public boolean hasNode (String node)
      // EFFECTS: Returns true iff node is a node in this.

   public StringIterator nodes ()
      // EFFECTS: Returns the StringIterator that
      //      yields all nodes in this in arbitrary order.

   StringSet getNeighbors (String node)
      // REQUIRES: node is a node in this
      // EFFECTS: Returns the StringSet consisting of all nodes in this
      //      that are directly connected to node:
      //         \result =  { n | {node, n} is in this.edges }
}
To implement the Graph data abstraction:
  1. Select a representation for Graph. Consider carefully several different possible representations, and what their advantages and disadvantages will be.
  2. Determine the rep invariant and abstraction function
  3. Implement Graph (), addNode and hasHode
  4. Implement addEdge and getNeighbors
Don't expect to finish all of these today. If you finish 1 and 2 and make some progress on 3, you are doing well.

CS201J University of Virginia
Department of Computer Science
CS 201J: Engineering Software
Sponsored by the
National Science Foundation
cs201j-staff@cs.virginia.edu