Module 1: Unidimensional Arrays


Supplemental material


Arrays of integers

Let's start with a simple example:

public class OddNumbers {

    public static void main (String[] argv)
    {
	// Declare an array variable (to hold int's) and initialize
	// it with values.
	int[] oddIntegers = {1, 3, 5, 7, 9};

	// Print some attributes of the array.
	System.out.println ("Array length: " + oddIntegers.length);
	System.out.println ("First index=0, last index=" + (oddIntegers.length-1));

	// Next, the elements along one line.
	System.out.print ("Array elements: ");
	for (int i=0; i < oddIntegers.length; i++) {
	    // Note: "print" and not "println"
	    System.out.print (" " + oddIntegers[i]);
	}
	System.out.println ();
    }

}
Note:

Let's look at a little variation:

// We need this for the class java.util.Arrays.
import java.util.*;

public class OddNumbers2 {

    public static void main (String[] argv)
    {
	// Declare and create space at once.
	int[] oddIntegers = new int [5];
	for (int i=0; i < oddIntegers.length; i++) {
	    oddIntegers[i] = 2*i + 1;
	}

	// The class Arrays has many useful methods, among which is:
	String outputStr = Arrays.toString (oddIntegers);
	System.out.println (outputStr);
    }

}

And another variation:

import java.util.*;

public class OddNumbers3 {

    public static void main (String[] argv)
    {
	// If we want to make various such arrays, it makes sense
	// to put this in a method.
	int[] oddIntegers = makeOddArray (5);

	System.out.println ( "Our array: " + Arrays.toString(oddIntegers) );
    }


    // Note: return type is an array.

    static int[] makeOddArray (int size)
    {
	// Note use of variable "size" in creating array space.
	int[] oddGuys = new int [size];
	for (int i=0; i < oddGuys.length; i++) {
	    oddGuys[i] = 2*i + 1;
	}
	return oddGuys;
    }

}

In-Class Exercise 1: Download OddExercise.java and implement the method search() to search in an array of integers.


Character arrays

Consider this example:

import java.util.*;

public class CharExample {

    public static void main (String[] argv)
    {
        // Make a character array and initialize it.
        char[] letters = {'f', 'a', 'c', 'e', 't', 'i', 'o', 'u', 's'};
        System.out.println ( Arrays.toString(letters) + " has " + letters.length + " chars");
        
        // Make a char array via a String.
        String s = "facetious";
        char[] letters2 = s.toCharArray ();
        System.out.println ( Arrays.toString(letters2) + " has " + letters2.length + " chars");

        
        // Directly create and assign each element.
        char[] letters3 = new char [4];
        letters3[0] = 'f';
        letters3[1] = 'a';
        letters3[2] = 'c';
        letters3[3] = 'e';
        System.out.println ( Arrays.toString(letters3) + " has " + letters3.length + " chars");
    }
    
}

Thus, char arrays are not very different from int arrays.

In-Class Exercise 2: Download CharExercise.java and implement the method checkEqual() to see if two char arrays are exactly the same.


How stuff is stored: a first look at memory

Let's start with a high-level conceptual view of memory:

There is another conceptual view that's useful:

  • First, let's step back and look at all of memory as one block:

  • Within this, parts are allocated to the operating system and to a user's applications.

  • The user's Java program is one such application.

  • The space allocated to the Java program itself has parts: place for the code, and for three types of program memory:
    • The stack: where local variables and parameters are stored.
    • The heap: where arrays and objects are stored.
    • The global area: where everything else (like static variables) is stored.

Now consider a simple program:

public class Simple {

    static int a;
    static int b;
    static int c;

    public static void main (String[] argv)
    {
        // At this point: a==0, b==0, c==0;
        a = 5;
        // At this point: a==5, b==0, c==0;
        b = 1;
        // At this point: a==5, b==1, c==0;
        c = a + b;
        // At this point: a==5, b==1, c==6;
    }

}
Note:
  • Initially, space is allocated (in the "global area") for the variables:

  • Then, after the first assignment:

  • After the last assignment:

Next, let's consider arrays:

public class Simple2 {

