Module 6: Supplemental Material


Iterating through a set - some variations

Recall the integer example:

import java.util.*;

public class SetExample4 {

    public static void main (String[] argv)
    {
	// Create an instance of a linked list and add some data.
	LinkedList<Integer> oddGuys = new LinkedList<Integer>();
	oddGuys.add (1);
	oddGuys.add (3);
	oddGuys.add (5);
	oddGuys.add (7);
	oddGuys.add (9);

	// Another set.
	LinkedList<Integer> primes = new LinkedList<Integer>();
	primes.add (1);
	primes.add (2);
	primes.add (3);
	primes.add (5);
	primes.add (7);

	// Set intersection and difference.
	LinkedList<Integer> intersection = computeIntersection (oddGuys, primes);

        // ...
    }


    static LinkedList<Integer> computeIntersection (LinkedList<Integer> listA, LinkedList<Integer> listB)
    {
	LinkedList<Integer> listC = new LinkedList<Integer>();
	for (int i=0; i<listA.size(); i++) {
	    Integer K = listA.get(i);
	    // To be in the intersection, K needs to be in both sets.
	    if ( listB.contains(K) ) {
		listC.add (K);
	    }
	}
	return listC;
    }

    // ...

}

We'll now focus on the computeIntersection() and write it in a few different ways. All of these are in SetExample9.java.

First, instead of get() we'll use an iterator object:

    static LinkedList<Integer> computeIntersectionAlt1 (LinkedList<Integer> listA, LinkedList<Integer> listB)
    {
	LinkedList<Integer> listC = new LinkedList<Integer>();
        Iterator<Integer> iter = listA.iterator();
        while (iter.hasNext()) {
            Integer K = iter.next();
	    if ( listB.contains(K) ) {
		listC.add (K);
	    }
        }
        return listC;
    }
  
Note:

Notice that the Iterator above is specialized to Integer's. It is possible to use one without this specialization:

        Iterator iter = listA.iterator();
        while (iter.hasNext()) {
            Integer K = (Integer) iter.next();
	    if ( listB.contains(K) ) {
		listC.add (K);
	    }
        }

In this case, we need to cast the return value of next() to our Integer variable K.

The iteration above was written with a while-loop. We can convert that into a for-loop:

        for (Iterator iter = listA.iterator(); iter.hasNext(); ) {
            Integer K = (Integer) iter.next();
	    if ( listB.contains(K) ) {
		listC.add (K);
	    }
        }

Exercise 1: A standard for-loop has three parts to the for-statement: an initialization part, a test and an increment. The above example doesn't have the third part. Why does it still work?

An even more compact way of iterating through Java's data structures is to use this version of the for-loop:

        for (Integer K: listA) {
	    if ( listB.contains(K) ) {
		listC.add (K);
	    }
        }

Next, observe that we need not "specialize" the list to Integer's. For example:

    static LinkedList<Integer> computeIntersectionAlt5 (LinkedList listA, LinkedList listB)
    {
	LinkedList<Integer> listC = new LinkedList<Integer> ();
        Iterator iter = listA.iterator(); 
        while (iter.hasNext()) {
            Integer K = (Integer) iter.next();
	    if ( listB.contains(K) ) {
		listC.add (K);
	    }
        }
        return listC;
    }
Note:

We could also change the return type:

    static LinkedList computeIntersectionAlt6 (LinkedList listA, LinkedList listB)
    {
	LinkedList listC = new LinkedList ();
        Iterator iter = listA.iterator(); 
        while (iter.hasNext()) {
            Integer K = (Integer) iter.next();
	    if ( listB.contains(K) ) {
		listC.add (K);
	    }
        }
        return listC;
    }
Note:


More about linked lists

Recall the add() method in OurLinkedList: (source file)

    public void add (String s)
    {
	if (front == null) {
	    front = new ListItem ();
	    front.data = s;
	    rear = front;
	    rear.next = null;
	}
	else {
            // We declare and use the variable nextOne.
            ListItem nextOne = new ListItem ();
	    nextOne.data = s;
	    rear.next = nextOne;
	    rear = nextOne;
	}    

	numItems ++;
    }

It is possible to write the else part a little more compactly, without an additional variable: (source file)

    public void add (String s)
    {
	if (front == null) {
            // ... same ...
	}
	else {
            // No variable declared. Simply make rear.next point to a new instance.
            rear.next = new ListItem ();
	    rear = rear.next;
	    rear.data = s;
	}    

        // ...
    }
Note:

Exercise 2: Contrast the above code with the previous approach on paper. Imagine an initially empty list into which three strings are added. Show the complete memory picture (provide some imagined addresses) at each step of the else clause above.

Next, let us return to deletion in a doubly-linked list:

Copying a list:

Exercise 3: Download and modify DoublyLinkedList7.java to make copy() work correctly. Use DoublyLinkedListExample7.java for testing (Read this file first).


