--------------------------------------------------------------------------
COMP 731 - Data Struc & Algorithms - Lecture 15 - Thursday, August 3, 2000
--------------------------------------------------------------------------
Today:
o Program abstraction / programming style
- a solution to PS3
I was pleased that people managed to get the programs done and working.
But I felt that the programs were, without exception, poorly abstracted ;-(
So: the first part of today's lecture was spent discussing some general
guidelines for the writing of better programs. Among the guidelines:
1. Make the structure of the program mirror the high-level ideas
of the problem; find clear abstraction boundaries!
This is the most difficult aspect of programming, the most
important, and the most intangible and difficult to teach.
You have to find the "good", "clean" abstraction boundaries.
2. Use the features of the language you are working in. In particular,
when writing a C++ program, use classes to implement ADTs.
I don't recommend use of inheritance for programs of the sort we
do in this class -- but proper use of classes will result in nicer
programs.
3. Avoid excessive use of global variables. Have your programs
communicate through arguments/messages passing.
4. Choose your data structures carefully, weighing all the possibilities
and options.
5. Comment code appropriately.
6. Make sure that one subroutine is doing ONE thing. This one thing
should be clear enough to describe in a brief comment. This is of
course part of (1).
7. Keep the code of any one routine short -- normally, less than one
page.
8. Express ideas "directly" in the programming language, sticking to common
conventions (eg., the C/C++ use of 0 for false, nonzero for true -- not, eg.,
the characters "t" and "f").
9. Make sure you spend time on what matters. Use a profiler to
see where execution time is being spent. For example, in program 2, I believe
that most of the time is now being spent in on the pseudorandom number
generator, so optimizing other parts of the program won't much
impact performance.
I now went over my solution to program 3. It's not perfect (eg., I should
probably be using constructors instead of Makeset() and AddVerticies(), but
hopefully it at least illustrates finding better abstractin boundaries.
I omit
void error (char* s)
and
int random(int n)
(the program won't run until you add these routines).
I wrote these, but you really should probably take these from a library, instead.
_________________________________________________________________________________
/*
* prog2.cpp
*
* Estimate F(n) for various values of n
* Phillip Rogaway
* COMP 731 - Term 1 of 2000
*/
#include
const nmax = 1000; // maximum size graph program can handle. Also max number of sets
//
// The standard "union/find data structure", implemented using union-by-rank (but
// not bothering with collapsing-find). The elements in the universe have names 1..N.
// Union allows arbitrary names. We also keep track of the number of disjoint
// sets in the collection, and return this value as a member function.
//
class Sets
{
struct Pair {
int parent;
int rank;
};
Pair L[nmax+1];
int N; // elements have names 1..N, where N<=nmax
int num_disjoint_sets; // number of disjoint sets
public:
void Makesets(int n); // make n disjoint sets. (should be constructor)
int Find(int x); // return the canonical name for the set containing x
void Union(int x, int y) // union the sets containing x and y
int NumDisjointSets() {return num_disjoint_sets;} // number of disjoint sets
};
void Sets::Makesets(int n)
{
if (n<1 || n>nmax) error("Sets::Makesets() - Bad argument");
N = n;
num_disjoint_sets = n;
for (int i=1; i<=n; i++) {
L[i].parent = i;
L[i].rank = 0;
}
}
int Sets::Find(int x)
{
if (x<1 || x>N) error("Sets::Find() - Bad argument");
while (L[x].parent!=x) x = L[x].parent;
return x;
}
void Sets::Union(int x, int y)
{
if (x<1 || x>N || y<1 || y>N)
error("Sets::Union() - Bad argument");
int a = Find(x);
int b = Find(y);
if (a==b) return;
num_disjoint_sets--;
if (L[a].rank > L[b].rank) {L[b].parent = a;}
else if (L[b].rank < L[a].rank) {L[a].parent = b;}
else {L[b].parent = a; L[a].rank++; }
}
//
// A graph G=(V,E) is represented by n=|V|, m=|E|, and a compacted adjacency
// matrix, which records, as a seqeunce of bits, Adj[1,2], Adj[1,3], ..., Adj[n-1,n].
// We also maintain the set of components of G, as a set of sets, s.
//
class Graph
{
int n; // number of verticies
int m; // number of edges
unsigned long A[nmax*(nmax-1)/2 / 32 + 1]; // compacted adjacency list
Sets s;
public:
int NumEdges() {return m;} //returns the number of edges in the graph
void AddVerticies(int N); //replace the graph by N isolated verticies
int HasEdge(int i, int j); //return true iff {i,j} is an edge of the graph
void AddEdge(int i, int j); //modify the graph by adding edge {i,j}
void AddRandomEdge(); //modify graph by adding a random absent edge
int NumComponents() {return s.NumDisjointSets();} //return # of components
};
void Graph::AddVerticies(int N)
{
n = N;
m = 0;
s.Makesets(N);
for (int i=0; i <= n*(n-1)/2/32; i++) {
A[i] = 0;
}
}
int Graph::HasEdge(int i, int j)
{
if (i<1 || i>n || j<1 || j>n) error("Graph::HasEdge - Bad arguments");
if (i>j) {int temp = i; i=j; j=temp;}
if (i==j) return 0;
int pos = (i-1)*n - i*(i-1)/2 + j-i-1;
int high = pos >> 5;
int low = pos & 0x1f;
return ((A[high] & (1<n || j<1 || j>n) error("Graph::AddEdge - Bad arguments");
if (i==j) error("Graph::AddEdge - Attempt to add a self loop");
if (i>j) {int temp = i; i=j; j=temp;}
int pos = (i-1)*n - i*(i-1)/2 + j-i-1;
int high = pos >> 5;
int low = pos & 0x1f;
if ((A[high] & (1<1)
G.AddRandomEdge();
nedges += G.NumEdges();
}
return (double)nedges/ntimes;
}
/*
* main
*/
void main (int argc, char** argv)
{
cout << "COMP 731 - Assignment 3" << endl
<< "Program to estimate F(n) values" << endl << endl;
cout << "Estimate for F(4) = " << EstimateF(4) << endl;
for (int n=10; n<=100; n+=10) {
cout << "Estimate for F(" << n << ") = " << EstimateF(n) << endl;
}
for (n=200; n<=1000; n+=100) {
cout << "Estimate for F(" << n << ") = " << EstimateF(n) << endl;
}
}