470 likes | 575 Views
CSE 143 Lecture 10. Generics, Stack, Queue Based on slides by Alyssa Harding. Review: ArrayIntList client. Now that we’ve built ArrayIntList , we can be its client
E N D
CSE 143Lecture 10 Generics,Stack, Queue Based on slides by Alyssa Harding
Review: ArrayIntList client Now that we’ve built ArrayIntList , we can be its client ArrayIntList list1 = new ArrayIntList(); list1.add(42); list1.add(7); list1.add(-10); for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); }
Generics But what if we want to have a list of Strings? We could copy ArrayIntListand change it into ArrayStringList… Java to the rescue: Generics! Redundant!
ArrayList<E> uses Generics <E> is a type parameter
ArrayList<E> ArrayList<E> has all the methods we want: // adds given value to end of list void add(E value) // adds given value at the given index void add(E value, int index) // removes value at the given index void remove(int index) // returns value at given index E get(int index) // returns size of list int size() E is the Element type
ArrayList<E> Now we can create and use a list of any type of Object:ArrayList<String> list = new ArrayList<String>();list.add(“hello”);String s = list.get(0); However, this only works for Objects, not primitive data types, such as ints or doubles
Wrapper classes Java has classes that wrap the primitive types in objects: Example of autoboxing:ArrayList<Integer> list = new ArrayList<Integer>();list.add(42);int x = list.get(0);
ArrayList<E> client Now we can get back to our client code ArrayList<Integer> list1 = new ArrayList<Integer>(); list1.add(42); list1.add(7); list1.add(-10); for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); }
Stacks and Queues Sometimes it is good to have a collection that is less powerful, but is optimized to perform certain operations very quickly. Today we will examine two specialty collections: stack: Retrieves elements in the reverse of the order they were added. queue: Retrieves elements in the same order they were added. stack
Abstract data types (ADTs) abstract data type (ADT): A specification of a collection of data and the operations that can be performed on it. Describes what a collection does, not how it does it We don't know exactly how a stack or queue is implemented, and we don't need to. We just need to understand the idea of the collection and what operations it can perform. (Stacks are usually implemented with arrays; queues are often implemented with a linked list.)
Stacks stack: A collection based on the principle of adding elements and retrieving them in the opposite order. Last-In, First-Out ("LIFO") The elements are stored in order of insertion,but we do not think of them as having indexes. The client can only add/remove the last element added (the "top"). basic stack operations: push: Add an element to the top. pop: Remove the top element.
Stacks in computer science Programming languages and compilers: method calls are placed onto a stack (call=push, return=pop) compilers use stacks to evaluate expressions Matching up related pairs of things: find out whether a string is a palindrome examine a file to see if its braces {} and other operators match convert "infix" expressions to "postfix" or "prefix" Sophisticated algorithms: searching through a maze with "backtracking" many programs use an "undo stack" of previous operations
Queues queue: Retrieves elements in the order they were added. First-In, First-Out ("FIFO") Elements are stored in order ofinsertion but don't have indexes. Client can only add to the end of thequeue, and can only removethe front of the queue. basic queue operations: enqueue: Add an element to the back. dequeue: Remove the front element.
Queues in computer science Operating systems: queue of print jobs to send to the printer queue of programs / processes to be run queue of network data packets to send Programming: modeling a line of customers or clients storing a queue of computations to be performed in order Real world examples: people on an escalator or waiting in a line cars at a gas station (or on an assembly line)
Stacks • Stacks are a Last In First Out (LIFO) structure // add value to the top of the stack void push(E value) // remove and return the value at the top E pop() // return the size int size() // return whether the stack is empty boolean isEmpty()
Queues • Queues are a First In First Out (FIFO) structure // add value to the back of the queue void enqueue(E value) // remove and return the value at the front E dequeue() // return the size int size() // return whether the queue is empty boolean isEmpty()
Interfaces & Implementations Important!! • We’re using our ownStack<E> and Queue<E>interfaces • We’re using our ownArrayStack<E> and LinkedQueue<E>implementations (classes) • We are using generics with both the interfaces and the implementations, so we can store any type of Object in them • Note: There are other implementations (and interfaces) of stacks and queues in the Java collections. They have more method and use different names for the methods. We are NOT using these!
Our Stack Interface public interface Stack<E> { public void push(E value); public E pop(); public int size(); public boolean isEmpty(); }
Our Queue Interface public interface Queue<E> { public void enqueue(E value); public E dequeue(); public int size(); public boolean isEmpty(); }
Stack and Queue client Stack<Integer> s = new ArrayStack<Integer>(); Queue<Integer> q = new LinkedQueue<Integer>(); for (int i = 0; i < 3; i++) { s.push(i); q.enqueue(i); } System.out.println("stack = " + s); System.out.println("queue = " + q);
What does this do? Stack<Integer> s = new ArrayStack<Integer>(); Queue<Integer> q = new LinkedQueue<Integer>(); for (int i = 0; i < 3; i++) { s.push(i); q.enqueue(i); } System.out.println(s.pop()); System.out.println("stack = " + s); System.out.println(q.dequeue()); System.out.println("queue = " + q);
Stack and Queue client methods: fillQueue • We might want to fill a Queue with values: public static Queue<Integer> fillQueue(int n) { Queue<Integer> q = new LinkedQueue<Integer>(); return q; } Note that the variable declaration and return value use the interface type
Stack and Queue client methods: fillQueue We might want to fill a Queue with values: public static Queue<Integer> fillQueue(int n) { Queue<Integer> q = new LinkedQueue<Integer>(); for (int i = 0; i < n; i++) { q.enqueue(i); } return q; } Note that the variable declaration and return value use the interface type
Stack and Queue client methods: queueToStack • We might want to move values from a Queue to a Stack: public static void queueToStack( Queue<Integer> q, Stack<Integer> s) { while ( ) { } }
Stack and Queue client methods: queueToStack We might want to move values from a Queue to a Stack: public static void queueToStack( Queue<Integer> q, Stack<Integer> s) { while ( !q.isEmpty() ) { int n = q.dequeue(); s.push(n); } } Boolean zen!
Stack and Queue client methods: sum (first attempt) We might want to sum the values in a Queue: public static int sum(Queue<Integer> q) { int sum = 0; while ( !q.isEmpty() ) { sum += q.dequeue(); } return sum; }
Stack and Queue client methods: sum (first attempt) • We might want to sum the values in a Queue: public static int sum(Queue<Integer> q) { int sum = 0; while ( !q.isEmpty() ) { sum += q.dequeue(); } return sum; } But this destroys the Queue and leaves it empty!
Stack and Queue client methods: sum (2nd attempt) We might want to sum the values in a Queue: public static int sum(Queue<Integer> q) { int sum = 0; for ( ) { } return sum; }
Stack and Queue client methods: sum • We might want to sum the values in a Queue: public static int sum(Queue<Integer> q) { int sum = 0; for (int i = 0; i < q.size(); i++) { int n = q.dequeue(); sum += n; q.enqueue(n); } return sum; } So we store values back in the Queue and loop the correct number of times.
Stack and Queue client methods: fillStack We might also want to fill a Stack: public static Stack<Integer> fillStack(int n) { Stack<Integer> s = new ArrayStack<Integer>(); for (int i = 0; i < n; i++) return s; } We can copy the Queue code and change it to use a Stack.
Stack and Queue client methods: fillStack • We might also want to fill a Stack: public static Stack<Integer> fillStack(int n) { Stack<Integer> s = new ArrayStack<Integer>(); for (int i = 0; i < n; i++) s.push(i); return s; } We can copy the Queue code and change it to use a Stack.
Stack and Queue client methods: stackToQueue • We might also want to move values from a Stack to a Queue: public static void stackToQueue( Stack<Integer> s, Queue<Integer> q) { while ( !s.isEmpty() ) { int n = s.pop(); q.enqueue(n); } } We can copy the Queue code and change it to use a Stack.
Stack and Queue client methods: sum (first attempt) • We might want to sum the values in a Stack: public static int sum(Stack<Integer> s) { int sum = 0; for (int i = 0; i < s.size(); i++) { int n = s.pop(); sum += n; s.push(n); } return sum; } What is wrong with this code?
Stack and Queue client methods: sum (first attempt) We might want to sum the values in a Stack: public static int sum(Stack<Integer> s) { int sum = 0; for (int i = 0; i < s.size(); i++) { int n = s.pop(); sum += n; s.push(n); } return sum; } But if we copy the Queue code, we push and pop the same value over and over.
Stack and Queue client methods: sum (2nd attempt) • We might want to sum the values in a Stack: public static int sum(Stack<Integer> s) { int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); for (int i = 0; i < s.size(); i++) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q,s); return sum; } Does this work?
Stack and Queue client methods: sum (2nd attempt) We might want to sum the values in a Stack: public static int sum(Stack<Integer> s) { int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); for (int i = 0; i < s.size(); i++) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q,s); return sum; } Even though we store values in a Queue, i increases and s.size() decreases, so we only examine half the values
Stack and Queue client methods: sum (3rd attempt) • We might want to sum the values in a Stack: public static int sum(Stack<Integer> s) { int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); while ( !s.isEmpty() ) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q,s); return sum; } Does this work?
Stack and Queue client methods: sum (3rd attempt) We might want to sum the values in a Stack: public static int sum(Stack<Integer> s) { int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); while ( !s.isEmpty() ) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q,s); return sum; } Now the sum is correct, but our resulting Stack is reversed! This happens when we transfer from a Stack to Queue and back.
Stack and Queue client methods: sum (final version) • We might want to sum the values in a Stack: public static int sum(Stack<Integer> s) { int sum = 0; Queue<Integer> q = new LinkedQueue<Integer>(); while ( !s.isEmpty() ) { int n = s.pop(); sum += n; q.enqueue(n); } queueToStack(q,s); stackToQueue(s,q); queueToStack(q,s); return sum; } Now both the sum and the Stack are correct.
Stack and Queue client methods: findMin We might want to find the minimum value in a Stack: public static int findMin(Stack<Integer> s) { int min = 0; Queue<Integer> q = new LinkedQueue<Integer>(); while ( !s.isEmpty() ) { int n = s.pop(); if ( n < min ) min = n; q.enqueue(n); } queueToStack(q,s); stackToQueue(s,q); queueToStack(q,s); return min; } Does this work?
Stack and Queue client methods: findMin • We might want to find the minimum value in a Stack: public static int findMin(Stack<Integer> s) { int min = 0; Queue<Integer> q = new LinkedQueue<Integer>(); while ( !s.isEmpty() ) { int n = s.pop(); if ( n < min ) min = n; q.enqueue(n); } queueToStack(q,s); stackToQueue(s,q); queueToStack(q,s); return min; } What if the values in the Stack are all greater than 0? This would still return 0 as the minimum.
Stack and Queue client methods: findMin(final version) • We might want to find the minimum value in a Stack: public static int findMin(Stack<Integer> s) { Queue<Integer> q = new LinkedQueue<Integer>(); int min = s.pop(); s.push(min); while ( !s.isEmpty() ) { int n = s.pop(); if ( n < min ) min = n; q.enqueue(n); } queueToStack(q,s); stackToQueue(s,q); queueToStack(q,s); return min; } Instead, we start our minimum at a value that is in the Stack.
Preconditions • Remember the precondition of Stack’s pop method? • The Stack cannot be empty when we call pop • Since we call pop in our method without checking that the Stack isn’t empty, our method has this precondition too
Preconditions • How are we going to deal with this? • We need to return a value, but what would we return if we were passed an empty Stack? • We can comment this precondition, but that doesn’t stop clients from using our method incorrectly
Exceptions • Java lets us throw exceptions in our methods: throw new <exception type here>(); • In this case, we want to throw an exception if the method was given an illegal argument, so we throw an IllegalArgumentException: if ( s.isEmpty() ) throw new IllegalArgumentException();
Exceptions • Always comment the type and cause of any exceptions you throw: // throws IllegalArgumentExceptionif the // stack is empty