    static int a;
    static int b;
    static int c;
    static int[] A;

    public static void main (String[] argv)
    {
        // At this point: a==0, b==0, c==0, A==null;
        a = 5;
        // At this point: a==5, b==0, c==0, A==null;
        b = 1;
        // At this point: a==5, b==1, c==0, A==null;
        c = a + b;
        // At this point: a==5, b==1, c==6, A==null;

        A = new int [4];
        // At this point: A will have an address (a pointer).
    }

}
Note:
  • In some languages, space for arrays is allocated in the global area:

  • In Java:

    • The variable A is itself in the global area (at address 112).
    • The variable A contains the address ("810") of the actual array.
    • The actual array with its contents is allocated on the heap at address "810".

  • What happens with assignment? Consider this modification:
    public class Simple3 {
    
        static int a;
        static int b;
        static int c;
        static int[] A;
    
        public static void main (String[] argv)
        {
            // At this point: a==0, b==0, c==0, A==null;
            a = 5;
            // At this point: a==5, b==0, c==0, A==null;
            b = 1;
            // At this point: a==5, b==1, c==0, A==null;
            c = a + b;
            // At this point: a==5, b==1, c==6, A==null;
    
            A = new int [4];
            // At this point: A will have an address (a pointer).
    
            A[2] = 12;
            // At this point: A[0]==0, A[1]==0, A[2]==12, A[3]==0.
        }
    
    }
       
    • Initially, when allocated, the array contents will be all zero.
    • After the assignment A[2] = 12:

Let's summarize what we've learned:

  • Main memory is a sequence of locations, each of which has a numeric address (like "812" above).

  • At the highest level, various parts of memory are allocated to programs like the operating system and applications.

  • Your Java program runs as one application.

  • The memory allocated to your program itself has four parts: code, stack, heap, global.

  • static variables are allocated in the global area.

  • An array is a contiguous sequence of memory locations.

  • Array variables are really pointers: an array variable points to an area of heap memory where the actual contents are.

  • The term "pointer" is used in the sense that an envelope with your address on it "points" to you: it has information on how to reach you (your address).

The idea of a memory pointer is a really important one that we will encounter many times in this course.

In-Class Exercise 3: Why are the addresses above numbered 100, 104, 108, ... , instead of 100, 101, 102, ... ?

In case you were wondering what kinds of variables are allocated on the heap or stack, here's a preview:

class MySpecialObject {
    // Space for this variable is allocated on the heap.
    int a;
}
// In fact, space for the whole object instance is allocated on the heap.


public class VariableExamples {

    // Static variable allocated in global area.
    static int b = 1;


    //                       NOTE: parameter variable argv

    public static void main (String[] argv)
    {
        // Local variable 'c' allocated on the stack.
        int c = 2;

        // The value in 'c' gets copied into the parameter variable (see below).
        print (c);

        // Space for the whole object is allocated on the heap.
        MySpecialObject obj = new MySpecialObject ();

        // Access variables inside the object via variable name and '.' operator.
        obj.a = 3;
        print (obj.a);

        // When the method exits, 'c' and 'obj' both disappear.
    }
    

    //                 Parameter variable x is on the stack.
    static void print (int x)
    {
        System.out.println ("x=" + x);

        // You can treat the parameter like any variable.
        x = 4;

        // Static variable 'b' is accessible here.
        System.out.println ("b=" + b);

        // When the method exits, 'x' disappears.
    }
    
}


Sizing an array

The following example shows a variety of ways through which an array is given a size:

import java.util.*;

public class ArraySize {

    public static void main (String[] argv)
    {
        // Hard-coded size.
        int[] A = new int [5];
        System.out.println ("Array A has size " + A.length);
        
        // Use a variable, and assign the variable a value.
        int size = 6;
        int[] B = new int [size];
        System.out.println ("Array B has size " + B.length);
        
        // Have the size determined elsewhere.
        size = getSize ();
        int[] C = new int [size];
        System.out.println ("Array C has size " + C.length);

        // Unknown size until run-time.
        size = getSizeFromScreen ();
        int[] D = new int [size];
        System.out.println ("Array D has size " + D.length);
    }


