Monday July 17, 2000


Quiz 23


1. What two classes are used to obtain input from an external file?

2. What two classes are used to write output to an external file?

3. What class do we use to break a string into pieces based on a delimiting string?

4. In what package is this class found?
































Exercise

The Sieve of Eratosthenes

Eratosthenes of Cyrene developed an ingenious method for finding all prime numbers <= n. The idea is based on the following principle.

Theorem:

If a>1 is a composite number (that is a is not prime), then a has at least one divisor smaller than or equal to its square root.

Proof:

Suppose a is a composite number. Then a can be written as b*c where b and c are both greater than 1. One of these two is the larger (unless they are the same number), so let us assume, without loss of generality, that b <= c.

So b*b <= b*c =a. In other words b2 <= a. Since both a and b are positive, this means that b <= square root(a).

Hence, we have demonstrated that if a is a composite number, then it has at least one divisor less than or equal to its square root.

Q.E.D


So if we have a number n and write down all of the number between 2 and n, then successively eliminate all of the numbers in the list which are multiples of the numbers between 2 and the integer part of the square root of n, then we have eliminated all of the composite numbers from the list.

Input a number n from the user. Create a file called Eratosthenes.data. For each number from 2 to n, create a string with the value "y", join these strings by semicolons and write them to Eratosthenes.data.

For each number, counter, from 2 to the integer portion of the square root of n do the following.

For each number, counter1, from counter+1 to n, if counter1 is divisible by counter, then locate the position in the file Eratosthenes.data corresponding to counter1 and put the string "n" in that position.

Read the string out of the file Eratosthenes.data and for each position with a "y" print out that number is prime, otherwise print out that number is not prime.

A Solution

import java.io.*;
import java.util.*;

public class Write {

// Since we are not going to create an instance of the class Write,
// in order to use a method we will use class methods so that
// we can call them from the main method.

   public static void change_string(int n) throws IOException {

// This methods accepts as input the number corresponding to the
// position we wish to change.

// In order to read from a file we must use the two classes
// FileReader and BufferedReader.

      FileReader file = new FileReader("Eratosthenes.data");
      BufferedReader inputFile = new BufferedReader(file);

// To get a line out of the file we use the readLine()
// method.

      String s = inputFile.readLine();

// Since we are done reading from the file we now close it.

      inputFile.close();

// We now break the string apart into tokens delimited by a ;.

      StringTokenizer myStringTokenizer = new StringTokenizer(s,";");

// We record the total number of tokens found in the string.

      int numberOfTokens = myStringTokenizer.countTokens();

// We are going to begin putting the tokens we find back together
// until we come to the token which we want to change. So we read
// the first token from the String and place that into a String
// called output.

      String output = myStringTokenizer.nextToken();

// Now we must decide which token we want to replace.
In order to determine this, let us look at the following example string. Suppose that n is 5. Then the string stored in Eratosthenes.data is

y;y;y;y
Consider the following data.

# of tokens left (y) number corresponding to position of token being read (x)
4 2
3 3
2 4
1 5

Let us use a couple of these data points to derive a general formula to relate the # of tokens left to the number corresponding to the position of the token being read.

Two (x,y) pairs are (2,4) and (3,3). The slope of a straight line between these two points is (3-4)/(3-2) = -1. Since the equation of any non-vertical straight line is y = mx + b, where m is the slope, and (0,b) is the y-intercept. Using our first (x,y) pair, we find that 4 = -1*2 + b, so that b is 6. So the general equation of the straight line passing between the points (2,4) and (3,3) is y = -x + 6.

So this means that y = 6-x. In other words, the # of tokens left is equal to 6 - the number corresponding to the position of the token being read. Where does 6 come from? Well if we look at our string, we see that 6 is the total number of tokens + 2. So our relationship is that when we are ready to read a token from the string, the result of countTokens() is equal to the total number of tokens + 2 - the number corresponding to the position we want to read. So as long as we haven't reached that point, we keep reading tokens out of the string. We stop when we get to the position of the token we want to change.

      while (myStringTokenizer.countTokens() > numberOfTokens-n+2)
         output += ";" + myStringTokenizer.nextToken();

      output += ";" + "n";

// We now read the token we replaced but don't record it.

      s = myStringTokenizer.nextToken();

// Now we go through the rest of the tokens and add them to the
// String output.

      while (myStringTokenizer.countTokens() > 0)
         output += ";" + myStringTokenizer.nextToken();

// Now we are ready to write the String back to the file so we
// again declare a FileWrite object and a PrintWriter object.	

      FileWriter file1 = new FileWriter("Eratosthenes.data");
      PrintWriter outputFile = new PrintWriter(file1);

// We write the string to the file.

      outputFile.println(output);

// We now close the file.

      outputFile.close();
   }

