Monkeys at Keyboards: APE Antics
© Michael James Heron and Pauline Belford
Topic: Java Programming
Level: 1
Version: alpha

It is better to keep your mouth closed and let people think you are a fool than to open it and remove all doubt.
Mark Twain

10 - Stringing Your Words Together

PreviousTable of ContentsNext
Forum


Chapter Objectives

By the end of this chapter, the reader will be able to:

  • apply string parsing routines


10.1

Introduction

In days gone by, there didn't exist such a thing as a 'String' to programming languages like C. They treated Strings as something much less intuitive - contextually linked lists of single characters. This made it quite difficult to work with strings of text, which was unfortunate - strings are one of the most useful and common types of data that programmers have to deal with.

Those Bad Old Days are gone now though, and as Java programmers we have access to a type of data called, unsurprisingly, the String. This data type is used for storing and manipulating strings of alphanumeric characters.


Terminology Alert!

Alphanumeric characters are characters that range from letters, numbers, to special symbols.


The String data type is slightly different to the data types you have used before -it's more powerful and complex than ints and doubles. We have used the String data type before, but in this chapter we're going to look at some of the things that can be done with the String datatype. Strings are immensely versatile and will provide a very useful tool in your arsenal of weapons, so let's get on with the main content.

10.2

The Structure of a String

We create a String in the standard way:

String myString; 

And we assign values in the usual manner:

myString = "Hello World!"; 

This much is familiar - but much like the Pacman game, Strings have methods to go with them, and these methods let us perform useful functions on the data. Say for example we wanted to turn a string into an upper-case version of itself:

myString = myString.toUpperCase(); 

It's really that simple - myString would then contain an uppercase version of the text it originally contained. We can turn a string entirely lower-case too by using the toLowerCase method:

myString = myString.toLowerCase(); 

Not bad, eh? But that's only the beginning - we have so much more we can do with Strings, and we'll be using in the course of this chapter to tell Pacman what to do.

So, a String contains some data (the text you give it), and some methods for acting on that data. This sets them aside from ints and doubles, and so we categories data types into two categories:

  • Primitive data types such as ints, doubles and chars
  • Complex data types, such as Strings and the Pacman game.

Java treats these two kinds of data differently when it sees them, but we'll talk about that later - for now, just take on board the fact we are working with a complex data type.

10.3

String Comparisons

If you've spent any time in the past few chapters trying to work with Strings, you may have noticed something quite strange - if you use == or != to try and determine if one string equals another, it doesn't always work. This is because of what is hinted above - Java treats complex data types differently for primitive data types. A String is a complex data type, and so when you try to use one of the equivalence operators on the String, what Java does is check to see if the memory location of one string is equal to the memory location of another string.

This will only be the case if the two strings are the same string - but that's not usually what you are trying to do. What you want is to know if String A has the same contents as String B, and the == operator will not do that for you. We'll discuss this in more depth when we talk about reference and value data-types later in the book.

Instead, we must use the equivalence methods provided by the String data-type to do our comparisons - this is the only way to ensure that our code always works. The first of these methods is equals(), and takes a single String parameter which is the String to check against:

if (myString.equals ("bing!")) { 
// Do something
}

Equals is a case sensitive comparison, which means that the if statement above will evaluate to true only if myString contains "bing!". It won't work if it contains "Bing!", "BING!", or any combination of alternate casings.

This can be quite unhelpful, so Java provides us with a second equivalence method. It works exactly the same way, it just ignores the case of letters:

if (myString.equalsIgnoreCase ("bing!")) { 
// Do something
}

This method will cause the if statement to evaluate as true if myString contains "Bing!", "bing!", "BinG" or any combination. This is much more convenient!

10.4

String Additions

Since Strings are alphanumeric in nature, we also get some zany results if we try and perform mathematical operators on them as you can image. What is "bing" * "bing", for example? In Java's opinion, it is an error and it will refuse to countenance such nonsense, and you will be similarly rebuffed if you try and divide or subtract strings.

However, Java is happy for you to add Strings together - all it will do is take the second string and add it onto the end of the first string. This is a process called concatenation, and in the context of adding strings together the + symbol is properly known as the concatenation operator.

Let's look at example of this:

import draconia.APE.pacman.*; 

public class StringMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
String one = "Hello";
String two = "There";
main.setMap ("blank");
main.showGame();
main.sendMessage (one + two);
}

}

This gives us the following output when we run the program:

String Output
Fig 10.1: String Output

It just takes the second String and adds it onto the end of the first string. But it's also quite clever, because it will also do 'on the fly' conversions of other data types into Strings as it does so. That may sound complex, so let me explain.

Let's say you wanted to store a number as a String. How do you do it? The first instinct may be to do something like this:

int number = 100; 
String numberAsString = number;

Alas, no. Java will complain bitterly at this, since an int is not a String, how many times does it have to tell you?

But we can fool Java - Java will automatically convert any other data type into a String if you try to add it to another String - first it converts the other data types and then attempts to concatenate them in the way you request:

String numberAsString = "" + number; 

In this case, Java thinks to itself 'Ah-ha, I'm being asked to add these two together, but number isn't a string. I'll make it into a String, which will solve the problem'. This then becomes, in Java's mind:

String numberAsString = "" + "100"; 

This then becomes a string containing 100 concatenated onto an empty string, giving us "100". Very handy!

This kind of implicit data conversion allows you to turn any kind of data type into a string ready for making use of.

We can concatenate as many strings as we like:

String one = "Ding "; 
String two = "Dong ";
String three = "The witch is dead";
String four = one + two + three;

And we can also implicitly convert any number of variables as we do so. This will become more and more powerful the further you progress through this book.

10.5

String Methods

So, what methods do strings have to Excite and Entice us? As it happens, they have lots and lots of them! Some of these are too complex for us to go into, but we'll look at some of the simpler ones here and why they are important to us.

We have a trim() method, which removes any white-space from the beginning and end of a string - very useful for ensuring that you don't need to waste time checking for spaces where they shouldn't be. We use this the same way as we do toUpperCase or toLowerCase:

myString = myString.trim(); 

This would change a string such as:

"   look at all the white space   " 

Into:

"look at all the white space".  

We'll see that this is a very handy method later in the chapter when we start trying to communicate with Pacman.

Another very useful method we'll soon see in action is length(), and this gives us how many characters there are in a string. This gets returned as a whole number:

int characters = myString.length(); 

These methods will serve as the basis for what we are going to do in this chapter. We will introduce further methods during the text of the book as they are appropriate.

10.6

Issuing Orders

Wouldn't it be cool if we didn't have to write the program for Pacman for each map he's on? We could load up a map, look at it, and tell exactly how it should be solved. It's fairly hard work making Pacman work out how to navigate a map, after all - why can't we control him ourselves like in the arcade game?

Well, there's no reason why not - in fact, why don't we make it so that we can issue him commands ourselves? We don't know how to make him respond to a keyboard or mouse (and won't for a long time), but we do know how to request information from the user - we saw that in chapter three - we use the getTextFromUser method. Why not ask the user for instructions?

By Jingo, that's a great idea - let's do that! We write the initial shell of the program, prompting the user for instructions:

import draconia.APE.pacman.*; 

public class ParseMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
String instructions;
main.setMap ("blank");
main.showGame();
instructions = main.getTextFromUser ("I am a soldier, I was bred to take orders!");
}

}

Hrm. Now what? We run the program and we get asked for instructions - but nothing happens!

Issuing Instructions
Fig 10.2: Issuing Instructions

That's because we have the instructions from the user, but we're not doing anything with it. We need a way to turn the String we have in the instructions variable into an actual command to Pacman.

10.7

String Parsing

Extracting such information from a string is a process known as parsing. We must parse our string to extract the juicy, tasty orders.


Terminology Alert!

Strictly speaking, parsing is the process of extracting regular series of meaningful strings from larger strings, but we use it informally in the context of turning a large string into useful information.


How do we do that? Well, that's the first problem - how do we want the user to submit orders? What if the user write 'Turn left, you yellow git'? How do we tell from that what Pacman is supposed to do? We can read it and understand, but how do we get pacman to understand?

The simple answer is - we don't. It's well beyond us to write a program that will analyse any text given for meaning and instruction, purely because of the richness of language when expressing these kind of things. 'Turn left, and then move forward if there are no obstacles in the way' is a meaningful instruction, but one that would be very difficult for us to parse.

Instead, we simplify the problem - we create a protocol for interacting with Pacman - a stripped down set of words to which Pacman will respond. This will be his command set - the list of orders to which he will respond. To begin with, we will make it simple and give him only three:

  • Move
  • Left
  • Right

So, if the instruction the user provides isn't any of these, we present them with an error message. If it is one of these, we perform the desired command:

import draconia.APE.pacman.*; 

public class ParseMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
String instructions;
main.setMap ("blank");
main.showGame();
instructions = main.getTextFromUser ("I am a soldier, I was bred to take orders!");
if (instructions.equals ("move")) {
main.move();
}

else if (instructions.equals ("left")) {
main.turnLeft();
}

else if (instructions.equals ("right")) {
main.turnRight();
}

else {
main.sendMessage ("Eh? What? Help, I don't understand your gibberish!");
}

}

}

Run this, and we can actually issue a simple command to Pacman. Fantastic! Except, that if we issue something that is 'kind of' like an instruction he won't do anything. He understands 'right', but not 'Right' - that's because we are using .equals() for our method which is case sensitive. Let's change it to equalsIgnoreCase:

import draconia.APE.pacman.*; 

public class ParseMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
String instructions;
main.setMap ("blank");
main.showGame();
instructions = main.getTextFromUser ("I am a soldier, I was bred to take orders!");
if (instructions.equalsIgnoreCase ("move")) {
main.move();
}

else if (instructions.equalsIgnoreCase ("left")) {
main.turnLeft();
}

else if (instructions.equalsIgnoreCase ("right")) {
main.turnRight();
}

else {
main.sendMessage ("Eh? What? Help, I don't understand your gibberish!");
}

}

}

Super - but it's still not very robust. If we type " right" for example, it won't work - so let's trim the instructions before we attempt to parse it:

import draconia.APE.pacman.*; 

public class ParseMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
String instructions;
main.setMap ("blank");
main.showGame();
instructions = main.getTextFromUser ("I am a soldier, I was bred to take orders!");
instructions = instructions.trim();
if (instructions.equalsIgnoreCase ("move")) {
main.move();
}

else if (instructions.equalsIgnoreCase ("left")) {
main.turnLeft();
}

else if (instructions.equalsIgnoreCase ("right")) {
main.turnRight();
}

else {
main.sendMessage ("Eh? What? Help, I don't understand your gibberish!");
}

}

}

Now he'll accept instructions regardless of how sloppy they are formed - 'RIGht', " RighT " and any combination inbetween. Very nice indeed.

Of course, he only accepts a single instruction before he gives up here - really, what we want to be able to do is keep issuing instructions until we're fed up... we can do this by adding in a fourth entry to our command set: STOP.

We'll then put the prompting for instructions and the parsing of these instructions into a while loop - while the last command was not stop, we keep going.

Except, how do we do that? We already know that != will not work on a string, and there is no doesNotEqual method. How do we check that something is not true if we only have a method to check if it is?

We use the not operator to do this: ! . We say "while it is not true that instructions is equal to stop":

while (!instructions.equalsIgnoreCase ("stop")) { 
}

Like so:

import draconia.APE.pacman.*; 

public class ParseMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
String instructions = "";
main.setMap ("blank");
main.showGame();
while (!instructions.equalsIgnoreCase ("stop")) {
instructions = main.getTextFromUser ("I am a soldier, I was bred to take orders!");
instructions = instructions.trim();
if (instructions.equalsIgnoreCase ("move")) {
main.move();
}

else if (instructions.equalsIgnoreCase ("left")) {
main.turnLeft();
}

else if (instructions.equalsIgnoreCase ("right")) {
main.turnRight();
}

else if (instructions.equalsIgnoreCase ("stop")) {
main.sendMessage ("At last, rest. Bless you mortals.");
}

else {
main.sendMessage ("Eh? What? Help, I don't understand your gibberish!");
}

}

}

}

And there we have it, simple instructions issued to Pacman - and it puts the orders on its skin or else it gets the hose again. Smashing!

10.8

A More Advanced Example - Characters and Indexes

We're lazy people, let's not deny it - and all that typing seems like work. Sure, Pacman will obey us, but couldn't we make it so that all we had to do was type in a list of instructions and he'd follow them? In fact, can't we just type in single letters and get him to follow them?

String listOfInstructions = "mmlmms"; 

Couldn't he interpret that as 'move, move, left, move, move, stop'? That way we could get all of this done in an instant and then go off and do something else.

Well, there is indeed a way you can do that, and it underlines the way Strings are structured... they are lists of alphanumeric data, and we can access each individual part of that list if we want to.

The position that a letter has in a string is known as its index, and the letter at a particular index is known as an element. However, to be awkward, the first index in a string is 0, and so the first letter in a string has the index 0. Weird? Perhaps - but you'll get used to it.

Each of the letters can be extracted from the string as a single char variable, and we use the charAt method in the string to do this. Taking the listOfInstructions variable above, if we wanted the first letter we could do something like this:

char letter; 
letter = listOfInstructions.charAt (0);

The letter variable would then contain the value 'm'.

We know how to get the number of characters in a string - we use the length() variable. Perhaps then there's a way to step over each of the characters in the string and have him deal appropriately with the instructions?

There is indeed, and it harks back to the idea of a for loop. A for loop is a bounded loop, and we need to know how many times we are going to repeat. And indeed we do - we have the number of letters in the string. We need a counter variable (we'll call this i), we know our termination condition (we'll continue looping while i is less than the number of letters in the string), and we know what our upkeep will be - our loop would look like this:

int i; 
for (i = 0; i < listOfInstructions.length(); i++) {
}

That seems easy enough - now it's just a case of pulling off the appropriate letter:

int i; 
char letter;
for (i = 0; i < listOfInstructions.length(); i++) {
letter = listOfInstructions.charAt (i);
}

And then dealing with it. We'll use a switch statement for this:

int i; 
char letter;
for (i = 0; i < listOfInstructions.length(); i++) {
letter = listOfInstructions.charAt (i);
switch (letter) {
case 'm':
main.move();
break;
case 'l':
main.turnLeft();
break;
case 'r':
main.turnRight();
break;
case 's':
break;
default:
main.sendMessage ("Didn't understand the command " + letter);
break;
}

}

Tada, and putting it all together we get the following program:

import draconia.APE.pacman.*; 

public class AdvancedParseMonkey {

public static void main (String args[]) {
PacmanGame main = new PacmanGame();
String listOfInstructions = "";
int i;
char letter;
main.setMap ("blank");
main.showGame();
listOfInstructions = main.getTextFromUser ("Give me orders, lots of them!");
for (i = 0; i < listOfInstructions.length(); i++) {
letter = listOfInstructions.charAt (i);
switch (letter) {
case 'm':
main.move();
break;
case 'l':
main.turnLeft();
break;
case 'r':
main.turnRight();
break;
case 's':
break;
default:
main.sendMessage ("Didn't understand the command " + letter);
break;
}

}

}

}

Run the program and input your instructions:

A list of instructions
Fig 10.3: A list of instructions

Hit return, and watch him wend his way through your instructions like a drunk trying to get home on a Saturday night:

Drunken Wanderings of Pacman
Fig 10.4: Drunken Wanderings of Pacman

That's not bad at all: A fully programmable pacman implemented in a few lines of code. It would be better if we could give him proper instructions though, such as 'move until you see an obstacle' or 'turn around', but we'll talk more about that in later chapters.

10.9

Conclusion

Strings are a very useful kind of data type, and give the developer enormous power over manipulation and modification of the data contained within. They are a complex data type, and there are dozens of more powerful methods available within (you'll find these discussed in some more depth in the Javanomicon), but even this overview of Strings gives you considerable flexibility in how you deal with the input sent into your pacman game from the user.

Further Reading

The following table details further reading on the topic in this chapter, and also any external resources that you may find useful.

ResourceDescription
Example Programs from this chapterThis is a zip file of all the programs shown in this chapter.

PreviousTable of ContentsNext

© 2004-2006 Michael James Heron and Pauline Belford