Module 8: Stacks, Queues, ADT's


Supplemental material


Stacks: a simple example

What is a stack?

Here's a simple example we saw earlier in the Introduction:

import java.util.*;

public class StackExample {

    public static void main (String[] argv)
    {
	// Use java.util.Stack, and specialize it to String's.
        Stack<String> toDoList = new Stack<String> ();

        // Add some strings.
        toDoList.push ("Pay bills");
        toDoList.push ("Clean room");
        toDoList.push ("Do homework");
        toDoList.push ("See movie");
        toDoList.push ("Hang out");
        
        // Print.
        System.out.println ("My priorities: ");
        while (! toDoList.isEmpty() ) {
            String nextPriority = toDoList.pop ();
            System.out.println (" " + nextPriority);
        }
    }
    
}
Note:

Although we have created a stack of strings, we could just as easily create and use a stack of integers or various kinds of objects.

For example, here's an example that pushes a couple of integer's and prints them out in reverse order:

import java.util.*;

public class StackExample2 {

    public static void main (String[] argv)
    {
        Stack<Integer> numberStack = new Stack<Integer> ();

        // Push some numbers
	numberStack.push (1);
	numberStack.push (2);
	numberStack.push (3);
        
        // Print in reverse order by popping off the stack.
        while (! numberStack.isEmpty() ) {
            System.out.println (numberStack.pop());
        }
    }
    
}
   

In-Class Exercise 1: Use a stack to print a string in reverse. Download StackExample3.java and add code to use a Character stack.


A stack application: balancing parentheses

We'll use a stack to check whether the parentheses in a string of parentheses are matched:

Consider this string of parentheses: "(()())"

Here's the program:
import java.util.*;

public class ParenBalancing {

    public static void main (String[] argv)
    {
	// Test 1.
	String s = "()(())()";
	checkParens (s);

	// Test 2.
	s = "((())";
	checkParens (s);

	// Test 3.
	s = ")(";
	checkParens (s);
    }

    static void checkParens (String inputStr)
    {
	// Extract letters from String.
	char[] letters = inputStr.toCharArray();

	// We'll need a Character stack.
	Stack<Character> stack = new Stack<Character> ();

	boolean unbalanced = false;

	for (int i=0; i<letters.length; i++) {

	    if (letters[i] == '(') {
		// Push left paren.
		stack.push (letters[i]);
	    }
	    else if (letters[i] == ')') {
		// Right paren: we should have a match on the stack.
		char ch = stack.pop ();
		if (ch != '(') {
		    // Not a match.
		    unbalanced = true;
		    break;
		}
	    }

	} //end-for
	
	if ( (unbalanced) || (! stack.isEmpty()) ) {
	    System.out.println ("String " + inputStr + " has unbalanced parens");
	}
	else {
	    System.out.println ("String " + inputStr + " has balanced parens");	
	}

    }

}

