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:
int[] oddIntegers = {1, 3, 5, 7, 9};
int[] oddIntegers; oddIntegers = {1, 3, 5, 7, 9}; // Won't compile.
// Pure declaration. int[] oddIntegers; // Create space. oddIntegers = new int [5]; // Assign values to individual locations. oddIntegers[0] = 1; oddIntegers[1] = 3; oddIntegers[2] = 5; oddIntegers[3] = 7; oddIntegers[4] = 9;
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.
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.
Let's start with a high-level conceptual view of memory:
There is another conceptual view that's useful:
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:
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:
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. } }
Let's summarize what we've learned:
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, ... ?
To answer this question, Consider the following...
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. } }
Consider the following code:
public class StateExample { // Static variable allocated in global area. static int q = 1; // NOTE: Java's static keyword does NOT mean the value can not change public static void main (String[] argv) { // Local variable 'p' allocated on the stack. int p = 0; // when p is overwritten with a new value, its state has changed p = 100; // when any variable is overwritten, its state is changed q = -1; // as variables change, the program's state also changes } }
Let's diagram the memory for the above program through its execution. Diagraming of this sort is often called a memory trace or just a trace of the program. Generating a memory trace is typically called tracing the program. Note that comments and white space are not processed in Java, so processing skips a few lines from the code when the program runs.
Immediately after the first statement on line 11 is processed, StateExample has the following state:
We will identify the memory area for each variable in our memory traces.
Of course, this program does not stop after processing line 11, so it proceeds through the remaining statements.
Immediately after the statement on line 14 is processed, StateExample has the following state:
Immediately after the statement on line 16 is processed and through the end of the program, StateExample has the following end state:
In this course, we will use the following model for tracing memory diagrams.
This memory trace model will evolve a bit as we come to understand the roles and behaviors of the different memory areas; however, the first step we will take is to condense the diagram to trace multiple states so we can see the big picture. For example, we can combine the individual states of the program into a single diagram to see how the state evolves over the entire runtime of the program.
Tracing involves performing an analysis on the state of the program over some timespan:
Take a minute to consider this table that expresses some of the integer powers and logarithms of base 2.
20 = 1 log21 = 0 21 = 2 log22 = 1 22 = 4 log24 = 2 23 = 8 log28 = 3 24 = 16 log216 = 4 25 = 32 log232 = 5 26 = 64 log264 = 6 27 = 128 log2128 = 7 28 = 256 log2256 = 8 29 = 512 log2512 = 9 210 = 1024 log21024 = 10 211 = 2048 log22048 = 11 212 = 4096 log24096 = 12 213 = 8192 log28192 = 13 214 = 16384 log216384 = 14 215 = 32768 log232768 = 15 216 = 65536 log265536 = 16
This is clearly mathematics, but look beyond the math and consider what concept and in particular what pattern is being expressed by the math.
For instance, the powers table expresses a doubling effect. Each step down the table is double the previous entry. This is a form of growth, colloquially called exponential growth. This type of growth is seen very often in nature. For instance, this growth function roughly models the rate of cell division during reproduction or the rate of viral infection through a population. Could the power function have some use to us when modeling problems in computer science?
The logarithmic table expresses the opposite effect, or a halving effect. In the integer domain, the base 2 logarithm predicts the maximum number of times something can be divided in half until it can no longer be subdivided. Many things are discrete, so the logarithm similarly helps to predict decay rates down to termination of the process. For example, a logarithmic function models radioactive half-life, "half-life" is the time for half the material to decay, because it is an analysis and prediction of two halves, i.e. how long until half the material is decayed and half the material remains. Does the logarithmic function have some use for us to model a problem in computer science?
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.
Depending on your strategy, this process could take a long time. What would be an effective strategy to minimize the number of times that you need to run the program? Describe your strategy.
Hints:
In a later module, we will discuss algorithms that divide a problem in two and then solve each subdivided problem with a subroutine. This is called a 'divide-and-conquer' strategy and the base 2 logarithm will play an important role when we analyze these particular strategies.
There are only two fundamental types of variables: primitive types and reference types.
Consider this example:
public class ReferenceExample { public static void main (String[] argv) { int x = 16; // Note: x is a primitive type. int[] A = {1, 2, 3, 4, 5}; // Note: A is really a reference type that refers to an array. } }
Note:
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:
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 reading this Analysis of the Pangram algorithm and then download this template. You will find these facts useful:
int start = (int) 'a';
char ch = (char) start;
int index = words[j].indexOf (ch); if (index >= 0) { // Yes, it's there. }
Why random 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.
What is an algorithm?
Algorithms are usually described as simply as possible.
int numSuccessfulTrials = 0; for (int n=0; n < numTrials; n++) { doTrial (); if (trialSuccessful) { numSuccessfulTrials ++; } } double probSuccess = (double) numSuccessfulTrials / (double) numTrials;
1. Repeat N times (steps 2 and 3): 2. Perform trial; 3. If trial is successful, increment count 4. estimate = count / N
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)