    static int getSize ()
    {
        return 7;
    }
    
    
    static int getSizeFromScreen ()
    {
        System.out.print ("Enter size: ");
        Scanner scanner = new Scanner (System.in);
        return scanner.nextInt ();
    }

}

In-Class Exercise 4: Use the above program to find the largest array you can allocate. Try to get the size accurately (down to the last digit). Keep track of how many times you run the above program.


String examples

Consider this example:

public class StringExample {

    public static void main (String[] argv)
    {
        // Pangram: a sentence with at least one occurence of each letter 'a' to 'z'.

        // Direct creation of String array with constants.
        String[] pangramWords = {"two", "driven", "jocks", "help", "fax", "my", "big", "quiz"};
        // Note: pangramWords is a pointer.

        // Count total length of all words.
        int totalChars = 0;
        for (int i=0; i < pangramWords.length; i++) {
            // Here we are using String's length() method.
            totalChars += pangramWords[i].length();
        }
        System.out.println ("Total #letters in pangram: " + totalChars);
    }

}
Note:
  • When the array is created, the variable pangramWords points to a section of the heap with space for 8 string variables.

  • The variable pangramWords is itself in stack memory, where local variables are placed.

  • String's are objects; an array of String's is represented a little differently than an array of int's.

    • Clearly, pangramWords is a pointer to a chunk of memory that has 8 spaces (length of array).
    • But 8 spaces of what?
    • It turns out, 8 spaces of pointers to String's.
    • Thus, each array entry is actually a pointer to a chunk of memory (at yet another heap location) that holds a String.
    • In general, object instances are chunks of memory in the heap.

Let's look over another example: we'll identify the number of words in our dictionary that begin and end with the same letter:

public class StringExample2 {

    public static void main (String[] argv)
    {
        // Get the list of words.
        String[] words = WordTool.getDictionary ();

        int num = 0;
        for (int i=0; i < words.length; i++) {
            // Extract first and last char's and check equality.
            char firstChar = words[i].charAt (0);
            char lastChar = words[i].charAt (words[i].length()-1);
            if (firstChar == lastChar) {
                num ++;
            }
        }
        System.out.println ("Number of words with same first and last char: " + num);
    }

}

In-Class Exercise 5: Write code to verify that a pangram is indeed a pangram (has all the letters). Start by downloading this template. You will find these facts useful:

  • To convert from a char to an int:
            int start = (int) 'a';
          
  • To go the other way:
            char ch = (char) start;
          
  • To see if a letter exists in a String:
            int index = words[j].indexOf (ch);
            if (index >= 0) {
               // Yes, it's there.
            }        
          


Random arrays

Why random arrays?

  • They're useful in creating test data to test array code.

  • Some interesting problems can be solved using randomly generated arrays.

First, let's see how we can create them: (Also needed: (UniformRandom.java)

import java.util.*;

public class RandomArray {

    public static void main (String[] argv)
    {
        // Generate 10 random integers, each between 1 and 100
        int[] randomInts = new int [10];
        for (int i=0; i < randomInts.length; i++) {
            randomInts[i] = UniformRandom.uniform (1, 100);
        }
    
        // Print.
        System.out.println ( "Array elements: " + Arrays.toString(randomInts) );

        // Generate 10 random doubles between 1 and 100
        double[] randomReals = new double [10];
        for (int i=0; i < randomReals.length; i++) {
            randomReals[i] = UniformRandom.uniform (1.0, 100.0);
        }
        
        // Print.
        System.out.println ( "Array elements: " + Arrays.toString(randomReals) );
    }

}

Next, let's solve this problem: what are the chances that a randomly generated array of integers is already sorted?

public class RandomArray2 {

    public static void main (String[] argv)
    {
        double p = estimateSortedProb (5, 100000);
        System.out.println ("Probability an array of length 5 is sorted: " + p);
    }
    