In-Class Exercise 2: Why do we need the second condition in the if-statement above?

	if ( (unbalanced) || (! stack.isEmpty()) ) {
	    System.out.println ("String " + inputStr + " has unbalanced parens");
            // ...
  

In-Class Exercise 3: Download, compile and execute ParenBalancing.java above. The third test results in an error. Can you see what the problem is and fix it?

Next, let's modify the above example to balance two types of parentheses simultaneously:

In-Class Exercise 4: Show the stack contents at each step for the above two examples.

Here's the program:

import java.util.*;

public class ParenBalancing3 {

    public static void main (String[] argv)
    {
	// Test 1.
	String s = "([()])";
	checkParens (s);

	// Test 2.
	s = "[][()]()";
	checkParens (s);

	// Test 3.
	s = "((())]";
	checkParens (s);

	// Test 4.
	s = "[)(]";
	checkParens (s);
    }


    static void checkParens (String inputStr)
    {
	char[] letters = inputStr.toCharArray();
	Stack<Character> stack = new Stack<Character> ();

	boolean unbalanced = false;

	for (int i=0; i<letters.length; i++) {

	    if ( (letters[i] == '(') || (letters[i] == '[') ) {
		// Push every left paren of each kind.
		stack.push (letters[i]);
	    }
	    else if (letters[i] == ')') {
		// We should have a '(' match on the stack
		char ch = ')';
		if (! stack.isEmpty() ) {
		    ch = stack.pop ();
		}
		if (ch != '(') {
		    // Not matched ⇒ unbalanced.
		    unbalanced = true;
		    break;
		}
	    }
	    else if (letters[i] == ']') {
		// We should have a '[' match on the stack
		char ch = ']';
		if (! stack.isEmpty() ) {
		    ch = stack.pop ();
		}
		if (ch != '[') {
		    // Not matched.
		    unbalanced = true;
		    break;
		}
	    }

	} // end-for
	
	if ( (unbalanced) || (! stack.isEmpty()) ) {
	    System.out.println ("String " + inputStr + " has unbalanced parens");
	}
	else {
	    System.out.println ("String " + inputStr + " has balanced parens");	
	}
    }

}

In-Class Exercise 5: Most parentheses matching applications occur in real text, where the parens are distributed inside text, such as:

if ((x < y) && (Math.abs(x)>0)) { if(x==5) {x += 3;}};  
  
Download ParenBalancing4.java and modify it to handle parentheses strewn about in real text. You will also need to add a new type of parenthesis: the curly bracket.


Another stack application: palindromes

Yet another way to check if a string is a palindrome:

Let's examine the program:

import java.util.*;

public class Palindrome {

    public static void main (String[] argv)
    {
	// Test 1.
        String str = "redder";
        System.out.println ( str + " " + checkPalindrome(str) );

	// Test 2.
        str = "river";
        System.out.println ( str + " " + checkPalindrome(str) );

	// Test 3.
        str = "neveroddoreven";
        System.out.println ( str + " " + checkPalindrome(str) );
    }
    

    static String checkPalindrome (String str)
    {
	// Extract the letters.
	char[] letters = str.toCharArray ();

	// Create an empty stack.
	Stack<Character> stack = new Stack<Character>();

	// The letters must "balance" up to the middle.
	int mid = letters.length / 2;

	// Push the first half.
	for (int i=0; i<mid; i++) {
	    stack.push (letters[i]);
	}

	// Odd or even? We have to adjust the mid-point accordingly.
	if (letters.length % 2 > 0) {
	    // Odd number => swallow middle letter.
	    mid = mid+1;
	}

	// Now check the second half.
	for (int i=mid; i<letters.length; i++) {
	    char ch = stack.pop ();
	    if (ch != letters[i]) {
		// Mismatch => not a palindrome.
		return "is not a palindrome";
	    }
	}

	return "is a palindrome";
    }

}
Note:
  • For a string of even length, the two characters at the middle have to be checked against each other
         ⇒ the two d's in redder

  • For a string of odd length, we need to ignore the middle letter
         ⇒ Ignore the v in civic

In-Class Exercise 6: Download and modify Palindrome2.java so that blanks and other punctuation are ignored in testing for palindromes. Thus, the following should test correctly as palindromes:

    Evil did I dwell; lewd I did live     // Oldest known recorded palindrome.

    A man, a plan, a canal: Panama     	  // One of the most famous.
  
You will find the method Character.isLetter() useful, as in
      if ( Character.isLetter (ch) ) {
          // ... ch is a letter (not punctuation)
      }
      else {
          // ... ch is something other than 'a' to 'z' or 'A' to 'Z'.
      }
  
One way to solve the problem is to extract all the actual letters and put that into a list, and then to use the list in checking for palindromicity.


Building our own stack

Let's build our own stack data structure:

  • We'll do this for char's
         ⇒ Use it for the paren-balancing application.

  • We'll use a simple array to hold the stack.
Sample code:
public class OurStack {

    char[] letters;         // Store the chars in here.
    int top;                // letters[top]: next available space.


    // Constructor.

    public OurStack ()
    {
	letters = new char [100];
	top = 0;
    }



    public void push (char ch)
    {
        // Note: what if top >= letters.length?
	letters[top] = ch;
	top ++;
    }


    public char pop ()
    {
        // Note: what if top < 0?
        top --;
        return letters[top];
    }


    public boolean isEmpty ()
    {
	if (top == 0) {
	    return true;
	}
	else {
	    return false;
	}
    }

}

Let's improve this stack with some error checking:

public class OurStack {

    char[] letters;         // Store the chars in here.
    int top;                // letters[top]: next available space.


    // Constructor.

    public OurStack ()
    {
	letters = new char [100];
	top = 0;
    }


    public void push (char ch)
    {
        // Test for full stack.
        if (top >= letters.length) {
            System.out.println ("ERROR: OurStack.push(): stack overflow");
            return;
        }
        
	letters[top] = ch;
	top ++;
    }


    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];
    }


    public boolean isEmpty ()
    {
	if (top == 0) {
	    return true;
	}
	else {
	    return false;
	}
    }

}
Note:
  • The error recovery from pop() is a little artificial
         ⇒ We are still forced to return something.

  • In the Supplement, we describe how to use exceptions as a better way to handle errors.

  • The stack above can be used in our paren-balancing application:
    import java.util.*;
    
    public class OurStackExample {
    
        public static void main (String[] argv)
        {
    	String s = "((()))";
    	checkParens (s);
    
    	s = "((())";
    	checkParens (s);
        }
    
        static void checkParens (String inputStr)
        {
    	char[] letters = inputStr.toCharArray();
    	OurStack stack = new OurStack ();
    
            // ... code similar to earlier examples ...
    
        }
    
    }
        

