First Part

Learning objectives

  • Recognize the situation where a stack would be necessary for the conception of an algorithm
  • Describe the implementation of a stack using an array
  • Implement a stack using a dynamic array
  • Implement a predefined interface type
  • Apply the concept of generic types for the implementation of classes and interfaces

Stack

Introduction

Stacks are part of the common data structure types used by programmers. There are however many ways to implement them. To do so, you must understand their behaviors. First, stacks follow the principle of last-in first-out (LIFO). In other words, the elements are stacked one on top of the other, and you can only access the element on top of the stack. To access an element below the top element, you can only access this element by removing every element on top of it, i.e. entered after.

You are constantly surrounded by stacks in the real world. Determine if these are indeed an example of a stack (true) or not (false).

Question 1.1 :

Tennis balls in their container

Answer
Question 1.2 :

The waiting list for students to be admitted in engineering.

Answer
Question 1.3 :

A stack of dishes.

Answer
Question 1.4 :

Evaluation of postfix expressions

Answer
Question 1.5 :

The chips in a Pringles can

Answer
Question 1.6 :

A toll booth

Answer
Question 1.7 :

The back button in your web browser

Answer

Application

This part of the lab will focus on two of the three implementations of the interface Stack. You will be evaluated on these two implementations in the midterm evaluation.

1.1 Modify the interface Stack: add a method clear()

Modify the interface Stack bellow to add a method abstract public void clear().

public interface Stack<E> {
    boolean isEmpty();
    E peek();
    E pop();
    void push(E element);
}
File:

1.2 Implement the method clear() of the class ArrayStack

The class ArrayStack uses an array of fixed size and implements the interface Stack. Since the interface Stack has been modified to have the method clear(), the implementation of the class ArrayStack is faulty. (Try to compile it now without changing anything, what error do you see?).

Since the class ArrayStack implements the interface Stack, it needs to have an implementation for all methods of the interface. Therefore, you will need to write a method void clear(). This method will remove all the elements from this stack (ArrayStack). The stack will be empty after this call. Use the class L6Q1 to test the implementation of the method clear().

Files:

1.3 Create a new class DynamicArrayStack

Modify the class ArrayStack using the technique known as dynamic array. This new class DynamicArrayStack has a constant DEFAULT_INC, with the value 25. The constructor takes an argument capacity which is the initial size of the array. However, the minimum size of the array is never less that DEFAULT_INC (25). It has a getter, getCapacity(), that returns an integer, which is the length of the array (physical size of the stack). When the array becomes full, a new array is created with DEFAULT_INC more cells than the previous array, and filled with the elements from the previous array. Similarly, when the stack has DEFAULT_INC elements less than the length of the array (physical size of the stack), it needs to be automatically reduced in size. Make the necessary changes, particularly in pop, push and clear, and in the constructor (recall, the minimal size of an array is DEFAULT_INC).

File:

 

Second part

This part is for you to do on your own at home, and does not need to be handed in. However, as this material is fair game for the midterm exam, we strongly recommend that you do indeed go through it.

Postfix expressions

Introduction

Postfix expressions are an example of the application of a stack. As viewed in class, it is a method where we push the operands of the expression until we encounter an operator. At this point, we remove (pop) the two previous elements (the operands). We then do the operation and push back the result onto the stack. At the end of the expression we should only be left with one element in our stack.