Array list revisited

Recall array lists:

Here's the program: (source file)

public class OurArrayList {

    // This is the array in which we'll store the integers.
    Integer[] data = new Integer [1];

    // Initially, there are none.
    int numItems = 0;


    public void add (Integer K)
    {
        if (numItems >= data.length) {
            // Need more space. Let's double it.
            Integer[] data2 = new Integer [2 * data.length];
            // Copy over data into new space.
            for (int i=0; i < data.length; i++) {
                data2[i] = data[i];
            }
            // Make the new array the current one.
            data = data2;
        }
        
        data[numItems] = K;
        numItems ++;
    }


    public int size ()
    {
	return numItems;
    }


    public Integer get (int i)
    {
	return data[i];
    }


    public boolean contains (Integer K)
    {
	// Note: we need to use numItems instead of strings.length
	for (int i=0; i < numItems; i++) {
	    if ( data[i].equals(K) ) {
		return true;
	    }
	}
	return false;
    }


    public String toString ()
    {
        if (numItems == 0) {
            return "Empty";
        }
        
        String s = "List: ";
        for (int i=0; i < numItems; i++) {
            s += " " + data[i];
        }
        return s;
    }
    

}
Note:
  • The initial array size is very small, just 1.

  • Here's an example of using the array-list: (source file)
        public static void main (String[] argv)
        {
            OurArrayList intList = new OurArrayList();
    
            // Put 10 integers.
            for (int k=0; k < 10; k++) {
                Integer K = new Integer (k);
                intList.add (k);
            }
    
            System.out.println (intList);
            
            Integer M = new Integer (5);
            if ( intList.contains(M) ) {
                System.out.println ("List contains " + M);
            }
    
        }
        

  • For large arrays, the copying over can be a significant overhead. However, this is to be balanced against the fact the number of increments is not likely to be large.

  • Arrays can also waste space, with almost half of it unused (just after an increment).

  • However, arrays are very efficient for accessing the data.

Exercise 4: Download both files OurArrayList.java and OurArrayListExample.java and modify OurArrayList so that the new array size is printed out each time the array size is increased. Then, modify the code in OurArrayListExample to add a 100 integers. How many times is the array size increased? Can you tell, without running the program (i.e., just by thinking), how many increments will occur if 1000 integers are added? In general, for adding n integers, how many times is the array size increased?

Let's use the code above to explore autoboxing, a Java feature that allows automatic conversion from int's to Integer's.

First, we will re-write the array list as follows: (source file)

public class OurArrayList2 {

    // Note: an int array
    int[] data = new int [1];

    int numItems = 0;


    public void add (Integer K)
    {
        if (numItems >= data.length) {
            // Need more space. Let's double it.
            int[] data2 = new int [2 * data.length];
            // Copy over data into new space.
            for (int i=0; i < data.length; i++) {
                data2[i] = data[i];
            }
            // Make the new array the current one.
            data = data2;
        }
        
        // Conversion from Integer to int for assignment:
        data[numItems] = K;
        numItems ++;
    }


    public int size ()
    {
	return numItems;
    }


    public Integer get (int i)
    {
        // Conversion from int to Integer:
	return data[i];
    }


    public boolean contains (Integer K)
    {
	// Note: we need to use numItems instead of strings.length
	for (int i=0; i < numItems; i++) {
            // Conversion from Integer to int for direct comparison:
	    if ( K == data[i] ) {
		return true;
	    }
	}
	return false;
    }


    public String toString ()
    {
        // ...
    }
    

}

Note:
  • Java converts from int's to Integer's and vice-versa where appropriate.

  • Thus, above the internal representation was changed to int's but the code in main() didn't change.

  • The input and output to this class is still of type Integer:
        public void add (Integer K)
        {
            // ...
        }
        

  • Observe the direct comparison:
                // K is an Integer, whereas data[i] is an int.
    	    if ( K == data[i] ) {
                }
        

  • This feature is known as autoboxing:
    • Autoboxing is provided for conversion from all base types (e.g, double) to their corresponding classes (e.g, Double).
    • It's called autoboxing because wrapper code is inserted by the compiler to handle the conversions.

The following self-explanatory code provides more examples: (source file)

public class AutoboxingExample {

    public static void main (String[] argv)
    {
        // One of each.
        Integer I = 5;
        int j = 6;

        // Mixed sum of Integer and int:
        Integer K = I * j;

        // Use of Integer's with comparison operators:
        if (K == 30) {
            System.out.println ("K=30");
        }

        // Here, I gets converted to an int. The return type of
        // add() is an Integer, which gets converted to the int "sum".
        int sum = add (I, j);
        System.out.println ("Sum=" + sum);
    }


    static Integer add (int a, int b)
    {
        // Automatic conversion to Integer of result.
        return (a + b);
    }
    
}



© 2006, Rahul Simha (revised 2017)