Ch11-AbstractDataTypes

advertisement
Problem Solving with Data
Structures using Java:
A Multimedia Approach
Chapter 11: Abstract Data
Types: Separating the
Meaning from the
Implementation
Chapter Objectives
Separating Meaning from
Implementation

Powerful engineering idea in computer
science.
• Meaning
• Identify the behavior of an object or data structure.
• Build other program components to interact with
that behavior.
• Implementation
• Hide how that behavior is implemented, so that no
program components can be dependent on the
implementation.
Abstract Data Type: Stacks

An example of an Abstract Data
Type (ADT):
•
•

We can define the methods and
the behavior apart from any
implementation.
There are multiple
implementations, some better than
others.
Can use a stack to reverse a list
in less time, but more space.
•
A classic tradeoff!
Space for time.
Only put on top,
Never pull from
bottom.
A Stack is a LIFO List

Last-In-First-Out (LIFO) List
•
•
First item in the list is the last one out.
Last one in is first one out.
I got here
third!
This is the top
of the stack
I got here
second!
I got here
first!
New items go at the top
I got here
fourth!
This is the
new top of the
stack
I got here
third!
I got here
second!
I got here
first!
Items only get removed from the
top
I got here
fourth! And
now I’m outta
here!
I got here
third!
This is the
new (er, old)
top of the
stack
I got here
second!
I got here
first!
What can we do with stacks?




push(anObject): Tack a new object onto
the top of the stack
pop(): Pull the top (head) object off the
stack.
peek(): Get the top of the stack, but don’t
remove it from the stack.
size(): Return the size of the stack
Separation of Concerns



Separation definition of behavior of data
structure from implementation.
Lets one change without changing the
other.
Java allows us to use an interface as a
way specify that behavior definition.
Stack
Interface
Just defining the
operations here.
E is the type of
the things in the
Stack.
Stack<E>
represents the
generic stack for
any time element
/**
* An abstract definition of a stack
* @author Barb Ericson
*/
public interface Stack<E> {
/**
* Method to add the element to the top of the stack
* @param element the element to add
*/
public void push(E element);
/**
* Method to return the top element on the
* stack, but not remove it from the stack
* @return the top object from the stack
*/
public E peek();
/**
* Method to remove the top element from the
* stack and return it
* @return the top element from the stack
*/
public E pop();
/**
* Method to return the number of elements in
* the stack
* @return the number of elements in the stack
*/
public int size();
/**
* Method to check if the stack is empty
* @return true if empty, else false
*/
public boolean isEmpty();
}
Creating an implementation


We declare a class as implementing
that interface.
We can continue to use the <E> generic,
so that we can later create a stack for
any type object.
import java.util.LinkedList; // Need for LinkedList
Stack as a
LinkedList
/**
* Class that represents a stack using a linked list
* of objects
* @author Mark Guzdial
* @author Barb Ericson
*/
public class LinkedListStack<E> implements
Stack<E> {
/** Where we store the elements */
private LinkedList<E> elements;
/**
* Constructor that takes no arguments
*/
public LinkedListStack() {
elements = new LinkedList<E>();
}
//// Methods ///
/**
* Method to add an element to the stack
* @param element the element to add
*/
public void push(E element) {
// New elements go at the front
elements.addFirst(element);
}
/**
* Method to return the top element on the stack
* but leave the element on the stack
* @return the top element on the stack
*/
public E peek() {
return elements.getFirst();
}
/**
* Method to remove the top element from a stack
* and return it
* @return the top element from the stack and remove it
*/
public E pop() {
E toReturn = this.peek();
elements.removeFirst();
return toReturn;
}
/**
* Method to get the number of elements in the stack
* @return the number of elements in the stack
*/
public int size(){return elements.size();}
/**
* Method to test if the stack is empty
* @return true if the stack is empty, else false
*/
public boolean isEmpty() {
return (size() == 0);
}
}
Using the
Stack with
Strings > Stack<String> stack = new LinkedListStack<String>();
Notice the
use of
<String>
> stack.push("This")
> stack.push("is")
> stack.push("a")
> stack.push("test")
> stack.size()
4
> stack.peek()
"test"
> stack.pop()
"test"
> stack.pop()
"a"
> stack.pop()
"is"
> stack.pop()
"This"
A Stack of
Pictures
Note: You can’t
create an object
using an interface
name, e.g., new
Stack<String>()
> Stack<Picture> stack = new LinkedListStack<Picture>();
> stack.push(new
Picture(FileChooser.getMediaPath("beach.jpg")));
> stack.push(new
Picture(FileChooser.getMediaPath("arch.jpg")));
> stack.push(new
Picture(FileChooser.getMediaPath("bridge.jpg")));
> stack.size()
3
> stack.peek()
Picture, filename C:\dsBook\media-source/bridge.jpg
height 640 width 480
> stack.pop()
Picture, filename C:\dsBook\media-source/bridge.jpg
height 640 width 480
> stack.pop()
Picture, filename C:\dsBook\media-source/arch.jpg
height 480 width 360
> stack.pop()
Picture, filename C:\dsBook\media-source/beach.jpg
height 480 width 640
A stack is a stack,
no matter what lies beneath.

