cs205: engineering software? |
(none) 20 September 2010 |
For the next question, consider these two specifications for the sort procedure:
A. From the Java 2 Platform API documentation (java.util.Arrays):
public static void sort(int[ ] a) Sorts the specified array of ints into ascending numerical order. The sorting algorithm is a tuned quicksort, adapted from Jon L. Bentley and M. Douglas McIlroy's "Engineering a Sort Function", Software-Practice and Experience, Vol. 23(11) P. 1249-1265 (November 1993). This algorithm offers n*log(n) performance on many data sets that cause other quicksorts to degrade to quadratic performance. |
B. From the textbook (p.46):
public static void sort(int[ ] a) MODIFIES: a |
1. (10) Describe three advantages of specification B over specification A.
2. (5) Describe one scenario where specification A is more useful.
public static int [] histogram (int [] a) // REQUIRES: a is non-null and the values in a are non-negative. // EFFECTS: Returns an array, result, where result[x] is the // number of times x appears in a. The result array has // maxval(a) + 1 elements. For example, // histogram ({1, 1, 2, 5}) = { 0, 2, 1, 0, 0, 1 }Note that the REQUIRES is needed. The implementation provided would produce a run-time exception if its argument includes a negative value.
public static int [] histogram (int [] a) throws NegativeValue // EFFECTS: If a contains any negative values, throws NegativeValue. // If a is null, throws a NullPointerException. // Otherwise, returns an array, result, where result[x] is the // number of times x appears in a. The result array has // maxval(a) + 1 elements. For example, // histogram ({1, 1, 2, 5}) = { 0, 2, 1, 0, 0, 1 }(Note we need to create the NegativeValue exception class.)
The advantage of this specification is the behavior is predictable when an array containing a negative value is passed in. The disadvantage is it requires a more complex implementation.
public static int [] histogram (int [] a) // EFFECTS: If a is null, returns { }. Otherwise, // returns an array, result, where result[minValue(a) + x] is the // number of times x appears in a and minValue(a) is the lowest value // in a. The result array has maxValue(a) - minValue(a) + 1 // elements. For example, // histogram ({1, 1, 2, 5}) = { 2, 1, 0, 0, 1 } // histogram ({-2, 0, 1, -2}) = { 2, 0, 1, 1 }
Another option is to return a value table:
public static java.util.HashMapThis may be more useful for the client, but requires changing the return type to a data type that can represent a mapping (such as the java.util.HashMap used here.histogram (int [] a) // EFFECTS: Returns a HashMap where the value associated with x in // the result is the number of times x appears in a. That is, // if result.containsKey (x) // appearances of x = result.get (x) // else // appearances of x = 0.
6. (17 points for code, 8 points for tests) Impement the task schedule as described above. Your program should take a file name as input, and output a valid schedule and completion time for completing the first task in the file. You implementation should be total: no matter what input it is given, it should behave in a sensible way. You should use procedural abstraction to avoid code duplication and enhance the clarity of your implementation.
You should implement your program by creating a new TaskScheduler class (in the ps2 package) with a main method.
package ps2; import java.util.Scanner; import java.util.Vector; import java.io.*; public class TaskScheduler { static private void userError(String s) { System.out.println("Error: " + s); throw new RuntimeException ("Unable to continue"); } static private Task lookupTask(String s, Set<Task> tasks) // EFFECTS: If s names a task in tasks, returns the task with the // matching name. Otherwise, returns null. { for (Task t : tasks) { if (t.getName().equals(s)) { return t; } } return null; } static private void scheduleTask(Task t, Vector<Task> schedule, DirectedGraph<Task> tgraph, int depth, int maxdepth) // REQUIRES: t is a task in tgraph, and schedule is a valid task schedule. // A valid schedule is a schedule in which every // task in schedule is preceded by the tasks on which it // depends in tgraph. // The value of depth is the number of nested calls to scheduleTask. // MODIFIES: schedule // EFFECTS: If depth is greater than maxdepth, issues an error message // and exits. Otherwise, schedule_post is a valid task schedule that // includes task t, and all of the tasks in schedule_pre. { if (depth > maxdepth) { userError ("The input graph contains a cycle!"); } if (schedule.contains(t)) { // Already scheduled, nothing to do. } else { Set<Task> depends; try { depends = tgraph.getAdjacent(t); } catch (NoNodeException e) { throw new RuntimeException("Requires violated, no node: " + e); } // Schedule dependent tasks for (Task dt : depends) { scheduleTask(dt, schedule, tgraph, depth + 1, maxdepth); } // All dependent tasks done, ready to do t schedule.add(t); } } public static void main(String[] args) { String maintask = null; Set<Task> tasks = new Set<Task>(); DirectedGraph<Task> tgraph = new DirectedGraph<Task>(); if (args.length < 1) { userError("Usage: the first parameter must be the name of a file."); } // Java compiler (unnecessarily) complains about possible // non-initialization without this. Scanner s = null; try { s = new Scanner(new File(args[0])); } catch (FileNotFoundException e) { userError("Unable to open file: " + e); } // Read task list file. Each line in the file describes a task. // entry ::= <name> <time> <depends> // depends ::= { <name>* } // The first entry is the main task (that must be completed). while (s.hasNextLine()) { // Read one line String line = s.nextLine(); if (line.startsWith("#")) { // comment line, no tokens } else { Scanner linescanner = new Scanner(line); String name = linescanner.next(); if (maintask == null) { maintask = name; } int time = linescanner.nextInt(); String lbrace = linescanner.next(); if (!lbrace.equals("{")) { userError ("Task line does not have dependency list (expected {):" + line); } Vector<String> depends = new Vector<String>(); boolean gotbrace = false; while (linescanner.hasNext()) { String dname = linescanner.next(); if (dname.equals("}")) { gotbrace = true; break; } depends.add(dname); } if (!gotbrace) { userError ("Task line dependency list is missing closing brace:" + line); } if (lookupTask(name, tasks) != null) { userError ("Duplicate task in input file: " + name); } Task t = new Task(name, time, depends .toArray(new String[depends.size()])); tasks.add(t); try { tgraph.addNode(t); } catch (DuplicateException de) { throw new RuntimeException ("Unexpected exception: " + de); } } } if (maintask == null) { userError ("No main task: the input file must contain at least one task."); } for (Task t : tasks) { String[] dependents = t.getDependencies(); for (String d : dependents) { Task dt = lookupTask(d, tasks); if (dt == null) { userError ("Dependent task for " + t.getName() + " not listed: " + dt.getName()); } try { tgraph.addEdge(t, dt); } catch (DuplicateException de) { userError ("Duplicate dependency for " + t.getName() + " on " + dt.getName()); } catch (NoNodeException nne) { System.err.println ("BUG: No node error: " + nne); } } } Vector<Task> taskOrder = new Vector<Task>(); scheduleTask(lookupTask(maintask, tasks), taskOrder, tgraph, 0, tasks.size() + 1); int timerequired = 0; for (Task t : taskOrder) { timerequired += t.getTime(); } System.out.println("Schedule: " + taskOrder); System.out.println("Completion time: " + timerequired); } }
7. (10) Write a specification for your program. Your specification should be total: it should describe what the program does on all possible inputs.
EFFECTS: If the command line arguments do not contain at least one element, and the first element is not the name of a readable file that satisfies the file format described below, the output is an error message. Otherwise, outputs a valid schedule for completing the first task listed in the input file. A valid schedule is a schedule in which: (1) only tasks that must be completed to complete to first task are included, and (2) every task is preceeded by the tasks on which it depends.The input file is described by this (somewhat informal) grammar:
Line ::= CommentLine | TaskLine CommentLine ::= # any characters up to and including the end of line TaskLine ::= Name Number { DependsList } end of line DependsList ::= DependsList ::= Name DependsList Name ::= a sequence of non-space printable characters Number ::= a sequence of characters that can be parsed as an integerEvery task name that appears in a DependsList must also appear as the Name of a TaskLine. The same Name may not appear as the Name on more than on TaskLine; the same Name may not appear more than once in a DependsList. The file must describe an acyclic graph. That is, if a given task A depends on task B, there must be no path in the graph from B to A.
There are many tests needed to try all paths through the specification. In particular, we should try:
main 10 { a b } a 10 { c } b 10 { c } c 5 { }Note that the implementation contains infinitely many possible paths, so we cannot possible test them all. But, the black box and glass box tests described about should at least cover all lines in the program.
Here are the tests I ran (for people who bet more than 0 points for question 9):