Module 6: Sets and lists
Supplemental material
Set operations: intersection, union, difference
What is a set?
  
  -  A set is merely a collection of items or objects.
  
 -  For example:
     
     -  A = {1, 3, 5, 7, 9} (odd numbers less than 10)
     
 -  B = {1, 2, 3, 5, 7} (primes less than 10)
     
 
   -  Standard operations on sets:
    
    -  Union: the union of A and B is
      the set consisting of elements found in either:
       
      ⇒
         A union B = {1, 2, 3, 5, 7, 9}
     -  Intersection: those elements in both sets
       
      ⇒
         A intersection B = {1, 3, 5, 7}
     -  Difference: elements found in one but not in the other
       
      ⇒
         A - B = {9}
     
   
 List data structures as sets:
  
  -  We will use a list data structure to hold the elements of a set.
       
      ⇒
       We'll use the LinkedList data structure already in the Java library.
   -  In our example, we'll store String's as elements.
  
 
Here's the example:
// We need to import the class LinkedList from here:
import java.util.*;
public class SetExample {
    public static void main (String[] argv)
    {
	// Create an instance of the data structure.
	LinkedList<String> favoriteShows1 = new LinkedList<String>();
	// Put some elements in so that it becomes a set of strings.
	favoriteShows1.add ("Yes minister");
	favoriteShows1.add ("Seinfeld");
	favoriteShows1.add ("Cheers");
	favoriteShows1.add ("Frasier");
	favoriteShows1.add ("Simpsons");
	// Create a second instance and add some elements.
	LinkedList<String> favoriteShows2 = new LinkedList<String>();
	favoriteShows2.add ("Mad about you");
	favoriteShows2.add ("Seinfeld");
	favoriteShows2.add ("Frasier");
	favoriteShows2.add ("Cosby show");
	// Compute set intersection and the difference favoriteShows1-favoriteShows2 in separate methods:
	computeIntersection (favoriteShows1, favoriteShows2);
	computeDifference (favoriteShows1, favoriteShows2);
    }
    static void computeIntersection (LinkedList<String> listA, LinkedList<String> listB)
    {
	System.out.println ("Intersection:");
	for (int i=0; i < listA.size(); i++) {
	    String s = listA.get(i);
	    // To be in the intersection, s needs to be in both sets.
	    if ( listB.contains(s) ) {
		System.out.println ("  " + s);
	    }
	}
    }
    static void computeDifference (LinkedList<String> listA, LinkedList<String> listB)
    {
	System.out.println ("Difference: ");
	for (int i=0; i < listA.size(); i++) {
	    String s = listA.get(i);
	    // If s is not in B, it's in the difference.
	    if ( ! listB.contains(s) ) {
		System.out.println ("  " + s);
	    }
	}
    }
}
Note:
  
  -  We need to import the class LinkedList from java.util.
  
 -  We have specialized the use of the class to store String instances:
    
	LinkedList<String> favoriteShows1 = new LinkedList<String>();
    
   -  Notice the methods used in the LinkedList class:
     
     -  The add() method to insert new elements.
     
 -  The size() method to find out how many elements a list has.
     
 -  The get() method to get a particular element.
     
 -  The contains() method to see if a particular element is in the list.
     
 
   
 In-Class Exercise 1:
Download this program and
implement union.
A similar example with integers:
import java.util.*;
public class SetExample3 {
    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.
	computeIntersection (oddGuys, primes);
	computeDifference (oddGuys, primes);
    }
    static void computeIntersection (LinkedList<Integer> listA, LinkedList<Integer> listB)
    {
	System.out.println ("Intersection:");
	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) ) {
		System.out.println ("  " + K);
	    }
	}
    }
    static void computeDifference (LinkedList<Integer> listA, LinkedList<Integer> listB)
    {
	System.out.println ("Difference: ");
	for (int i=0; i<listA.size(); i++) {
	    Integer K = listA.get(i);
	    // If K is not in B, it's in the difference.
	    if ( ! listB.contains(K) ) {
		System.out.println ("  " + K);
	    }
	}
    }
}
We'll modify the Integer example to actually compute the
intersection and difference sets instead of printing them:
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);
	// Note use of toString() in LinkedList.
	System.out.println ("Intersection: " + intersection);
	LinkedList<Integer> difference = computeDifference (oddGuys, primes);
	System.out.println ("Difference: " + difference);
    }
    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;
    }
    static LinkedList<Integer> computeDifference (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);
	    // If K is not in B, it's in the difference.
	    if ( ! listB.contains(K) ) {
		listC.add (K);
	    }
	}
	return listC;
    }
}
Note:
  
  -  The return-type of the methods is now a linked list:
     
    static LinkedList<Integer> computeIntersection (LinkedList<Integer> listA, LinkedList<Integer> listB)
    {
        // ...
    }
     
   -  The class LinkedList has a toString() method:
     
	LinkedList<Integer> intersection = computeIntersection (oddGuys, primes);
	System.out.println ("Intersection: " + intersection);
     
   
 In-Class Exercise 2:
Download this program and
implement union so that it returns a list containing the union.
 In-Class Exercise 3:
Download SetExample6.java, 
DataTool.java, 
DataSet.java,
and the text file datafortwo.
Then, do the following:
  
  -  Compile and execute to see how the data is retrieved and
    how intersection is computed.
  
 -  Draw the memory picture just before the println() in 
    main().
  
 -  Use this largedataset which
   has a list of preferences for more than two people, and compute
   all the pairwise intersections.
  
 
Our own very list data structure
Recall the methods that we used in the LinkedList class:
  
  -  An add() method to put stuff in.
  
 -  A size() method to get the size.
  
 -  A get() method to get the i-th element.
  
 -  A contains() method to see if a particular element is already in the list.
  
 
 Thus, if we were to create our own data structure to do this,
it might look something like:
public class OurList {
    public void add (String s) 
    {
        // ...
    }
    public int size ()
    {
        // ...
    }
    public String get (int i)
    {
        // ...
    }
    // ... contains() method ...
}
 In-Class Exercise 4:
What is the signature of the contains method that should be
in the template above?
We will implement such a data structure using arrays:
  
  -  We'll call the class OurListUsingArrays.
  
 -  The array itself will be internal to the class.
  
 -  Since we don't know the size ahead of time, we'll pick a large enough number for now.
  
 
Here's the program:
public class OurListUsingArrays {
    // This is the array in which we'll store strings.
    String[] strings = new String [100];;
    // Initially, there are none.
    int numStrings = 0;
    public void add (String s)
    {
	if (numStrings < 100) {
	    strings[numStrings] = s;
	    numStrings ++;
	}
    }
    public int size ()
    {
	return numStrings;
    }
    public String get (int i)
    {
	return strings[i];
    }
    public boolean contains (String s)
    {
	// Note: we need to use numStrings instead of strings.length
	for (int i=0; i < numStrings; i++) {
	    if ( strings[i].equalsIgnoreCase(s) ) {
		return true;
	    }
	}
	return false;
    }
}
Note:
  
  -  The methods are quite straightforward.
  
 -  The limitation of "100" is quite artificial.
  
 -  The above class does not include the usage of such a list,
  so let's write that part:
    
import java.util.*;
public class SetExample7 {
    public static void main (String[] argv)
    {
	// Create an instance of the data structure.
	OurListUsingArrays favoriteShows1 = new OurListUsingArrays();
	favoriteShows1.add ("Yes minister");
	favoriteShows1.add ("Seinfeld");
	favoriteShows1.add ("Cheers");
	favoriteShows1.add ("Frasier");
	favoriteShows1.add ("Simpsons");
	// Create a second instance and add some elements.
	OurListUsingArrays favoriteShows2 = new OurListUsingArrays();
	favoriteShows2.add ("Mad about you");
	favoriteShows2.add ("Seinfeld");
	favoriteShows2.add ("Frasier");
	favoriteShows2.add ("Cosby show");
	// Compute set intersection and the difference favoriteShows1-favoriteShows2
	computeIntersection (favoriteShows1, favoriteShows2);
    }
    static void computeIntersection (OurListUsingArrays listA, OurListUsingArrays listB)
    {
	System.out.println ("Intersection:");
	for (int i=0; i < listA.size(); i++) {
	    String s = listA.get(i);
	    // To be in the intersection, s needs to be in both sets.
	    if ( listB.contains(s) ) {
		System.out.println ("  " + s);
	    }
	}
    }
}
    
   
 In-Class Exercise 5:
Download OurListUsingArrays.java
and modify SetExample8.java
to compute unions.
About our implementation:
  
  -  One disadvantage of using arrays is that we don't know the size in advance but must make enough space in our array.
  
 -  If the array is too small, we run out of space.
  
 -  If the array is too big, we may end up wasting space 
       
      ⇒
       Imagine creating an array of size 100,000 for small sets of size 10.
   -  Our name OurListUsingArrays is quite a mouthful, so 
   perhaps we should name it ListWithArray.
  
 
Our own linked list
A linked list solves the "unknown size" problem:
  
  -  A linked list is initially empty (no space).
  
 -  A linked list grows to occupy only as much 
  space as needed to store its elements.
  
 
To see how a linked list works, we'll first examine this piece
of code:
// We'll use instances of this object in main() below.
class ListItem {
    String data;    
    ListItem next;
}
public class StrangeExample {
    public static void main (String[] argv)
    {
	// Make one instance and put a string in the data field.
	ListItem first = new ListItem();
	first.data = "Yes minister";
	// Make a second one.
	ListItem second = new ListItem();
	second.data = "Seinfeld";
	// Now link them: make the first one point to the second.
	first.next = second;
	// Make a third and make the second point to the third.
	ListItem third = new ListItem();
	third.data = "Cheers";
	second.next = third;
	// Make a fourth etc.
	ListItem fourth = new ListItem();
	fourth.data = "Frasier";
	third.next = fourth;
	ListItem last = new ListItem();
	last.data = "Simpsons";
	fourth.next = last;
	// Now print. Note the extensive use of the dot-operator.
	System.out.println ("First: " + first.data);
	System.out.println ("Second: " + first.next.data);
	System.out.println ("Third: " + first.next.next.data);
	System.out.println ("Fourth: " + first.next.next.next.data);
	System.out.println ("Last: " + first.next.next.next.next.data);
	System.out.println ("Last (alt): " + last.data);
    }
}
 In-Class Exercise 6:
Draw the memory picture after each new ListItem has been created.
Instead of using the dot-operator in sequence, we'll use a different
approach:
  
  -  We will track the last item in the list using a pointer.
  
 -  When a new item is to be added, we add that to the end 
       
      ⇒
       We'll use the "last" pointer.
   