In-Class Exercise 7: Download OurStackExample2.java and modify OurStack2.java to use an ArrayList instead of an array. This way, there's no upper limit to the size. What part of the code changes from the original OurStack.java? Do we still need to check for a lower limit in pop()?

Next, let's use a linked-list instead of an array:

import java.util.*;

public class OurStack3 {

    // Use a linked list instead of an array.
    LinkedList<Character> list;


    public OurStack3 ()
    {
	// Can be unlimited in size now.
	list = new LinkedList<Character>();
    }


    public void push (char ch)
    {
        // No need to check for upper limit.
	list.add (ch);
    }


    public char pop ()
    {
	if (! list.isEmpty()) {
	    return list.removeLast();
	}
	else {
	    System.out.println ("ERROR in OurStack.pop(): stack empty");
	    return '@';
	}
    }


    public boolean isEmpty ()
    {
	return list.isEmpty();
    }

}
Note:
  • The code is a little cleaner:
    • We don't have to keep track of the "top" since the list size is exactly the stack size.
    • We can exploit list methods such as isEmpty().

  • The stack grows only as much as necessary.


ADT's

What's an ADT?

  • ADT = Abstract Data Type

  • There are many senses in which this term is used
         ⇒ We'll examine these below.

First meaning of ADT: a "standard" data structure with "well-known" operations:

  • Example: stack with operations push, pop, isEmpty.

  • Example: list with operations add, search, get(i).

  • What's important here are the meanings of the operations.

  • It's clear that the list operations are different from the stack operations.

Second meaning of ADT: the idea that implementations are hidden

  • We can implement a stack using an array, a list or use some other internal workings.

  • The internal workings shouldn't change the behavior of the operations as seen by users of the operations.

  • The internal workings could be changed without affecting the use of the ADT by others.
    • Suppose our main() method uses a stack.
    • Suppose the stack is really implemented with an array.
    • If we changed the stack code so that it uses a linked-list, then we shouldn't have to change any code in main.

Third meaning of ADT: language features that support ADT's

  • Language feature: separate implementations in separate classes (and files).

  • Language feature: Use interface's in Java.

