CSE143 Notes for Friday, 10/6/23

I began by discussing ArrayIntList again. In the earlier version we had a method called addAll that added all of the values in a second ArrayIntList at the end of this ArrayIntList. I spent some time discussing how to write the corresponding removeAll method.

I asked for suggestions and someone suggested going through every value of the other list and removing it from the first list as long as the first list still contains it. That approach would work, but I said that I wanted to consider doing it the other way because we eventually want to get to an efficient version and that approach won't lead there.

So instead the pseudocode version of what we want to do is:

        for (all values in this list) {
            if (the other list contains this value) {
                remove this value
            }
        }
We translated this into corresponding code:

        for (int i = 0; i < size; i++) {
            if (other.contains(elementData[i])) {
                remove(i);
            }
        }
This version didn't work. I ran a testing program that produced this output:

        original values:
            list1 = [1, 2, 3, 4, 5, 2, 2, 3, 4, 4, 4, 4, 5, 6]
            list2 = [2, 4, 6, 8]
        after the call list1.removeAll(list2):
            list1 = [1, 3, 5, 2, 3, 4, 4, 5]
            list2 = [2, 4, 6, 8]
        list1 should be = [1, 3, 5, 3, 5]
After the call on RemoveAll, list1 still contains values it shouldn't, like 1, 6, and 9. What happened? We puzzled over it a bit and someone mentioned that it was skipping values. Because we are calling remove, we are shifting values to the left. For example, suppose that i is equal to 5 and we are removing the value at that index. We shift a new value into index 5 when we do that, and then the for loop increments i to be 6. So we skip looking at that value. An easy fix is to decrement i when we remove:

        for (int i = 0; i < size; i++) {
            if (other.contains(elementData[i])) {
                remove(i);
                i--;
            }
        }
That version works. Another way to fix it is to run the loop backwards. When you do that, the values being shifted are values we have already examined, so we don't end up missing any. So this version works as well:

        for (int i = size - 1; i >= 0; i--) {
            if (other.contains(elementData[i])) {
                remove(i);
            }
        }
I said that both of these solutions are inefficient. Someone mentioned that it would be easier if we had a temporary array to work with. The idea would be to build up a new list of values that are the ones to keep from the original, placing them into a temporary array. So our pseudocode would be:

        make a new temporary array
        for (all values in list) {
            if (value is not in the other list) {
                add the value to the temporary array
            }
        }
We need to do a bit more, because we need to keep track of where to put values in the temporary array. The first value to be moved will go into index 0, the next one will go into index 1, the next one into index 2, and so on. In our ArrayIntList we manage this by keeping track of the current size of the list and we can apply the same idea here:

        int[] temp = new int[elementData.length];
        int newSize = 0;
        for (int i = 0; i < size; i++) {
            if (!other.contains(elementData[i])) {
                temp[newSize] = elementData[i];
                newSize++;
            }
        }
        // copy values back from temporary to original
We would have to figure out how to copy values back. But there is an easier way. It turns out we don't need a temporary array at all. We can use elementData itself. Consider what happens with the values used in the testing program:

        list1 : [1, 2, 3, 4, 5, 2, 2, 3, 4, 4, 4, 4, 5, 6]
        list2 : [2, 4, 6, 8]
When we make the call list1.removeAll(list2), the only values we should have left are odd numbers. Think of how those values will be shifted from the original version of elementData to the new version:

         [0] [1] [2] [3] [4] [5] [6] [7] [8] [9][10][11][12][13]
        +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
        | 1 | 2 | 3 | 4 | 5 | 2 | 2 | 3 | 4 | 4 | 4 | 4 | 5 | 6 |
        +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
          |       |       |           |                   |
          |   +---+       |           |                   |
          |   |   +-------+           |                   |
          |   |   |   +---------------+                   |
          |   |   |   |   +-------------------------------+
          |   |   |   |   |
          V   V   V   V   V
        +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
        | 1 | 3 | 5 | 3 | 5 | - | - | - | - | - | - | - | - | - |
        +---+---+---+---+---+---+---+---+---+---+---+---+---+---+
         [0] [1] [2] [3] [4] [5] [6] [7] [8] [9][10][11][12][13]
I am displaying the indexes starting at 5 with a dash instead of a number because it doesn't matter what values are stored there because we are going to reset the size to be 5 when we are done. Notice that we are always copying values to earlier spots in the array. That means that we don't need a second temporary array. We can use elementData itself. But we have to be careful to reset the size after we finish copying the values to be retained in the list:

        public void removeAll(ArrayIntList other) {
            int newSize = 0;
            for (int i = 0; i < size; i++) {
                if (!other.contains(elementData[i])) {
                    elementData[newSize] = elementData[i];
                    newSize++;
                }
            }
            size = newSize;
        }
Then I switched to talking about the next programming assignment. I first gave an example that involved constructing an array of objects. I used the example of constructing an array of Point objects. The Point class is part of the java.awt package that is used for graphics. It was also a primary example in the CSE142 class.

I began with this code:

        import java.awt.*;
        
        public class PointArray {
            public static void main(String[] args) {
                Point[] points;
            }
        }