Here's the program:
class ListItem {
    String data;    
    ListItem next;
}
public class StrangeExample2 {
    public static void main (String[] argv)
    {
	// Make one instance and put a string in the data field.
	ListItem first = new ListItem();
	first.data = "Yes minister";
        
        // This is also the last one, right now.
        ListItem last = first;
	// Make a second one.
	ListItem nextOne = new ListItem();
	nextOne.data = "Seinfeld";
	// Now link them: make the first one point to the second.
	last.next = nextOne;
        // Advance the "last" pointer.
        last = nextOne;
	// Make a third and make the second point to the third.
	nextOne = new ListItem();
	nextOne.data = "Cheers";
	last.next = nextOne;
        last = nextOne;
	// Make a fourth etc.
	nextOne = new ListItem();
	nextOne.data = "Frasier";
	last.next = nextOne;
        last = nextOne;
        // Last one.
	nextOne = new ListItem();
	nextOne.data = "Simpsons";
	last.next = nextOne;
        last = nextOne;
	// Now print by repeatedly advancing the pointer.
        ListItem listPointer = first;
	System.out.println ("First: " + listPointer.data);
        listPointer = listPointer.next;
	System.out.println ("Second: " + listPointer.data);
        listPointer = listPointer.next;
	System.out.println ("Third: " + listPointer.data);
        listPointer = listPointer.next;
	System.out.println ("Fourth: " + listPointer.data);
        listPointer = listPointer.next;
	System.out.println ("Last: " + listPointer.data);
    }
}
Note:
  
  -  It's easier to first understand the printing:
     
     -  Notice how the pointer variable listPointer starts
     by pointing to the same thing that first is pointing to?
     
 -  Then observe that listPointer advances along to the
     second item, then the third etc.
     
 
   -  Similarly, earlier in the program, the nextOne
  variable (a pointer, since it's an object variable) is made
  to point to a new object instance each time.
  
 -  Each time a new item is added to the end, the last
  pointer is advanced along to point to the last one.
  
 
The repetition in printing suggests a loop:
class ListItem {
    String data;    
    ListItem next;
}
public class StrangeExample3 {
    public static void main (String[] argv)
    {
	// Make one instance and put a string in the data field.
	ListItem first = new ListItem();
	first.data = "Yes minister";
        ListItem last = first;
	// Make a second one.
	ListItem nextOne = new ListItem();
	nextOne.data = "Seinfeld";
	last.next = nextOne;
        last = nextOne;
	// Make a third and make the second point to the third.
	nextOne = new ListItem();
	nextOne.data = "Cheers";
	last.next = nextOne;
        last = nextOne;
	// Make a fourth etc.
	nextOne = new ListItem();
	nextOne.data = "Frasier";
	last.next = nextOne;
        last = nextOne;
        // Last one.
	nextOne = new ListItem();
	nextOne.data = "Simpsons";
	last.next = nextOne;
        last = nextOne;
	// Now print by repeatedly advancing the pointer - in a simple loop.
        ListItem listPointer = first;
        while (listPointer != null) {
            System.out.println (listPointer.data);
            listPointer = listPointer.next;
        }
    }
}
Note:
  
  -  Once again, the variable listPointer now moves along
  the list, at each step pointing at the next item in the list.
  
 -  Notice the loop termination condition:
    
        while (listPointer != null) {
            // ...
        }
    
    
    -  When the last item is added, its .next field
    contains null (because it points to nothing).
    
 -  Thus, we know we've reached the end when we're pointing to null.
    
 
   
 In-Class Exercise 7:
Download StrangeExample3.java
and add the line
  
  System.out.println (listPointer);
  
inside the while-loop. Now draw the list with actual
memory addresses and show how the variable listPointer advances
along the list.
Can the code for adding new items also be compacted? 
       
      ⇒
       Yes, indeed:
class ListItem {
    String data;    
    ListItem next;
}
public class StrangeExample4 {
    static ListItem first = null;
    static ListItem last = null;
    public static void main (String[] argv)
    {
	// Make one instance and put a string in the data field.
	first = new ListItem();
	first.data = "Yes minister";
        last = first;
	// Add the rest.
        add ("Seinfeld");
        add ("Cheers");
        add ("Frasier");
        add ("Simpsons");
	// Now print by repeatedly advancing the pointer - in a simple loop.
        ListItem listPointer = first;
        while (listPointer != null) {
            System.out.println (listPointer.data);
            listPointer = listPointer.next;
        }
    }
    static void add (String s)
    {
        // Make a new instance (new list node) and add the data.
	ListItem nextOne = new ListItem();
	nextOne.data = s;
        // Make the current last one point to the new one.
	last.next = nextOne;
        // Adjust the last pointer.
        last = nextOne;
    }
    
}
We're now in position to create our own linked-list:
  
  -  We will put all the code in a class called
  ListWithLinks (to resemble Java's LinkedList class).
  
 -  We will name the methods add(), size(), get() and contains().
  
 
Here's the program:
class ListItem {
    String data;
    ListItem next;
}
public class ListWithLinks {
    // Instance variables.
    ListItem front = null;
    ListItem rear = null;
    // To keep track of the size.
    int numItems = 0;
    public void add (String s)
    {
	if (front == null) {
            // The special case of an empty list needs to be handled differently.
	    front = new ListItem ();
	    front.data = s;
	    rear = front;
	    rear.next = null;
	}
	else {
            // Just like before:
            ListItem nextOne = new ListItem ();
	    nextOne.data = s;
	    rear.next = nextOne;
	    rear = nextOne;
	}    
	numItems ++;
    }
    public int size ()
    {
	return numItems;
    }
    
    public String get (int i)
    {
        // Sanity check:
	if (i >= numItems) {
	    return null;
	}
        // Otherwise, count up to the i-th item.
	int count = 0;
	ListItem listPtr = front;
	while (count < i) {
	    listPtr = listPtr.next;
	    count ++;
	}
	return listPtr.data;
    }
    public boolean contains (String s)
    {
        // Sanity check.
	if (front == null) {
	    return false;
	}
        // Start from the front and walk down the list. If it's there,
        // we'll be able to return true from inside the loop.
	ListItem listPtr = front;
	while (listPtr != null) {
	    if ( listPtr.data.equals(s) ) {
		return true;
	    }
	    listPtr = listPtr.next;
	}
	return false;
    }
}
 This class itself will be used in "main" (or elsewhere) as a
data structure:
public class ListWithLinksExample {
    public static void main (String[] argv)
    {
	ListWithLinks favoriteShows1 = new ListWithLinks();
	favoriteShows1.add ("Yes minister");
	favoriteShows1.add ("Seinfeld");
	favoriteShows1.add ("Cheers");
	favoriteShows1.add ("Frasier");
	favoriteShows1.add ("Simpsons");
	ListWithLinks favoriteShows2 = new ListWithLinks();
	favoriteShows2.add ("Mad about you");
	favoriteShows2.add ("Seinfeld");
	favoriteShows2.add ("Frasier");
	favoriteShows2.add ("Cosby");
	computeIntersection (favoriteShows1, favoriteShows2);
    }
    static void computeIntersection (ListWithLinks listA, ListWithLinks listB)
    {
	System.out.println ("Intersection:");
	for (int i=0; i < listA.size(); i++) {    // Calls the size() method in ListWithLinks
	    String s = listA.get(i);              // Calls the get() method
	    if ( listB.contains(s) ) {            // The contains() method
		System.out.println ("  " + s);
	    }
	}
    }
}
Note:
  
  -  Some nomenclature: each individual ListItem instance
    in the list is called a node of the list.
  
 -  We've changed the names first and last
    to the more traditional names of front and rear.
  
 
 In-Class Exercise 8:
Download ListWithLinks2.java
and ListWithLinksExample2.java
and implement the printList() method in
ListWithLinks2 to print out the list.
The code in ListWithLinksExample2 calls this method.
Conceptual view of a linked-list:
  
  -  A simple conceptual view:
     

  
 -  With a little more detail (actual addresses):
     

  
 -  Suppose this were a list of integers, containing the first
  four odd numbers:
     

    
    -  Notice that the view is conceptual: the actual addresses do
    not necessarily correspond to visual order.
    
 -  "front" and "rear" are variables that may be in other objects.
    
 
   -  If we were to add the data "9" to this list:
     

  
 
Some enhancements
We will add a couple more methods ("features") to our list:
  
  -  A toString() method.
  
 -  A way to print the list with memory addresses of the nodes.
  
 -  We'll change the name of the class to the more traditional 
    OurLinkedList, but different from Java's
    LinkedList class.
  
 
Here's the program:
class ListItem {
    // ... 
}
public class OurLinkedList {
    // ... same as before ...
    public void add (String s)
    {
        // ...
    }
    public int size ()
    {
        // ...
    }
    
    public String get (int i)
    {
        // ...
    }
    public boolean contains (String s)
    {
        // ...
    }
    public String toString ()
    {
	if (front == null) {
	    return "empty";
	}
        // Put all the elements (data only) into the string.
	String s = "[";
	ListItem listPtr = front;
	while (listPtr != null) {
	    s += " \"" + listPtr.data + "\"";
	    listPtr = listPtr.next;
	}
	return s + "]";
    }
    public void printWithAddresses ()
    {
	if (front == null) {
	    return;
	}
	ListItem listPtr = front;
	while (listPtr != null) {
            //  listPtr's default toString() prints out the memory address.
	    System.out.println (" \"" + listPtr.data + "\"  at address " + listPtr);
	    listPtr = listPtr.next;
	}
    }
}
Note:
  
  -  The toString() method must return a String.
       
      ⇒
       We build the desired output into the String and return it.
   -  For printing addressees, we have exploited the fact that 
    when an object without a toString() method is printed,
    the address is printed.
    
    -   ListItem doesn't have toString().
    
 -  Thus, printing any instance of ListItem displays
     the address of that instance.
    
 
   
 In-Class Exercise 9:
Add a toString() method to ListItem above, and
see what happens.
Doubly-linked lists
About the linked-list we've seen so far:
  
  -  We call it a singly-linked list (to contrast it with
  the doubly-linked list we will see next).
  
 -  Deletion is a little difficult in a singly-linked list,
  easier in a doubly-linked list.
  
 -  A doubly-linked list allows traversal in any direction.
  
 -  A singly-linked list allows traversal in only the "forward"
  (front to rear) direction.
  
 -  One implication for the singly-linked list is this:
     
     -  Consider this picture:
       

     
 -  And this code:
       
       public class SinglyLinkedListProblem {
           public static void main (String[] argv)
           {
               // Some list code:
               ListItem front = new ListItem ();
               front.next = new ListItem ();
               rear = front.next;
               
               // Given rear, can one print the element before it?
               printPrevNode (rear);
           }
           static void printPrevNode (ListItem listPtr)
           {
               // How do we go to whichever node comes before listPtr?
           }
       }
       
      
   
In a doubly-linked list:
  
  -  The conceptual view is:
     

     
     -  Each node points both to the next one in the list and the
     previous one.
     
 -  Except for the first node, whose "backpointer" points to
     null, and the last node, whose next pointer
     points to null.
     
 
   -  Let us now add the additional pointer to the class that
    used for each node:
    
class ListItem {
    String data;
    ListItem next;    // To point to next node in list.
    ListItem prev;    // To point to the previous node in the list.
}
    
   -  We'll have to set the prev pointer carefully when
  we add a new element.
  
 
Here's the program:
class ListItem {
    String data;
    ListItem next;    // To point to next node in list.
    ListItem prev;    // To point to the previous node in the list.
}
public class DoublyLinkedList {
    // Instance variables.
    ListItem front = null;
    ListItem rear = null;
    int numItems = 0;
    public void add (String s)
    {
	if (front == null) {
            // Similar to singly-linked list, except for setting rear.prev
	    front = new ListItem ();
	    front.data = s;
	    rear = front;
	    rear.next = null;
	    rear.prev = null;           // Must set this correctly.
	}
	else {
            // Make new ListItem and set its fields correctly.
            ListItem nextOne = new ListItem ();
	    nextOne.data = s;
	    nextOne.next = null;
	    nextOne.prev = rear;
            // Adjust the next pointer of the current last one, and adjust rear itself.
	    rear.next = nextOne;
	    rear = nextOne;
	}    
	numItems ++;
    }
    public int size ()
    {
    	// ...
    }
    
    public String get (int i)
    {
    	// ... same as in singly-linked list ...
    }
    public boolean contains (String s)
    {
    	// ... same as in singly-linked list ...
    }
    public String toString ()
    {
        // ... 
    }
}
Note:
  
  -  The size(), get() and contains() methods are
  the same as in a singly-linked list because those don't need the
  prev pointer.
  
 
 In-Class Exercise 10:
Download DoublyLinkedList2.java
and DoublyLinkedListExample2.java
and implement a method to print the list in reverse (starting from
the rear).
Next, let's examine what needs to change to build a doubly-linked list
that can store int's.
 In-Class Exercise 11:
Download DoublyLinkedIntList.java
and DoublyLinkedListExample3.java
and implement a doubly-linked list to hold integers.
You can copy over the code in DoublyLinkedList above into
your DoublyLinkedIntList to begin with, and then change
methods/data etc so that the list stores int's.
Deletion
There are many ways by which we might want to delete a particular
element:
 
 -  For example, we might want do it by identifying the element directly:
    
	DoublyLinkedList favoriteShows = new DoublyLinkedList();
	favoriteShows.add ("Crocodile Hunter");
        // ... add more stuff ...
        favoriteShows.delete ("Crocodile Hunter");
    
  -  Alternatively, we might want to delete by order of occurence in list:
    
	DoublyLinkedList favoriteShows = new DoublyLinkedList();
	favoriteShows.add ("Crocodile Hunter");
        // ... add more stuff ...
        favoriteShows.delete (0);     // Delete 0-th element.
    
  -  What needs to happen in a deletion?
    
    -  We need to find the node in question.
    
 -  For example, suppose we want to delete the third node.
        

    
 -  The "gap" after removal needs to be "stitched together" by
    adjusting links.
        

    
 
  
Here's the program:
class ListItem {
   // ...
}
public class DoublyLinkedList4 {
    // ...
    public void add (String s)
    {
        // ...
    }
    public int size ()
    {
        // ...
    }
    
    public String get (int i)
    {
        // ...
    }
    public boolean contains (String s)
    {
        // ...
    }
    public String toString ()
    {
        // ...
    }
    // Find String s and delete it if it occurs in the list.
    public void delete (String s)
    {
	ListItem listPtr = front;
	while ( (listPtr != null) && (! listPtr.data.equals(s)) ) {
	    listPtr = listPtr.next;
	}
        // If it's not there, return.
	if (listPtr == null) {
	    return;
	}
	// Otherwise delete: four cases.
        if (front == rear) {
            // Case 1: only one element.
            front = rear = null;
        }
	else if (listPtr == front) {
            // Case 2: we're deleting from the front.
	    front = listPtr.next;
            front.prev = null;
	}
	else if (listPtr == rear) {
            // Case 3: delete the last element.
	    rear = listPtr.prev;
            rear.next = null;
	}
	else {
	    // Case 4: In the middle: stitch the prev and next nodes together.
	    listPtr.prev.next = listPtr.next;
	    listPtr.next.prev = listPtr.prev;
	}
	numItems --;
    }
    // Delete the element at a particular position in the list
    public void delete (int i)
    {
        // Check for bad input.
	if ( (i < 0) || (i >= numItems) ) {
	    return;
	}
        // Find the i-th element.
	int count = 0;
	ListItem listPtr = front;
	while ( (listPtr != null) && (count != i) ) {
	    listPtr = listPtr.next;
	    count ++;
	}
	// Otherwise delete: four cases.
        if (front == rear) {
            // Case 1: only one element.
            front = rear = null;
        }
	else if (listPtr == front) {
            // Case 2: we're deleting from the front.
	    front = listPtr.next;
            front.prev = null;
	}
	else if (listPtr == rear) {
            // Case 3: delete the last element.
	    rear = listPtr.prev;
            rear.next = null;
	}
	else {
	    // Case 4: In the middle: stitch the prev and next nodes together.
	    listPtr.prev.next = listPtr.next;
	    listPtr.next.prev = listPtr.prev;
	}
	numItems --;
    }
}
 In-Class Exercise 12:
Modify DoublyLinkedList4.java
above to print out the addresses of the node-to-be-deleted, along with
the nodes on either side. Then, draw "before" and "after" lists
complete with addresses.
Use DoublyLinkedListExample4.java
as the class with main().
Deletion in a singly-linked list:
  
  -  Deletion is a little more complicated in a singly linked list.
  
 -  Example: suppose we want to delete the data "7" (4-th node)
  in this list of integers:
        

  
 -  First, we walk down the list to find the node:
        

  
 -  To remove the node, we have to make the previous node point
  to the next one:
        

  
 -  How do we "reach" the previous node in order to change the pointer?
       
      ⇒
       We need another pointer variable:
        
  
 -  This pointer variable needs to "move" along with the listPtr
  variable when we search for the node to be deleted:
        

  
 
 In-Class Exercise 13:
Write on paper the code needed to search the list for both
the item-to-be-deleted and the node prior to it.
Then write on paper the code needed for deletion.
 In-Class Exercise 14:
Implement your code in OurLinkedList2.java
and test it with OurLinkedListExample2.java.
© 2006, Rahul Simha (revised 2017)