   public static void main(String[] args) throws IOException {

// In order to get input from the user we must use the InputStreamReader
// and BufferedReader classes.

      InputStreamReader stream = new InputStreamReader(System.in);
      BufferedReader keyboard = new BufferedReader(stream);

      System.out.println("Please enter n");

// We now use a wrapper class to turn the String that the user input
// into an int.

      int n = new Integer(keyboard.readLine()).intValue();

// Now we set up an output file and prepare it to be written to. We
// use the FileWriter and PrintWriter classes.

      FileWriter file = new FileWriter("Eratosthenes.data");
      PrintWriter outputFile = new PrintWriter(file);

// We initialize our output String with a beginning "y".

      String output = "y";

// Now we add in "y"s corresponding to all numbers up to n.

      for (int counter=3;counter<=n;counter++)
         output += ";" + "y";

// Now we write the String to our output file.

      outputFile.println(output);

// Now we close the output file.

      outputFile.close();

// We now set up two loops. The outer loop will go from 2 to the
// integer portion of the square root of n. The inner loop will
// go from counter+1 to n. If the counter variable in the inner
// for loop has a zero remainder when we divide it by the counter
// variable in the outer for loop, when we then change the string
// in the position corresponding to the counter variable in the inner
// loop in the String stored in Eratosthenes.data to an "n".

      for (int counter=2;counter<=(int)(Math.sqrt(n));counter++)
         for (int counter1=counter+1;counter1<=n;counter1++)
            if (counter1 % counter == 0)
               Write.change_string(counter1);

// Now that the string is set up, we read it from the file, and
// tokenize it. For each token we get to, if it is a "y" we report
// that the number corresponding to that position is a prime
// number. Otherwise we report that the number corresponding to
// that position is not a prime number.

      FileReader file1 = new FileReader("Eratosthenes.data");
      BufferedReader inputFile = new BufferedReader(file1);

      String s = inputFile.readLine();

      StringTokenizer myStringTokenizer = new StringTokenizer(s,";");

      int counter = 2;

      while (myStringTokenizer.countTokens() > 0)
         if (myStringTokenizer.nextToken().equals("y"))
            System.out.println(counter++ + " is prime.");
         else
            System.out.println(counter++ + " is not prime.");
   }

}

  • Write.java
    There is a runtime error/logic error that comes up sometimes in Object-Oriented Programming that may be very difficult to correct. This runtime error/logic error is a NullPointerException. Consider the following code.

    public class Test {
       String a;
    
       public Test(String b) {
          String a = b;
       }
    
       public void display() {
          a = a.concat(" ");
       }
    
       public static void main(String[] args) {
          Test myTest = new Test("3");
    
          myTest.display();
       }
    
    }
    
    The code compiles without errors. However, when the program is run the following error is reported.

    java.lang.NullPointerException
            at Test.display(Test.java:9)
            at Test.main(Test.java:15)
    
    The reason for this is the following.

    Recall that String is an object. When we make the declaration String a, we are declaring a new reference to a String object. When the object is created, this reference is by default set to null. That is, the reference points nowhere. So we can't do some of the things with this reference that we would normally do with a reference to an actual object.

    When a method is entered by Java, its allocates space for all of the parameters in its formal parameter list and also any variables declared within the method. However, these variables are only allocated space as long as Java is executing that method. When Java is done executing that method, all space that was allocated for variables within the method is deallocated and those variable references are no longer valid.

    Notice that when the class is instantiated a new reference called a is created and by default set to null. When the compiler enters the constructor a local variable called a is created and given the value b. So when the constructor is finished executing, the new reference a is no longer valid. So any reference to a in other parts of the class refers to the instance variable a, which is still set to null.

    In the method display() we try to concatenate something onto the String object pointed to by a. However, a still points to nothing. Therefore, Java reports a NullPointerException.

    There are a few definitions related to this concept.

    Scope - The scope of an identifier refers to the region of a program in which an identifier can be used.

    Class Scope - An identifier with class scope is accessible from it point of declaration throughout the entire class. Examples of these would be instance variables and class variables.

    Block - A block is a section of code beginning with an {, ending with a }, and containing declarations and executable statements.

    Block Scope - An identifier with block scope is only accessible from its point of declaration to the end of the block.

    Consider the following two programs.
    public class Test1 {
    
       public static void main(String[] args) {
          String a = "2";
    
          String a = "3";
       }
    
    }
    
    When this program is compiled the following error is reported.
    Test1.java:6: Variable 'a' is already defined in this method.
          String a = "3";
                 ^
    1 error
    
    public class Test2 {
    
       public static void main(String[] args) {
          for (int counter=1;counter<=10;counter++) {
             String a = "3";
          }
    
          String a = "2";
       }
    
    }
    
    The second program compiles with no problem. The reason for this is that in the second program the first declaration of the String object a is within a block. So after the block is finished executing, the object reference a is no longer accessible. That is, Java doesn't know about a variable called a.

    Lifetime - the lifetime of an identifier is the period during which an identifier is known to the computer.

    Heap - the heap is a special area of memory reserved for the dynamic allocation of computer memory to objects during run-time.

    Garbage - after an object is allocated space on the heap and we lose the reference to the object, then the object is considered garbage.

    Garbage Collection - instead of allowing unreferenced space to take up computer memory we perform garbage collection to remove all objects that are garbage. In Java this is done automatically.

    Recall the points we mentioned about primitive data types.

    When dealing with ints, floats, doubles, and other numeric data types Java can perform automatic type conversion if it can safely store one data type in another.

    For example,

    int a = 3;
    
    double b = a;
    
    is legal because Java can safely store an int as a double.

    However,

    double a = 3.0;
    
    int b = a;
    
    is not allowed because Java can't safely store a double as an int.

    Java can safely store one data type as another if it can do so without losing any bits. Since a double is stored in 8 bytes and can contain much larger numbers than ints, we won't lose any bits if we store an int as a double. However, since an int is only stored in 4 bytes, its possible that we could lose some bits if we tried to store a double as an int.

    We now come to the last major construct we will discuss this semester. Before we discuss let's consider the following problem.

    Suppose you are put in charge of collecting information about customers. Since your business is very small you only have five customers. So to store their names you might use variable references like the following.

    String customer1 = "Karl Gauss";
    String customer2 = "Neils Henrik Abel";
    String customer3 = "Evariste Galois";
    String customer4 = "Augustin Louis Cauchy";
    String customer5 = "Karl Weierstrass";
    
    Similarly you could contain their addresses in variables address1, address2, ..., address5. You could also collect other information about them.

    You could then print out reports based on their purchases.

    This process will work very well while you have only five customers. But what happens when your business grows to ten customers, one hundred customers, or one thousand customers?

    There must be a more efficient way of storing information about these customers than to create ten, one hundred, or one thousand different variables.

    There is a more efficient way of doing this. It is called an array.

    The Array

    An array is a named collection of entities all of the same type. That is an array might be a collection of ints, floats, doubles, Strings, or other objects.

    The elements stored in an array are accessed by the name of the array and the position in the array in which the element is stored.

    An array can have more than one-dimension but let us begin our discussion with a one-dimensional array.

    To declare an array we can use the following syntax.

    data-type[] arrayname = new data-type[number of entries];
    
    For example if we want to declare a one-dimensional array of Strings called customer we could use the following syntax.

    String[] customer = new String[5];
    
    As always Java begins counting at 0.

    So the first element in the array is in position 0.

    In order to get experience using an array consider the following problem.

    Suppose you wish to create an array whose elements are 5-minute blocks of time from 7:00 am to 11:00 pm. For times after Noon we will use military time.

    For example the array should contain the following.

    times[0]  = 700;
    times[1]  = 705;
    times[2]  = 710;
    times[3]  = 715;
    times[4]  = 720;
    times[5]  = 725;
    times[6]  = 730;
    times[7]  = 735;
    times[8]  = 740;
    times[9]  = 745;
    times[10] = 750;
    times[11] = 755;
    times[12] = 800;
    ...
    
    As another exercise try rewriting the Sieve of Eratosthenes with an array? Do you find it much easier?