Project 1: Deques
Prerequisites to running the code.
Instructions: https://fa22.datastructur.es/materials/proj/proj1/
Solution: https://github.com/tomthestrom/cs61b/tree/master/proj1
This is were the course got interesting for me.
Deque
stands for "double ended queue"
sequence container with dynamic size, that can be expanded or contracted on both ends
About the project
The main task was to implement a Deque ADT. The first implementation felt quite natural, since we were asked to use a Linked List based implementation. The second one used an array based implementation, so I had to figure out an implementation of a circular array.
In the second part, I used my implementation of the Deque API to be the underlying data structure of a Guitar Hero clone. This part only included writing a couple lines to hook up my data structures with the rest of the Guitar Hero clone code, that had been provided by the CS 61 B instructors.
The project also includes some unit tests for the Deque data structures, some of them provided by the authors, more extensive written by me.
Deque API
Specifically, any deque implementation must have exactly the following operations:
public void addFirst(T item): Adds an item of typeTto the front of the deque. You can assume thatitemis nevernull.public void addLast(T item): Adds an item of typeTto the back of the deque. You can assume thatitemis nevernull.public boolean isEmpty(): Returnstrueif deque is empty,falseotherwise.public int size(): Returns the number of items in the deque.public void printDeque(): Prints the items in the deque from first to last, separated by a space. Once all the items have been printed, print out a new line.public T removeFirst(): Removes and returns the item at the front of the deque. If no such item exists, returnsnull.public T removeLast(): Removes and returns the item at the back of the deque. If no such item exists, returnsnull.public T get(int index): Gets the item at the given index, where 0 is the front, 1 is the next item, and so forth. If no such item exists, returnsnull. Must not alter the deque!
In addition, we also want our two deques to implement these two special methods:
public Iterator<T> iterator(): The deques objects we’ll make are iterable (i.e.Iterable<T>) so we must provide this method to return an iterator.public boolean equals(Object o): Returns whether or not the parameterois equal to the deque.ois considered equal if it is a deque and if it contains the same contents (as determined by the genericT’sequalsmethod) in the same order. (You’ll need to use theinstance ofkeywords for this. Read here for more information)
1. Linked List Deque #
Additional requirements
Your operations are subject to the following rules:
addandremoveoperations must not involve any looping or recursion. A single such operation must take “constant time”, i.e. execution time should not depend on the size of the deque. This means that you cannot use loops that go over all/most elements of the deque.getmust use iteration, not recursion.sizemust take constant time.Iterating over the
LinkedListDequeusing a for-each loop should take time proportional to the number of items.Do not maintain references to items that are no longer in the deque. The amount of memory that your program uses at any given time must be proportional to the number of items. For example, if you add 10,000 items to the deque, and then remove 9,999 items, the resulting memory usage should amount to a deque with 1 item, and not 10,000. Remember that the Java garbage collector will “delete” things for us if and only if there are no pointers to that object.
Implementation
About
The underlying implementation is a Doubly Linked List, it's items are instances of a nested class - DequeNode, in order to not deal with special cases when the Deque is empty, I used a sentinel node.
I did some extras such as:
get(int index):in case the element's index is in the second half of the Deque, iterate from the back - thus the real lookup is basically O(N/2)
getRecursive(int index):a recursive implementation of
getalso searches from the back, in case the index is in the 2nd half of the
Deque.
Code
2. Array Deque #
Additional requirements
For this implementation, your operations are subject to the following rules:
addandremovemust take constant time, except during resizing operations.getandsizemust take constant time.The starting size of your array should be 8.
The amount of memory that your program uses at any given time must be proportional to the number of items. For example, if you add 10,000 items to the deque, and then remove 9,999 items, you shouldn’t still be using an array of length 10,000ish. For arrays of length 16 or more, your usage factor should always be at least 25%. This means that before performing a remove operation that will bring the number of elements in the array under 25% the length of the array, you should resize the size of the array down. For arrays under length 16, your usage factor can be arbitrarily low.
Implementation
About
An array based deque implemented using a circular array. In order for it to function, I needed to keep track of the first and last index.
Implementing this is a bit tricky, because it can easily happen, that the first index is actually at a larger index in the underlying array, than the last index.
For example, to get an element by its index, since the first element in the Deque is at firstIndex:
we calculate the requested element's index in the circular array by adding it to the
firstIndexin case that the sum of those two is larger than the length of the underlying
circularArraywe need to subtract the length of the array from this sum - to see this in practice, check out the
get(int index)method.
In case we're adding elements into the deque and we fill the circularArray, we resize via resizeUpIfNecessary private method - when resizing, the size of the underlying array is doubled.
In case we're removing elements from the deque we might need to resize via resizeDownIfNecessary - we do this in case our usage ratio (R) is <= 0.25 - by reducing circularArray's length to half.
Code
3. MaxArrayDeque #
Additional requirements
A MaxArrayDeque has all of the methods that an ArrayDeque has, but it also has 2 additional methods and a new constructor:
public MaxArrayDeque(Comparator<T> c): creates aMaxArrayDequewith the givenComparator.public T max(): returns the maximum element in the deque as governed by the previously givenComparator. If theMaxArrayDequeis empty, simply returnnull.public T max(Comparator<T> c): returns the maximum element in the deque as governed by the parameterComparator c. If theMaxArrayDequeis empty, simply returnnull.
Implementation
About
Since MaxArrayDeque should have all the methods of the ArrayDeque, I simply extended ArrayDeque and implemented the required methods.
Code
Guitar Hero #
The code for most of the clone was provided, the only thing left to do was to edit GuitarString, a class which uses an Deque<Double> to implement the Karplus-Strong algorithm to synthesize a guitar string sound.
GuitarString
We want to finish the GuitarString file, which should use the deque package to replicate the sound of a plucked string. We’ll be using the Karplus-Strong algorithm, which is quite easy to implement with a Deque.
The Karplus-Algorithm is simply the following three steps:
Replace every item in a
Dequewith random noise (doublevalues between -0.5 and 0.5).Remove the front double in the
Dequeand average it with the next double in theDeque(hint: useremoveFirst)andget()) multiplied by an energy decay factor of 0.996 (we’ll call this entire quantitynewDouble). Then, addnewDoubleto the back of theDeque.Play the
double(newDouble) that you dequeued in step 2. Go back to step 2 (and repeat forever).
Or visually, if the Deque is as shown on the top, we’d remove the 0.2, combine it with the 0.4 to form 0.2988, add the 0.2988, and play the 0.2.

Implementation
About
The instructions are quite clear, so implementing this wasn't too difficult.
Code
Last updated