    static double estimateSortedProb (int arraySize, int numTrials) 
    {
        // Count the number of sorted arrays generated.
        int numSorted = 0;

        // Repeat for given number of experiments.
        for (int n=0; n < numTrials; n++) {

            // Generate a random array.
            int[] randomInts = new int [arraySize];
            for (int i=0; i < randomInts.length; i++) {
                randomInts[i] = UniformRandom.uniform (1, 100);
            }
            
            // See if sorted.
            boolean isSorted = true;
            for (int i=1; i < randomInts.length; i++) {
                if (randomInts[i] < randomInts[i-1]) {
                    isSorted = false;
                }
            }
            if (isSorted) {
                numSorted ++;
            }

        } //end-for-numTrials
        
        // The fraction of trials that resulted in a sorted array:
        double prob = (double) numSorted / (double) numTrials;

        return prob;
    }

}

In-Class Exercise 6: The above code only checks that the array is sorted in increasing-order. Add code to check whether it's sorted in decreasing order. This way, we count both sorted-increasing and sorted-decreasing as sorted.

Note: The general structure of such estimation problems is:

   int numSuccessfulTrials = 0;
   for (int n=0; n < numTrials; n++) {
       doTrial ();
       if (trialSuccessful) {
           numSuccessfulTrials ++;
       }
   }
   double probSuccess = (double) numSuccessfulTrials / (double) numTrials;

Next, let's use random arrays to solve the famous "Birthday Problem": in any group of N people, what are the chances that at least two of them share a birthday?

public class Birthday {

    public static void main (String[] argv)
    {
        double p = birthdayProblem (10);
        System.out.println ("Prob[shared birthday with 10 people] = " + p);
    }


    static double birthdayProblem (int numPeople)
    {
        // Number of times to repeatedly create a random group of people:
        int numTrials = 10000;

        // We'll need to count how often we get a shared b'day.
        int numTrialSuccesses = 0;

        // Repeat and count.
        for (int n=0; n < numTrials; n++) {

            // Generate birthdays (random array)
            int[] birthdays = new int [numPeople];
            for (int i=0; i < birthdays.length; i++) {
                birthdays[i] = UniformRandom.uniform (1, 365);
            }

            // Check if any two match.
            boolean matchExists = false;
            for (int i=0; i < birthdays.length; i++) {
                for (int j=0; j < birthdays.length; j++) {
                    if ( (i != j) && (birthdays[i] == birthdays[j]) ) {
                        // Note: musn't forget the i!=j test above!
                        matchExists = true;
                    }
                }
            }
            
            if (matchExists) {
                numTrialSuccesses ++;
            }
            
        } //end-for-trials
        
        double prob = (double) numTrialSuccesses / (double) numTrials;
        return prob;
    }
    
}

In-Class Exercise 7: How many people are needed for a better than 50% chance of seeing a shared birthday? Download Birthday.java and UniformRandom.java and find out.


Algorithms

What is an algorithm?

  • A way to solve a problem computationally.

  • It is NOT code, although code can sometimes serve as a way to describe an algorithm.

  • Algorithm's are conceptual, not tied to a language like Java.

Algorithms are usually described as simply as possible.

  • For example, we described the "estimation" algorithm as:
       int numSuccessfulTrials = 0;
       for (int n=0; n < numTrials; n++) {
           doTrial ();
           if (trialSuccessful) {
               numSuccessfulTrials ++;
           }
       }
       double probSuccess = (double) numSuccessfulTrials / (double) numTrials;
    

  • We could simplify this further into an English-like description:
        1.  Repeat N times (steps 2 and 3):
        2.      Perform trial;
        3.      If trial is successful, increment count
        4.  estimate = count / N
        

  • Or, we could use so-called "pseudocode"
        1.  count = 0
        2.  trialNum = 0
        2.  repeat
        3.      isSuccess = performTrial ();
        4.      if (isSuccess) 
        5.          count = count + 1
        6.      endif
        7.      trialNum = trialNum + 1
        8.  while (trialNum < N)
        9.  return (count / N)
        

  • There is no standard for describing algorithms:
    • Generally, you try to remove language-specific detail.
    • You want to keep the key ideas of the algorithm intact.



© 2006, Rahul Simha (revised 2017)