Let's modify the paren-checking code to identify the position in the string where the mismatch occurs:
import java.util.*; public class ParenBalancing5 { public static void main (String[] argv) { // Test 1. String s = "((()))"; int index = checkParens (s); if (index >= 0) { System.out.println ("Unbalanced: mismatch at position " + index + " in string " + s); } else { System.out.println ("String " + s + " has balanced parens"); } // ... other tests ... } static int checkParens (String inputStr) { char[] letters = inputStr.toCharArray(); Stack<Character> stack = new Stack<Character> (); for (int i=0; i<letters.length; i++) { if (letters[i] == '(') { stack.push (letters[i]); } else if (letters[i] == ')') { // Look for a match on the stack. char ch = ')'; if (! stack.isEmpty() ) { ch = stack.pop (); } if (ch != '(') { // Return the position where the mismatch occured. return i; } } } // If we've reached here, either there's no mismatch, or // the mismatch is because of unmatched parens still on the stack. if ( ! stack.isEmpty() ) { return letters.length; } else { return -1; } } }
Recall the pop() method from our first implementation of a stack in OurStack.java:
public class OurStack { char[] letters; // Store the chars in here. int top; // letters[top]: next available space. // ... other methods ... public char pop () { // Test for empty stack. if (top <= 0) { System.out.println ("ERROR in OurStack.pop(): stack empty"); // Still need to have a return statement, so we return some "junk letter". return '@'; } top --; return letters[top]; } }Note:
Exceptions:
The first step is to define an exception:
public class OurStackException extends Exception { public String toString () { return "Stack is empty: you can't pop an empty stack"; } }Note:
The next step is to re-write the pop() method to use it:
public class OurStack4 { char[] letters; int top; // ... other methods ... // The new pop() method that uses the newly defined exception. public char pop () throws OurStackException { if (top > 0) { top --; return letters[top]; } else { // Error: throw an exception throw new OurStackException (); // We don't need this: return '@'; } } }Note:
public char pop () throws OurStackException { // ... body of method ... }This is necessary because the caller needs to be ready to catch the exception, if thrown.
public char pop () throws OurStackException { if (top > 0) { // ... } else { throw new OurStackException (); } }
Finally, the caller needs to be re-written so that the call to pop() is within a try-catch statement:
public class OurStackExample4 { public static void main (String[] argv) { try { OurStack4 stack = new OurStack4 (); stack.pop (); } catch (OurStackException e) { e.printStackTrace (); } } }Note:
public static void main (String[] argv) { try { OurStack4 stack = new OurStack4 (); stack.pop (); } catch (OurStackException e) { System.out.println ("Something's very, very, very wrong here ..."); System.exit(0); } }
Exercise 1: Replace the line e.printStackTrace(); with System.out.println(e);. What do you notice is the difference? Suppose we were to add the line System.exit(0); inside the catch clause. What happens? Is it a good idea in general to add the line System.exit(0); to the catch clause of an exception?
Suppose there are two checkout lanes at the grocery store:
To simulate the coin-flip strategy:
Some details:
Exercise 2: Why does the above method work? Another way of thinking about this:
Next, let's look at the code for the random-queue simulation:
import java.util.*; public class ShortestQueue { public static void main (String[] argv) { // Four experiments with random-Queue using 100,1000,10000 and 100,000 customers. randomQueue (100, 1, 0.6, 0.6); randomQueue (1000, 1, 0.6, 0.6); randomQueue (10000, 1, 0.6, 0.6); randomQueue (100000, 1, 0.6, 0.6); // Four experiments with shortest-Queue using 100,1000,10000 and 100,000 customers. shortestQueue (100, 1, 0.6, 0.6); shortestQueue (1000, 1, 0.6, 0.6); shortestQueue (10000, 1, 0.6, 0.6); shortestQueue (100000, 1, 0.6, 0.6); } static void randomQueue (int numTimeSteps, double arrivalRate, double server1Rate, double server2Rate) { // Create two queues. LinkedList<Integer> queue1 = new LinkedList<Integer>(); LinkedList<Integer> queue2 = new LinkedList<Integer>(); // Statistics that we'll track. double sumOfWaitTimes = 0; int numCustServed = 0; // Repeat for numTimeSteps steps. for (int n=0; n<numTimeSteps; n++) { // See if arrival occurs and if so, which queue it should join. if (UniformRandom.uniform() < arrivalRate) { // Now choose a queue randomly (flip a coin). if (UniformRandom.uniform() > 0.5) { // We'll store the time-stamp for each customer. queue1.add (n); } else { queue2.add (n); } } // See if anyone completes in queue 1. if ( (! queue1.isEmpty()) && (UniformRandom.uniform() < server1Rate) ) { int arrivalTime = queue1.remove(); sumOfWaitTimes += (n - arrivalTime); numCustServed ++; } // See if anyone completes in queue 2. if ( (! queue2.isEmpty()) && (UniformRandom.uniform() < server2Rate) ) { int arrivalTime = queue2.remove(); sumOfWaitTimes += (n - arrivalTime); numCustServed ++; } } //end-for double avgWaitTime = sumOfWaitTimes / numCustServed; System.out.println ("Random queue stats:#time steps=" + numTimeSteps); System.out.println (" Average wait time: " + avgWaitTime); System.out.println (" Num left in system: " + (queue1.size() + queue2.size()) ); } static void shortestQueue (int numTimeSteps, double arrivalRate, double server1Rate, double server2Rate) { // ... we'll look at this later ... } }Explanation:
// Statistics that we'll track. double sumOfWaitTimes = 0; int numCustServed = 0; // Repeat for numTimeSteps steps. for (int n=0; n<numTimeSteps; n++) { // ... simulation details ... } //end-for double avgWaitTime = sumOfWaitTimes / numCustServed;
// See if arrival occurs and if so, which queue it should join. if (UniformRandom.uniform() < arrivalRate) { // Now choose a queue randomly (flip a coin). }
// Now choose a queue randomly (flip a coin). if (UniformRandom.uniform() > 0.5) { // We'll store the time-stamp for each customer. queue1.add (n); } else { queue2.add (n); }
queue1.add (n);
if ( (! queue1.isEmpty()) && (UniformRandom.uniform() < server1Rate) ) { // ... }Of course, we need to do this only for queues that aren't empty.
if ( (! queue1.isEmpty()) && (UniformRandom.uniform() < server1Rate) ) { int arrivalTime = queue1.remove(); sumOfWaitTimes += (n - arrivalTime); numCustServed ++; }
Finally, let's look at the equivalent code for the shortest-queue simulation:
static void shortestQueue (int numTimeSteps, double arrivalRate, double server1Rate, double server2Rate) { // ... initialization, create two queues etc ... same as above. // Repeat for given number of time steps. for (int n=0; n<numTimeSteps; n++) { // See if arrival occurs. if (UniformRandom.uniform() < arrivalRate) { // Now choose shortest queue. if (queue1.size() > queue2.size()) { queue2.add (n); } else { queue1.add (n); } } // ... the rest of the for-loop is the same ... } //end-for // ... same stats ... }Note:
Exercise 3: Download, compiler and run the above program, ShortestQueue.java: