Saturday, November 12, 2011

Program to Create Five Random "Words"

Problem: Write a method randomWord that returns a randomly constructed “word” consisting of randomly chosen letters. The number of letters in the word should also be chosen randomly by picking a number between the values of the named constants MIN_LETTERS and MAX_LETTERS. Write a ConsoleProgram that tests your method by displaying five random words. (Roberts ch 10, problem 2).

What it looks like:



/* File: fiveRandomWords
 * --------------
 * This program generates random "words," just random letters stuck together. The characters of the word are 
 * random, but must be an upcase letter-- these are integers that correspond to the ASCII characters of the 
 * upcase alphabet. The length of the word is also random, but bounded by the constants MIN_LETTERS and
 * MAX_LETTERS.
 * 
 * The run method of the program simply starts of with blank String instance and keeps pulling random letters
 * out of the random generator until the word length is reached. It relies on a private method to generate the 
 * random characters and to set the word length.
 */

import acm.program.*;
import acm.util.RandomGenerator;

public class FiveRandomWords extends ConsoleProgram { 
    public void run() {
        for (int i = 0; i < 5; i++) {
            int wordLength = rgen.nextInt(MIN_LETTERS, MAX_LETTERS);
            String myRandomWord = " ";
            for (int j = 0; j < wordLength; j++) {
                myRandomWord += randomLetter();
            }
            println(myRandomWord);
        }
    }
    
    private char randomLetter() { 
        return (char) rgen.nextInt((int) 'A', (int) 'Z');
    }
    private RandomGenerator rgen = RandomGenerator.getInstance();
    private static final int MIN_LETTERS = 3;
    private static final int MAX_LETTERS = 10;
}
What made it tough:

This simple problem is all about getting comfortable with scalar types: data values that can be represented as numbers. The key snippet in the program is this:

 private char randomLetter() { 
        return (char) rgen.nextInt((int) 'A', (int) 'Z');
    }

How do you return a *character* that's random but bounded by the *integers* A and Z? Because the "char" data type in Java maps directly to Unicode and ASCII. Unicode and ASCII are both systems that match characters (letters, punctuations marks, etc) to integers so that it's easier for the computer to understand. 

Fun facts I learned: 
  • Precursors to ASCII literally mapped 'A' to 1, 'Z' to 26, and everything in-between, but ASCII didn't do so since there are additional characters that precede letters. 
  • The ASCII system came first, Unicode second. ASCII characters don't encode most non-Western characters, so Unicode was created with thousands of new characters to account for these.
  • Unicode contains everything that's in ASCII, and to be completely backwards-compatible, the first 128 digits of Unicode map directly to ASCII


Because characters are mapped this way, you can perform some numeric operations on *letters* as specifying a range like I did above, or taking a letter 'A' and adding 5 to get 'F.'  When specifying the range of characters, you could either provide the character or the number equivalent-- the computer can handle each one and in fact the (int) cast isn't necessary- it's there to make things clearer for humans so they don't think, "Why are you bounding nextInt with characters?" You must specify the (char) cast however to make sure that when you do rgen.nextInt, you get a letter back, not a number.


Program to Create Acronyms

This came from the book as an example, but I wanted to make it better :)

This program, taken from the unit in the book that deals with characters and string manipulation, lets the user input a string (usually a sentence of multiple words) and get an acronym back. The method that builds the acronym works by taking the very first character, and after that, hunting for space characters. It looks at the character right *after* each space and slices that following character to add to the acronym.

There is a bug however-- what happens if the user enters in "hello**world" (say that "*" is a space here)? The acronym SHOULD be "hw", but instead you get "h*w." No good.

I wrote a program, first to test out the buggy method to verify what the acronym for "hello**world" would be, and then to fix that edge case.

What it looks like:



/* File: acronymBuilder
* --------------------
* This program takes a string and returns the acronym, or, a string made up of the first character of each
* word. It works via a private method that takes the first character and saves it to the instance variable
* "result." Then it finds each space, takes the character directly after the space, and takes that following 
* character and appends it to "result" until there are no spaces left.
* 
* This method originally appeared in ch 10 of Roberts' "The Art and Science of Java," but the original method
* didn't account for inputs where there were 2 spaces in a row, so I'm making the modification and writing a
* run method to use it.
 */
import acm.program.*;
 
public class acronymBuilder extends ConsoleProgram {
    public void run(){
        String initialString = readLine("Enter a string you want to generate the acronym for:");
        String result = acronym(initialString);
        println(result);
    }
    
    private String acronym(String str) {
        String result = str.substring(0, 1); 
        int pos = str.indexOf(' '); 
        while (pos != -1) {
            if (str.charAt(pos + 1) != ' ') { 
                result += str.substring(pos + 1, pos + 2); 
            }
            pos = str.indexOf(' ', pos + 1); //Finds the next space sign           
        } 
    return result;
    }
}
What made it tough: There were a couple bugs that stumped me along the way.
First, I wanted to write an "if" statement checking to see if there are two space signs in a row. Specifically, you look at a space sign and see if the character after it is also a space sign. The "if" statement looked like this:

if (str.charAt(pos + 1) != " ");

Eclipse highlighted this statement in red and did not like it. Why?

My first thought was because of that problem that you can't compare strings with each other. The "==" and "!=" operators are mathematical comparisons that you can only use on primitives like ints, whereas for strings that operator will examine the underlying object in memory that stores the string; it won't examine the value.

But wait a minute, that can't be it. First of all, comparing strings is technically a valid argument...it will just usually return an inaccurate result. Like if there were two strings "hello" and "hello" that pointed to two different objects, it will return "False" when the developer probably wanted it to return "True". 

Also, str.charAt(pos + 1)is actually a char, not a string, and chars are primitives, so it should be OK to use the "!=" on it.

The problem was that the second part of my comparison, " ", is a string, so I was comparing a char against a string which isn't ok. All I had to do to fix it was put the space sign in single quotes, since Java denotes chars with single quotes. Easy :)

The second bug came up as I tried to run the program. This is what the code looked like at that point:

import acm.program.*;
 
public class acronymBuilder extends ConsoleProgram {
    public void run(){
        String initialString = readLine("Enter a string you want to generate the acronym for:");
        String result = acronym(initialString);
        println(result);
    }
    
    private String acronym(String str) {
        String result = str.substring(0, 1); 
        int pos = str.indexOf(' '); 
        while (pos != -1) {
            if (str.charAt(pos + 1) != ' ') { 
                result += str.substring(pos + 1, pos + 2); 
                pos = str.indexOf(' ', pos + 1); //Finds the next space sign
            }
        } 
    return result;
    }
}
When I ran the code, it compiled and I input the string "ne**s****w". (There were no "*" signs really, I just used them to demonstrate) where spaces were. When I hit "return", the program didn't print anything! Why?

At first I thought there was an unclosed loop or something, but then I realized: I had inserted an "if" statement checking for if the character following the space sign is NOT another space, but I didn't tell the program what to do if that following character WAS a space! The direction to move on to the next space sign (ie "pos = str.indexOf(' ', pos + 1)" was inside the new "if" statement! Once I adjusted the stuff in my loops I was fine.

Strange though-- on that second bug, my return statement WAS outside the "if" statement, and I had already initiated "result", so why didn't my program atleast print the first character...?