Our description of the stack minus the
implementation is an example of an abstract
data type (ADT).
•

An abstract type is a description of the methods that a
data structure knows and what the methods do.
We can actually write programs that use the
abstract data type without specifying the
implementation.
•
•
There are actually many implementations that will
work for the given ADT.
Some are better than others.
New implementation:
Stack as Array
/**
* Implementation of a stack as an array
* @author Mark Guzdial
* @author Barb Ericson
*/
public class ArrayStack<E> implements Stack<E> {
/** default size of the array */
private static final int ARRAY_SIZE = 20;
/** Where we'll store our elements */
private Object[] elements;
Constructor
/** Index where the top of the stack is */
private int top;
/**
* No argument constructor
*/
public ArrayStack() {
elements = new Object[ARRAY_SIZE];
top = 0;
}
Methods for Stack as Array
/**
* Method to add an element to the top of the stack
* @param element the element to add
*/
public void push(E element) {
// New elements go at the top
elements[top]=element;
// then add to the top
top++;
if (top==ARRAY_SIZE) {
System.out.println("Stack overflow!");
}
}
/**
* Method to return the top element on the stack
* but not remove it.
* @return the object at the top of the stack
*/
public E peek() {
if (top==0) {
System.out.println("Stack empty!");
return null;
} else {
// this will give a warning but it is unavoidable
return (E) elements[top-1];
}
}
/**
* Method to remove and return the top element on the stack
* @return the element on the top of the stack
*/
public E pop() {
E toReturn = this.peek();
top--;
return toReturn;
}
/**
* Method to return the number of elements in the stack
* @return the number of elements in the stack
*/
public int size(){return top;}
/**
* Method to check if the stack is empty
* @return true if the stack is empty else false
*/
public boolean isEmpty() {return this.size() == 0;}
}
Trying out Stack as Array
> Stack<String> stack = new ArrayStack<String>();
Pushing one element
> stack.push("Matt");
Push two more, pop one
> stack.push("Katie");
> stack.push("Jenny");
> stack.size() // without the ending ';' it prints out the result
3
> stack.peek() // without the ending ';' it prints out the result
"Jenny"
> stack.pop()
"Jenny"
Critique the array-based
implementation

What happens if the number of elements
in the stack is more than 20?
What are stacks good for?




The algorithm for converting an equation into
a tree uses a stack.
Often use stacks when describing card
games, like solitaire.
The list of Web pages you have visited in a
browser is stored in a stack, so that you can
go “back.”
The list of operations you have executed is
stored in a stack, so that you can “undo.”
Stacks describe function calls

As new functions get called, position in
old functions get pushed on a stack.
• So you always return to the last function you
•
•
were in.
If an error occurs, you get a stack trace.
If your recursion goes into an infinite loop,
what error do you get? Stack overflow!
A Stack Example: New Reverse

Recall our original implementation of reverse().
•
•
•

We go to the end of the original list to find the last().
We then remove() it (which involves walking the list
until we find the one before last())
We then insert it at the end of the new list (via add(),
which does last().insertAfter()).
All told: For each node, we walk the whole list
three times.
•
O(n*n2)=O(n3)
Original
Reverse
/**
* Reverse the list starting at this,
* and return the last element of the list.
* The last element becomes the FIRST element
* of the list, and THIS points to null.
*/
public LayeredSceneElement reverse() {
LayeredSceneElement reversed, temp;
// Handle the first node outside the loop
reversed = this.last();
this.remove(reversed);
Highly
inefficient.
Touching each
node requires
touching every
other node:
O(n2)
while (this.getNext() != null) {
temp = this.last();
this.remove(temp);
reversed.add(temp);
}
// Now put the head of the old list on the end of
// the reversed list.
reversed.add(this);
// At this point, reversed
// is the head of the list
return reversed;
}
New Reverse: Push all, pull off
reversed
/**
* Reverse2: Push all the elements on
* the stack, then pop all the elements
* off the stack.
*/
public LayeredSceneElement reverse2() {
LayeredSceneElement reversed, current, popped;
Stack<LayeredSceneElement> stack =
new LinkedListStack<LayeredSceneElement>();
// Push all the elements on the list
current=this;
while (current != null)
{
stack.push(current);
current = current.getNext();
}
// Make the last element (current top of stack) into new first
reversed = stack.pop();
// Now, pop them all onto the list
current = reversed;
while (stack.size()>0) {
popped = stack.pop();
current.insertAfter(popped);
current = popped;
}
return reversed;
}
What’s the diff? Time