Let’s look at the postfix expression: 5 2 - 4 *

  1. First, we read the expression from left to right. We start with numbers that will be used as operands, so we stack them:Bottom [5 2
  2. When we encounter an operator (here “-“), we pop the 2 operator from the stack, 2 and 5 respectively. We calculate the result of the operation. Note that the order is important, we take the second element that was removed (5) minus (-) the first element to be removed (2). We then push the result on the stack(3). Don’t forget that 5 and 2 have been removed. Bottom [3
  3. The next element is “4”, as it is a number, we push it on the stack: Bottom [3 4
  4. Finally we read “*”. As it is an operator (multiplication), we remove the two elements on top of the stack, 4 and 3 respectively. We then do the operation “3 * 4” and we push back the result: Bottom [12
  5. We have used all of the elements from our postfix expression and we only have one value in our stack. “12 “ is therefore the answer to the expression!

Note: if you encounter an operator but there are not two numbers in the stack, the postfix expression is invalid. Similarly, the expression is invalid if after its execution there is more than one number in the stack.

Let’s practice! These concepts might be part of your midterm!

Here is a series of simple exercises. Make sure to understand how to read the postfix expressions and how to convert arithmetic expressions into postfix expression.

Question 2.1 :

Convert this expression into a postfix expression: 5 * 3 + 5

Answer
Question 2.2 :

Convert this expression into a postfix expression: 4 - 6 / 3 * 2

Answer
Question 2.3 :

Solve this postfix expression (find the result): 9 2 * 6 / 2 3 * +

Answer
Question 2.4 :

Solve this postfix expression (find the result): 4 2 8 * - + 7 +

Answer

Application

2.1 Algo1

For this part of the laboratory, there are two algorithms for the validation of expressions containing parenthesis (round, squared and brackets).

The class Balanced below presents the first algorithm algo1, a simple version to validate expressions. We see that a well formed expression is one that for each type of parenthesis (round, squared and curly brackets), the number of opening parenthesis is equal to the number of closing parenthesis.

public static boolean algo1(String s) {

	int curly, square, round;

	curly = square = round = 0;

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

		char c;
		c = s.charAt(i);

		switch (c) {
			case ’{’:
				curly++;
				break;
			case ’}’:
				curly--;
				break;
			case ’[’:
				square++;
				break;
			case ’]’:
				square--;
				break;
			case ’(’:
				round++;
				break;
			case ’)’:
				round--;
		}
	}

	return curly == 0 && square == 0 && round == 0;
}

Compile this program and experiment. First make sure that the program runs properly for valid expression such as “()[]()”, “([][()])”. You will remark that the algorithm works for expressions that contains operands and operators: “(4 * (7 - 2))”.

Next you will need to find expressions where the algorithm returns true even if the expressions are not valid.

File

5. Algo2

You found expressions that make this algorithm fail? Good job! Indeed, a valid expression is one where the number of opening parenthesis is equal to the number of closing ones, for each type of parenthesis. But also, when we encounter a closing parenthesis, it has to be the same type as the last opening parenthesis that wasn’t treated. For instance, “ (4 + [2 - 1)] " is not valid!

You need to implement an algorithm using a stack to validate expressions: return true if the expression is valid, false otherwise. Also, the analysis should go through the stack only once. You will need to create your implementation in the class Balanced, name this method algo2. (the model solution is about 15 lines!)

Do several tests using valid and invalid expressions. Make sure that your algorithm treats this case: "((())". How do you deal with this case?

 

Part Three

Generic data structures

It is possible to use generic types to implement classes or interfaces. These can be reused in many contexts, with parameterized types. Remember the interface Comparable! By using the generic type, we make sure to keep the compiler validation.

Files:

3.1 Implement the interface Map

Define the interface Map. A class that realizes Map has to save the key-value association. In our case, we need a Map to contain identical keys, in which case the method get, put, replace, and remove will refer to the association containing this key that is the further right (the last). Map is a generic class with two types of parameters, K and V, where K is the type of the Key and V is the type of the values. A Map has the following methods:

  1. V get(K key) : Returns the rightmost value associated to the key specified in the parameter.
  2. boolean contains(K key) : Returns true if there is an association for the key specified in the parameter
  3. void put(K key, V value) : Creates a new key-value association
  4. void replace(K key, V value) : Replaces the value of the rightmost occurrence of the association for the specified key
  5. V remove(K key) : Removes the rightmost occurrence of the specified key and returns the value that was associated to this key

Note that no parameter can be null in any of these methods

3.2 Implement the class Dictionary

Dictionary keeps track of String-Integer associations (key-value).

  • The Dictionary class implements the interface Map <String,Integer >.
  • It uses an array (the first instance variable) to store each association. The elements in this array are of type Pair , a class that will be public. A Pair type object must store an association, "key" and "value" of type String and Integer respectively. Study the pre-solved file provided. Note this is an alternative solution until nested classes are taught in class.
  • You must treat your array of items as a stack. So the bottom of your stack will be element 0, while the top of the stack will be the non-null element at the highest position. Use a counter (the second instance variable) that will tell you how many items are in your table.
  • Finally, because the Dictionary class stores an arbitrarily large number of associations, you will need to use the dynamic table technique (as shown in part 1). The initial size of the array will be 10, while its increment will be 5 whenever the array is full. These two values ​​will be treated as constants.
  • You clearly need to implement all methods of the interface. Do not forget to consider that the rightmost association (element of type Pair) will always be the top most of the stack! You will therefore have to go through your table in the opposite direction.
  • Add a toString() method that prints your table elements from last to first (top of stack to bottom of stack).
  • It has getters, getCount() which returns the current number of items in the dictionary, and getCapacity() which returns the current length of the array.
  • DictionaryTest.java contains a number of tests for your implementation that you can use while developing your solution.
Note that you do not have to handle exceptional inputs in your solution (for example, removing, getting, or replacing a key that doesn't exist.)

 

Part Four

Create and submit a zip folder (1 point)

Instructions

  • Create a directory lab6_123456, where 123456 is replaced by your student number.
  • Inside this directory, place the 6 .java files for this lab. Include only your source code. Do not include any class files or any other files; only Java files.
  • In this directory, also create a file README.txt which is a text file containing your name, student number and a brief description of the content of the directory:
    
    Student name: Jane Doe
    Student number: 123456
    Course code: ITI1121
    Lab section: B-2
    
    This archive contains the 7 files of lab 6, that is, this file (README.txt),
    plus ArrayStack.java, Dictionary.java, DynamicArrayStack.java, Pair.java, Map.java, Stack.java.
    
                        
  • Create a zip file lab6_123456.zip containing the directory lab6_123456 and all of the java files.
  • Verify that your archive is correct by uncompressing it somewhere and making sure that all the files and the directory structure are there.
  • Submit the archive using https://uottawa.brightspace.com/

Important remarks!

We are using automated scripts to test and grade your lab submission. Therefore, you must follow these instructions exactly. In particular:
  • Your naming of all the files and methods must be exact. Copy/paste the starter code provided during the lab to avoid problems.
  • You MUST submit a .zip; not individual files; not a .rar, not a .7z, or anything else.
  • All the required java files must be present and must compile (that is, running javac *.java must not produce any errors), even if you were unable to complete an exercise.
  • Even if you could not solve an exercise's solution completely, you have to hand in a file with the methods that compiles (you can put "return 0" if you gave up).
  • Your submission is due by Tuesday 11:59PM the week after the lab. You can submit as often as you want before then, but not after. Only your last submission will be considered.

JUnits

To help you making sure that your code is correct, we have prepared some JUnit tests.

Resources

Table of Contents