Let's look at an example of using interfaces.

  • First, we'll re-write our balanced-parens example as:
    public class ParenBalancing5 {
    
        public static void main (String[] argv)
        {
    	String s = "()(())()";
    
            // Use this stack implementation for the first example.
            StackImpl1 stack1 = new StackImpl1 ();
    	ParenCheckTool.check (s, stack1);
    
    	s = "((())";
    
            // Use another stack implementation for the second example.
            StackImpl2 stack2 = new StackImpl2 ();
    	ParenCheckTool.check (s, stack2);
        }
    
    }
      
    Note:
    • We've put the real checking code in another class
           ⇒ We call the method check() in that class.
    • We also create a stack and pass that on to check().

  • So, what's in the class ParenCheckTool?
    public class ParenCheckTool {
    
        // NOTE: checkParens() is written to accept an interface instead
        // of an actual class.
    
        static void check (String inputStr, OurStackInterface stack)
        {
            // This code below is complete unchanged from before ...
    
    	char[] letters = inputStr.toCharArray();
    
    	boolean unbalanced = false;
    	for (int i=0; i < letters.length; i++) {
    	    if (letters[i] == '(') {
    		stack.push (letters[i]);
    	    }
    	    else if (letters[i] == ')') {
    		// We should have a match on the stack.
    		char ch = ')';
    		if (! stack.isEmpty() ) {
    		    ch = stack.pop ();
    		}
    		if (ch != '(') {
    		    // Mismatch ⇒ unbalanced.
    		    unbalanced = true;
    		    break;
    		}
    	    }
    	}
    	
    	if ( (unbalanced) || (! stack.isEmpty()) ) {
    	    System.out.println ("String " + inputStr + " has unbalanced parens");
    	}
    	else {
    	    System.out.println ("String " + inputStr + " has balanced parens");	
            }
        }
    
    }
      

  • Next, examine the file OurStackInterface.java:
    public interface OurStackInterface {
    
        // Signature, and only the signature, of the push() method:
        public void push (char ch);
    
        // Same for the pop() method:
        public char pop ();
    
        // Same for the isEmpty() method:
        public boolean isEmpty ();
    
    }
      

    Note:

    • An interface has only method signatures, no code.
    • An interface is really a specification.
    • Whichever class implements the interface must have bodies for the methods in the interface.

  • Next, let's look at the first implementation:
    public class StackImpl1 implements OurStackInterface {
    
        // An array implementation ...
        char[] letters;         // Store the chars in here.
        int top;                // letters[top]: next available space.
    
        // ... the rest of the code is the same as in OurStack.java ...
    
    }
      

  • Similarly, here's the second one:
    import java.util.*;
    
    public class StackImpl2 implements OurStackInterface {
    
        // Use a linked list instead of an array.
        LinkedList<Character> list;
    
        // ... rest of the code is the same as OurStack3.java ...
    
    }
      

  • What's important to note:
    • Suppose the class ParenCheckTool is compiled.
    • Years later, we could change the code in the implementations and in ParenBalancing5:
      public class ParenBalancing5 {
      
          public static void main (String[] argv)
          {
      	String s = "()(())()";
      
              // Use this stack implementation for the first example.
              StackImpl3 stack3 = new StackImpl3 ();
      	ParenCheckTool.check (s, stack3);
      
      	s = "((())";
      
              // Use another stack implementation for the second example.
              StackImpl2 stack2 = new StackImpl2 ();
      	ParenCheckTool.check (s, stack2);
          }
      
      }
          
    • We would not have to re-compile ParenCheckTool
           ⇒ This is a key feature of Object-Oriented Programming (OOP).


Queues

What is a queue?

  • A queue is a list.

  • When you add something to the queue, you add it to the end of the list.

  • When you remove something from the queue, you remove it from the front of the list.

The most basic Queue ADT has three operations:

  • add: add an element to the end of the list.

  • remove: remove an element from the front of the list.

  • isEmpty: see if the queue is empty.

A more powerful queue might also implement methods like:

  • get(i): get the i-th element in the queue.

  • size: how many elements are in the queue?

Consider this example of a queue that uses Java's LinkedList to serve as our queue data structure:

import java.util.*;

public class QueueExample {

    public static void main (String[] argv)
    {
        // We'll use Java's LinkedList as our queue.
        LinkedList<String> taskQueue = new LinkedList<String>();

        // Add some strings.
        taskQueue.add ("Pay bills");
        taskQueue.add ("Clean room");
        taskQueue.add ("Do homework");
        taskQueue.add ("See movie");
        taskQueue.add ("Hang out");
        
        // Now extract in "queue" order using the removeFirst() method in LinkedList.
        System.out.println (taskQueue.removeFirst());
        System.out.println (taskQueue.removeFirst());
        System.out.println (taskQueue.removeFirst());
        System.out.println (taskQueue.removeFirst());
        System.out.println (taskQueue.removeFirst());

        System.out.println ("⇒ Tasks remaining: " + taskQueue.size());
    }

}
Note:
  • Because Java's LinkedList is used for other purposes (as a linked list!), it has a few variations of "remove" methods.

  • The method removeFirst() is what we need for the queue's remove operation.

In-Class Exercise 8: Modify QueueExample.java above to print the queue after each removal. Change the actual task strings to suit your own priorities.

In-Class Exercise 9: Modify QueueExample.java above to use Java's ArrayList instead of a LinkedList. You may need to look at the Java API's to see which of ArrayList's remove methods you could use.


Queue example: a strange card game

Consider the following two-person card game:

  • There are N cards numbered 0, ..., N-1.

  • Each player is dealt M cards from a shuffled deck.

  • Each player is required to keep their pile of cards face down
         ⇒ Players can't see the cards they haven't played yet.

  • There is a common pile, initially empty, onto which players will add their cards.

  • At every turn, each player "plays" by taking the card on top of their own pile and placing that in the common pile.

  • The cards are revealed when "played" on to the common pile.

  • If, in one turn, any one player's card exceeds the other's card by by more than 2, then that player gets to keep all the cards in the common pile.

  • For example, if player 2 plays "7" and player 1 plays "3", then player 2 scoops up the common pile and adds it to the bottom of their own pile.

  • The first player to have no cards remaining loses.