How often is each node touched in
reverse2()?
• Twice: Once going onto the stack, once
•

coming off.
O(2*n) => O(n)
The stack-based reverse is faster than
the original reverse.
What’s the diff? Space



How much space does reverse2() take?
•
Whatever space the stack takes.
How much additional space does reverse()
take? None
Very common tradeoff: Space for time.
•
•
You can make an algorithm go faster, by using more
space.
If you need to fit into less memory, you have to do
more processing, which takes more time.
Testing Reverse in
SoundListTest()
public void reverseTest(){
Sound s = null; // For copying in sounds
s = new Sound(FileChooser.getMediaPath("guzdial.wav"));
SoundNode root = new SoundNode(s);
s = new Sound(FileChooser.getMediaPath("is.wav"));
SoundNode one = new SoundNode(s);
root.last().insertAfter(one);
s = new Sound(FileChooser.getMediaPath("scritch-q.wav"));
SoundNode two = new SoundNode(s);
root.last().insertAfter(two);
s = new Sound(FileChooser.getMediaPath("clap-q.wav"));
SoundNode three = new SoundNode(s);
two.insertAfter(three);
//root.playFromMeOn();
SoundNode reversed = (SoundNode) root.reverse2();
reversed.playFromMeOn();
}
Second ADT:
Introducing a Queue

First-In-First-Out List
• First person in line is first person served
I got here
third!
This is the tail
of the queue
I got here
second!
I got here
first!
This is the
front or head
of the queue
First-in-First-out

New items only get added to the tail.

Items only get removed from the head.
• Never in the middle
I got here
third!
This is the tail
of the queue
I got here
second!
I got here
first!
This is the
front or head
of the queue
As items leave, the head shifts
I got here
third!
I got here
second!
This is the tail
of the queue
Now, this is
the front or
head of the
queue
I got here
first! AND
NOW I’M UP!
Served!
As new items come in, the tail
shifts
I got here
fourth!
Now, this is
the tail of the
queue
I got here
third!
I got here
second!
Now, this is
the front or
head of the
queue
Queue Operations