I asked what kind of objects this program creates. The answer is that it doesn't create any objects. It defines a variable called points that is of type Point[], which means that it is capable of storing a reference to an array of Point objects. But if we want an actual array or some actual Point objects, we have to explicitly construct them.

So I added code to construct the array:

        Point[] points = new Point[5];
We used jGRASP to see what the program is doing and we saw that it constructs the array, but not any Point objects. When you work with an array of objects, you have to construct not just the array, but also every individual object.

Java initializes the array to the zero-equivalent for the type, which in the case of an array of objects means that Java initializes each array element to null:

           +--+     +---------+---------+---------+---------+---------+
    points | -+-->  |    /    |    /    |    /    |    /    |    /    |
           +--+     +---------+---------+---------+---------+---------+
                        [0]     [1]       [2]       [3]       [4]
It is a common convention to use a slash ("/") to represent null. To fill up this array, we had to write a loop that constructed individual Point objects that were stored in the array:

        for (int i = 0; i < points.length; i++) {
            points[i] = new Point(i, 2 * i + 1);
        }
This constructed 5 different Point objects:
                     +-------+ +-------+ +-------+ +-------+ +-------+
                     | x = 0 | | x = 1 | | x = 2 | | x = 3 | | x = 4 |
                     | y = 1 | | y = 3 | | y = 5 | | y = 7 | | y = 9 |
                     +-------+ +-------+ +-------+ +-------+ +-------+
                         ^         ^         ^         ^         ^
                         |         |         |         |         |
           +--+     +----+----+----+----+----+----+----+----+----+----+
    points | -+-->  |    *    |    *    |    *    |    *    |    *    |
           +--+     +---------+---------+---------+---------+---------+
                        [0]       [1]       [2]       [3]       [4]
I then asked how we could print each of the Point objects with a println. Someone mentioned that we could use a foreach loop:

        for (Point p : points) {
            System.out.println(p);
        }
This produced the following output:

        java.awt.Point[x=0,y=1]
        java.awt.Point[x=1,y=3]
        java.awt.Point[x=2,y=5]
        java.awt.Point[x=3,y=7]
        java.awt.Point[x=4,y=9]
Then I started discussing the programming assignment. The assignment involves simulating guitar strings and musical instruments built from those strings. Each instrument will be stored in its own class. You will be provided with a class called GuitarLite that has just two guitar strings and you will define a class called Guitar37 that has 37 strings.

I asked people to consider the issue of making our code more general. If we know that we are going to want to use different objects as our guitar, then how do we structure our code so that we can make minimal changes to the code? We don't want to write all of our code for one kind of guitar and then find that it doesn't work for another kind of guitar.

Someone mentioned that this is a good place to introduce an interface. To do so, we have to think about what are the behaviors we expect of a guitar object. We expect the guitar to have these methods:

This can be turned into an interface:

        public interface Guitar {
            public void playNote(int pitch);
            public boolean hasString(char string);
            public void pluck(char string);
            public double sample();
            public void tic();
            public int time();
        }
I mentioned that the first part of the assignment involves implement a GuitarString class that simulates a string that can be plucked. The writeup gives you all of the information about how the simulation works.

You are provided with a GuitarLite object that has two strings. Once you have a working GuitarString class, you can use the GuitarLite object to write some client code. I said it would be instructive for us to write some client code. I began by constructing a GuitarLite object:

        Guitar g = new GuitarLite();
Notice that the variable is of type Guitar using the interface. We only need to specify the specific type of Guitar object when we construct it.

The Guitar interface has methods for playing the guitar. You can play a note using the playNote method. The notes are specified using a chromatic scale where concert-A has the value 12. But you have to give more instructions to the guitar object than just to play the note. Telling it to play the note will pluck an appropriate string. But there are two important methods called sample and tic. The sample method returns the current sound information that we send to the sound card and the tic method advances the simulation. Most often you'll do these two things together, calling sample to play the sound and then calling tic. But they are distinct operations, so it's best to have them in separate methods. For example, you might want to mute the program so you might call tic to move time forward without calling sample.

This simulation involves sending information to the sound card at a rapid rate. We are using a class called StdAudio that has a constant called SAMPLE_RATE. It indicates that we are sampling 44,100 times a second. So that means we have to call sample and tic thousands of times just to get a fraction of a second of audio.

Then I mentioned that these Guitar objects also have a different interface for playing notes. Each Guitar object is allowed to introduce a mapping of characters to notes. For the GuitarLite object, the mapping is from "a" and "c" to concert-A and concert-C. The Guitar37 class has a more complex mapping that allows you to use the computer's keyboard to play it like a piano. I opened up a client program that is called GuitarHero. This is being provided to you. It is set up to play the GuitarLite guitar. I compiled it and ran it and I showed that it was able to play those two notes as I would hit the keys "a" and "c". Then I changed this line of code:

        Guitar g = new GuitarLite();
to be:
        Guitar g = new Guitar37();
and then I was able to play 37 different keys. I played a bad version of "Three Blind Mice." I spent the rest of the time discussing details about the homework that are included in the assignment writeup.


Stuart Reges
Last modified: Fri Oct 6 12:43:14 PDT 2023