In-Class Exercise 10: Find a partner and play the game. You can use small pieces of paper numbered 0, ..., 9 and start by dealing 5 cards each.

We will write a program to simulate the card game:

  • We'll use a queue for each player's pile.

  • We'll use a queue for the common pile.
Here's the program:
// This card game is a variation of the War card game described
// in the book "Object-Oriented Data Structures Using Java" by
// N.Dale, D.T.Joyce and C.Weems.

import java.util.*;

public class StrangeCardGame {

    public static void main (String[] argv)
    {
        // Use cards numbered 0,..,9 and deal 5 cards to each player.
	playGame (5, 10);
    }


    static void playGame (int dealSize, int numCards)
    {
        // Cards dealt out to player 1:
	LinkedList<Integer> player1 = new LinkedList<Integer>();
        // Cards dealt out to player 2:
	LinkedList<Integer> player2 = new LinkedList<Integer>();

        // The pile between the two players:
	LinkedList<Integer> pile = new LinkedList<Integer>();


        // Make the cards and shuffle them randomly.
	int[] cards = new int [numCards];
	for (int i=0; i<cards.length; i++) {
	    cards[i] = i;
	}
	shuffle (cards);

	// Deal cards to each player.
	int cardCount = 0;
	for (int k=0; k<dealSize; k++) {
	    player1.add (cards[cardCount]);
	    player2.add (cards[cardCount+1]);
	    cardCount += 2;                         	    // Note: += operator.
	}

	// Now play.
	boolean done = false;
	int round = 0;

	while (! done) {

	    // Each player plays their first card.
	    int player1first = player1.removeFirst ();
	    pile.add (player1first);
	    int player2first = player2.removeFirst ();
	    pile.add (player2first);

	    System.out.println ("Round 0: player1's card=" + player1first + "  player2's card=" + player2first);

	    if (player1first > player2first+2) {
		// Add pile into player 1's cards.
		addListToList (pile, player1);
		System.out.println ("  ⇒ player1 gets pile");
	    }
	    else if (player2first > player1first+2) {
		// Add pile into player 2's cards.
		addListToList (pile, player2);
		System.out.println ("  ⇒ player2 gets pile");
	    }
	    else {
		System.out.println ("  ⇒ both cards added to pile");
	    }

	    if (player1.isEmpty()) {
		System.out.println ("Player 2 wins!");
		done = true;
	    }
	    else if (player2.isEmpty()) {
		System.out.println ("Player 1 wins!");
		done = true;
	    }

	} //end-while

    }


    static void shuffle (int[] A)
    {
       // ... random permutation ...
    }


    static void addListToList (LinkedList<Integer> list1, LinkedList<Integer> list2)
    {
        // ... Extract every item in list1 and add them to list2 ...
    }

}
Note:
  • The queue's above are queues of Integer's.

  • There are three queues: one each for the two players, and one for the common pile.

  • The queue operations used are: add(), removeFirst() and isEmpty().

In-Class Exercise 11: The above code is incomplete. Download StrangeCardGame.java and implement the addListToList() method using queue operations.


A GUI application

Consider the following simple animation:

  • We'll build a simple "game" in which an anteater will chase down ants to eat.

  • We'll put up a blank canvas on which a user can click to create ants.
         ⇒ These ants will go into a queue.

    An anteater will systematically hunt down ants one by one
         ⇒ The next ant is taken from the head of the queue.

  • Only the anteater gets to move (to make things simple).

In-Class Exercise 12: Download, compile and try out AntEater.java. Click rapidly in the screen in a few different places and watch the anteater scour for ants.

Here's the program

// Various import's needed for GUI's.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;            // This we need for LinkedList.


class AntEaterPanel extends JPanel {

    // We'll put all the ant-clicks into a queue, from which the anteater 
    // will pull out the next target.
    LinkedList<Point> antQueue = new LinkedList<Point>();

    Point antEater;    // The current position of the anteater.
    Point nextAnt;     // The position of the current ant being chased.


    // Constructor.