push(element): Tack a new element onto the
tail (end) of the queue.
pop(): Pull the top (head) element off the
queue.
peek(): Get the head of the queue, but don’t
remove it from the queue.
size(): Return the size of the queue.
isEmpty(): Return true or false, if the size of
the queue is zero.
Queue
Interface
/**
* Interface to define an abstract queue
* @author Barb Ericson
*/
public interface Queue<E> {
/**
* Push an element onto the tail of the Queue
* @param element the element to add to the queue
*/
public void push(E element);
/**
* Peek at, but don't remove, the head of the queue
* @return the head of the queue (top)
*/
public E peek();
/**
* Pop an object from the Queue
* @return the head (top) of the queue and
* remove it from the queue
*/
public E pop();
/**
* Return the size of a queue
* @return the number of elements in the queue
*/
public int size();
/**
* Method to see if the queue is empty
* @return true if the queue is empty, else false
*/
public boolean isEmpty();
}
Implementing a Queue as a
Linked List
import java.util.*; // LinkedList representation
/**
* Implements a simple queue using a linked list
* @author Mark Guzdial
* @author Barb Ericson
*/
public class LinkedListQueue<E> extends AbstractQueue<E> {
/** Where we'll store our elements */
private LinkedList<E> elements;
/**
* No argument constructor
*/
public LinkedListQueue() {
elements = new LinkedList<E>();
}
/// Methods
/**
* Push an element onto the tail of the Queue
* @param element the element to add to the queue
*/
public void push(E element) {
elements.addFirst(element);
}
/**
* Peek at, but don't remove, top (first) of queue
* @return the first object in the queue
*/
public E peek() {
return elements.getLast();
}
//**
* Pop an object from the Queue
* @return the top object from the queue (and remove it)
*/
public E pop() {
E toReturn = this.peek();
elements.removeLast();
return toReturn;
}
/**
* Return the size of a queue
* @return the number of elements in the queue
*/
public int size() { return elements.size(); }
/**
* Method to see if the queue is empty
* @return true if the queue is empty, else false
*/
public boolean isEmpty() { return size() == 0; }
}
Testing our
implementation:
Behaving as
expected?
> Queue<String> line = new LinkedListQueue<String>();
> line.push("Fred");
> line.push("Mary");
> line.push("Jose");
> line.size()
3
> line.peek() // without ending ';' prints the result
"Fred"
> line.pop()
"Fred"
> line.peek()
"Mary"
> line.pop()
"Mary"
> line.peek()
"Jose"
> line.pop()
"Jose"
Queue
as Array
/**
* Implements a simple queue using an array
* @author Mark Guzdial
* @author Barb Ericson
*/
public class ArrayQueue<E> extends AbstractQueue<E> {
/** constant for the size of the queue */
private static final int ARRAY_SIZE = 20;
/** Where we'll store our elements */
private Object[] elements;
/** The index of the head */
private int head;
/** The index of the tail */
private int tail;
/** No argument constructor */
public ArrayQueue() {
elements = new Object[ARRAY_SIZE];
head = 0; tail = 0;
}
/// Methods
/**
* Push an element onto the tail of the Queue
* @param element the element to add to the queue
*/
public void push(E element) {
if ((tail + 1) >= ARRAY_SIZE) {
System.out.println("Queue underlying implementation failed");}
else {
// Store at the tail,
// then increment to a new open position
elements[tail] = element;
tail++;}
}
/**
* Peek at, but don't remove, the head of the queue
* @return the head of the queue (top)
*/
public E peek() {
// this will give a warning but there is no way around it
return (E) elements[head];
}
/**
* Pop an object from the Queue
* @return the head (top) of the queue and
* remove it from the queue
*/
public E pop() {
E toReturn = this.peek();
if (((head + 1) >= ARRAY_SIZE) ||
(head > tail)) {
System.out.println("Queue underlying implementation failed.");
return toReturn;
}
else {
// Increment the head forward, too.
head++;
return toReturn;
}
}
/**
* Return the size of a queue
* @return the number of elements in the queue
*/
public int size() { return tail-head;}
/**
* Method to see if the queue is empty
* @return true if the queue is empty, else false
*/
public boolean isEmpty() { return size() == 0; }
}
Again, testing
implementation
> Queue<String> line = new ArrayQueue<String>();
> line.push("Fred");
> line.push("Mary");
> line.push("Jose");
> line.size()
3
> line.peek() // without ending ';' prints the result
"Fred"
> line.pop()
"Fred"
> line.peek()
"Mary"
> line.pop()
"Mary"
> line.peek()
"Jose"
> line.pop()
"Jose"
How the array implementation
of queue works

An empty queue
Pushing “Matt”
Pushing “Katie”
Popping (returns “Matt”)
Notice that we’ve now permanently “lost” the first cell.
Challenge: Can you “recover” that cell?
Improving the implementation

Our two implementations have
duplicated code.
• Check out isEmpty() in each.

Where could we put the code so that it’s
not duplicated?
• Interfaces can’t have method bodies.
• We can use an abstract class.
Abstract Queue Class
/**
* Class to define an abstract queue
* @author Barb Ericson
*/
public abstract class AbstractQueue<E> implements Queue<E> {
/**
* Push an object onto the Queue
* @param element the element to add to the queue
*/
public abstract void push(E element);
/**
* Peek at, but don't remove, the head of the queue
* @return the head of the queue (top)
*/
public abstract E peek();
/**
* Pop an object from the Queue
* @return the head (top) of the queue and
* remove it from the queue
*/
public abstract E pop();
/**
* Return the size of a queue
* @return the number of elements in the queue
*/
public abstract int size();
Finally, the isEmpty method,
factored out
/**
* Method to see if the queue is empty
* @return true if the queue is empty, else false
*/
public boolean isEmpty() {
return (size() == 0);
}
}
Revised
ArrayQueue
/**
* Implements a simple queue using an array
* @author Mark Guzdial
* @author Barb Ericson
*/
public class ArrayQueue extends AbstractQueue {
/// ... fields and other methods as before
/**
* Method to see if the queue is empty
* @return true if the queue is empty, else false
*/
// commented out since inherited from AbstractQueue
// public boolean isEmpty() { return size() == 0; }
}
Revised
LinkedListQueue
/**
* Implements a simple queue using a linked list
* @author Mark Guzdial
* @author Barb Ericson
*/
public class LinkedListQueue extends AbstractQueue {
// ... fields and other methods as before
/**
* Check if the queue is empty
* @return true if no elements in the queue, else false
*/
// commented out since inherited from AbstractQueue
// public boolean isEmpty() {return this.size() == 0;}
}
Can switch implementations easily
As ArrayList
If inserting/deleting a lot, use
LinkedList
> import java.util.*;
> List<String> nameList = new
ArrayList<String>();
> nameList.add("Shayna");
> nameList.add("Marcus");
> nameList.add("Jakita");
> nameList
[Shayna, Marcus, Jakita]
> import java.util.*;
> List<String> nameList = new
LinkedList<String>();
> nameList.add("Shayna");
> nameList.add("Marcus");
> nameList.add("Jakita");
> nameList
[Shayna, Marcus, Jakita]
How the List is used
(methods and behavior)
is the same in both
cases.
A Common Pattern