    public AntEaterPanel () 
    {
        // Listen to mouseclicks.
        this.addMouseListener (
            new MouseAdapter () {
                public void mouseClicked (MouseEvent e) 
                {
                    handleClick (e.getX(), e.getY());
                }
            }
                                    
        );
        
        // The anteater will run in a separate thread.
        Thread t = new Thread () {
                public void run () 
                {
                    move ();
                }
            };
        t.start ();
    }



    public void paintComponent (Graphics g)
    {
        super.paintComponent (g);

        // Clear drawing area.
        g.setColor (Color.white);
        Dimension D = this.getSize();
        g.fillRect (0,0, D.width, D.height);

        // Draw the ants.
        g.setColor (Color.gray);
        if (nextAnt != null) {
            g.fillOval (nextAnt.x-2,nextAnt.y-2, 4, 4);
        }
        for (Iterator<Point> iter=antQueue.iterator(); iter.hasNext(); ) {
            Point p = iter.next();
            g.fillOval (p.x-2,p.y-2, 4, 4);
        }

        // AntEater.
        g.setColor (Color.red);
        g.fillOval (antEater.x-10,antEater.y-10, 20, 20);
    }
    
    
    void draw ()
    {
        // Place a call to paintComponent(). 
        this.repaint ();
    }


    void handleClick (int x, int y)
    {
        // Add a new ant to the queue.
        antQueue.add (new Point(x,y));
        draw ();
    }
    

    void move ()
    {
        // This is where we'll start the anteater.
        antEater = new Point (0,0);
        
        while (true) {

            // Anteater sleeps 100 milliseconds.
            try {
                Thread.sleep (100);
            }
            catch (InterruptedException e) {
            }

            if (nextAnt == null) {
                if (! antQueue.isEmpty() ) {
                    // See if there's an ant to chase.                    
                    nextAnt = antQueue.removeFirst();
                }
            }
            else {
                if ( distance(nextAnt, antEater) < 10 ) {
                    // Eat the ant.
                    nextAnt = null;
                }
                else {
                    // Otherwise, step towards ant.
                    double theta = Math.atan2 ((nextAnt.y - antEater.y), (nextAnt.x - antEater.x));
                    double stepsize = 10.0;
                    antEater.x = (int) (antEater.x + stepsize*Math.cos(theta));
                    antEater.y = (int) (antEater.y + stepsize*Math.sin(theta));
                    draw ();
                }
            }
        }
    }


    double distance (Point p, Point q)
    {
        double distSq = (p.x-q.x)*(p.x-q.x) + (p.y-q.y)*(p.y-q.y);
        return Math.sqrt (distSq);
    }

} //end-panel



class AntEaterFrame extends JFrame {
    
    public AntEaterFrame ()
    {
        this.setTitle ("AntEater");
        this.setSize (300, 300);

        // The frame only consists of the panel.
        AntEaterPanel panel = new AntEaterPanel ();
        this.getContentPane().add (panel);

        this.setVisible (true);
    }

} //end-frame



public class AntEater {

    public static void main (String[] argv)
    {
        AntEaterFrame f = new AntEaterFrame ();
    }

}
Note:
  • Let's point out a few things from the GUI code:
    • There are three parts to it: main(), the frame (AntEaterFrame) and the drawing area (AntEaterPanel).
    • main() is very simple: we merely create an instance of the frame.
    • The frame too is quite simple: we set the size, place an instance of the panel inside and make it visible.
    • All the work is done inside the panel.
    • To draw on the panel, we need to extend Java's JPanel class to override the paintComponent() method.
           ⇒ This is what is called for drawing to occur.
           ⇒ It's where we put our drawing code.
    • The panel also listens for mouseclicks, which are placed in the queue.
    • The panel also has the "logic" (anteater movement).

  • Let's focus on the use of a queue:
    • Each click creates a new ant, which is placed in the queue (the add() operation).
    • Once the anteater eats an ant, we look at the queue for the next ant (the removeFirst() operation).
    • We also need to test whether the queue is empty.

In-Class Exercise 13: Modify the code above to print the number of ants eaten and to print the total distance travelled by the anteater.

In-Class Exercise 14: Consider how the anteater selects the next ant to pursue after it's finished with the current ant. In the program above, it goes after the next in queue order. What would we have to change, and how, to get the anteater to go after the nearest ant from among those in the queue? Write down pseudocode on paper and then try to implement it. What "queue rules" are we violating in doing this? Can you think of an alternative data structure for this variation of the problem?


© 2006-2020, Rahul Simha & James Taylor (revised 2020)