We see this structure
repeated throughout
java.util
•
•
•
Interface defines ADT
Abstract class defines
common parts
Concrete classes differ in
implementation
Switching implementations

Switching the underlying implementation
can provide advantages.
• Of speed
• Of flexibility

For example, our stack implemented as
an Array can go out of bounds if grows
too large.
• But not if we use ArrayList!
import java.util.*;
Using an
ArrayList:
ArrayListStack
/**
* Implementation of a stack as an ArrayList
* @author Mark Guzdial
* @author Barb Ericson
*/
public class ArrayListStack<E> implements Stack<E> {
/** Where we'll store our elements */
private List<E> list = new ArrayList<E>();
/**
* No argument constructor
*/
public ArrayListStack() {
}
//// Methods ///
/**
* Method to add an element to the top of the stack
* @param element the element to add
*/
public void push(E element) {
list.add(element);
}
/**
* Method to return the top element on the stack
* but not remove it.
* @return the object at the top of the stack
*/
public E peek() {
return list.get(list.size() - 1);
}
/**
* Method to remove and return the top element on the stack
* @return the element on the top of the stack
*/
public E pop() {
return list.remove(list.size() - 1);
}
/**
* Method to return the number of elements in the stack
* @return the number of elements in the stack
*/
public int size(){return list.size();}
Main() for testing
public static void main(String[] args) {
Stack<String> stack = new ArrayListStack<String>();
stack.push("Matt");
stack.push("Katie");
stack.push("Jenny");
System.out.println(stack.size());
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
New ADT: A Map

A Map associates a value with a key.

If you store two values under one key,
the second value will overwrite the first
value as THE value for THAT key.
• The key can be any unique object.
• The value can be any single object.
Using a Map to create oil
paintings



Oil paintings have only one color for region
(size of brush)
Using the Java implementation of Map
(java.util.Map) to track how often a color (the
key) appears in a region.
For each region,
• Store color counts in map.
• Get most common (highest count) color.
• Set all colors in region to the most common color.
oilPaint method in Picture
/**
* Method to do an oil paint effect on a picture
* @param dist the distance from the current pixel
* to use in the range
* @return the new picture
*/
public Picture oilPaint(int dist) {
// create the picture to return
Picture retPict = new Picture(this.getWidth(),this.getHeight());
// declare pixels
Pixel currPixel = null;
Pixel retPixel = null;
// loop through the pixels
for (int x = 0; x < this.getWidth(); x++) {
for (int y = 0; y < this.getHeight(); y++) {
currPixel = this.getPixel(x,y);
retPixel = retPict.getPixel(x,y);
retPixel.setColor(currPixel.getMostCommonColorInRange(dist))
;
}
}
return retPict;
}
getMostCommonColorInRange
in class Pixel
/**
* Method to return the most common color in the given
* range from the current pixel in the picture or just
* return this color.
* @param dist the distance to use for the range
* @return the most common color in this range
*/
public Color getMostCommonColorInRange(int dist) {
Map<Color,Integer> colorMap = new
HashMap<Color,Integer>();
The variable colorMap is of type
Pixel currPixel = null;
Map.
We instantiate HashMap, but could
Integer value = null;
switch to another implementation
Color theKey = null;
// loop through the pixels around this one within the distance
for (int currY = y - dist; currY <= y + dist; currY++) {
for (int currX = x - dist; currX <= x + dist; currX++) {
if (currY >= 0 && currY < picture.getHeight() &&
currX >= 0 && currX < picture.getWidth()) {
currPixel = picture.getPixel(currX,currY);
theKey = currPixel.getColor();
value = colorMap.get(theKey);
if (value == null)
colorMap.put(theKey,1);
else
colorMap.put(theKey, value + 1);
}
}
}
// find the color that is most common
int maxValue = 1;
int currValue = 0;
theKey = this.getColor(); // use current color as default
Set<Color> keySet = colorMap.keySet();
for (Color key : keySet) {
currValue = colorMap.get(key);
if (currValue > maxValue) {
theKey = key;
maxValue = currValue;
}
}
return theKey;
}
Example Use
Download