Monday, November 17, 2014

Arrays of Golden Sun

We've seen processing of simple integer values already, but what if you have to deal with a bunch of them?  Perhaps you have 20 test results and want to do something with them.

I suppose we could write something like this:

int testResult1;
int testResult2;
int testResult3;
...

Then we could write a calculateAverage function...

int calculateAverage(int testResult1, int testResult2, int testResult3..

You know, I'm already tired of typing that, and I've got a whole lot more to go.  And you just know that next year there will be 22 tests, right?

Never fear, we aren't stuck with such awfulness.  Java (as with most languages) supports the concept of arrays.  Don't fear the word, the concept is actually pretty simple to grasp.

First, let's go back and deal with testResult1.  We can think of that variable as being a box that holds a number.  We can put a number in the box, or we can ask the box what number is in it.

testResult1 = 97;  //We've just put 97 into the box

System.out.println("Your first test result was " + testResult1 ); // We've just asked the box what number is in it.

All the operations boil down to one of those.  If we do math, we're just asking what's in the box.  If we reassign the number (like 'testResult1 ++') we're really just asking what's inside, making a new number and putting the new one into the box.  Whatever was in the box before is gone once you do this, there's no room for two numbers.  These are pretty simplistic boxes.

So, I can conceive of 'testResult1' through 'testResult20' as being a row of 20 boxes, each of which I can use to store or retrieve a number.  But man, it's going to be annoying to do much useful work with individually addressable variables like that.

Enter the array.

Let's just take that same row of 20 boxes and relabel them.  In fact, let's glue them together in a long line and give the whole spiel one single label.  We'll call it 'testResults'.

Of course, testResults is not an int.  It's a grouping of 20 ints.  That's going to make it slightly more difficult to do math on it.  Enter the square bracket and the array index.

If we want to refer to the item in the first box, first we have to get over a little hump.  For reasons we do not really need to get into now, just trust me that they're valid, the first box is number 0, not 1.  So, what we previously would have called 'testResult1' can now be called 'testResults[0]'.  It is important to understand that 'testResults' is NOT an integer.  However, testResults[0] (or testResults[13]) IS an integer and is just a marker for one of the boxes in our row.

I know, that seems a bit more difficult at first, but it turns out not to be.  Here's why:

Remember before?  We had to declare testResult1, testResult2, etc. on individual lines?

Well now we can just do:

int [] testResults = new int [ 20 ];

We've specified that testResults is not an int, but an array of ints with the '[]' notation.  We've made it have 20 boxes with 'new int [ 20 ]'.  (This can be done live at runtime, you could do 'new int [ someCalculatedValue ]').  In one fell swoop we've made storage space for 20 individual (but related) integer values.  It gets better.

The 'calculateAverage' function can now be declared like this:

int calculateAverage( int [] valuesToAverage ) {

Well isn't that a whole lot easier?  And you can imagine that before, that calculateAverage method would have had to look something like this:

return (value1 + value2 + value3 + ... + value20) / 20;

Which seems like it takes up a lot of valuable real estate on screen, and, as mentioned before, is irritating to change.

Our new version takes advantage of the fact that the array is indexed (it could do more but I'll talk about that later):

float total = 0;

for (int i = 0; i < 20; i ++ ) {
    total = total + valuesToAverage[i];
}
return total / 20f;

That's the whole thing.  I didn't even get tired.

Of course, this can be improved further.  What if there aren't 20 values in the array?  What if there are 12?  We could change the code, or we could let the array tell us how big it is:

float total = 0;
float valueCount = valuesToAverage.length;

for (int i = 0; i < valueCount; i++ ) {
    total = total + valuesToAverage[i];
}
return total/valueCount;

Now we have a completely generic method for averaging any number of integer values we want, that we will never have to change again.  All we need to do is make sure before using it that the array we're passing in is sized correctly and populated.  We can stash that into a utility class somewhere and use it for years.

Naturally none of this is going to work without stashing numbers in the array in the first place.

One can initialize an array with hard coded values:

int [] testScores = { 100, 95, 22, 84 };

Or one can write code to set the values.  Assuming we have appropriate helper functions in place, this kind of thing could work.  It's more likely that you'd be getting input from the user or a file for your early experiments with the language.  I think we'll talk about those soon.

int [] testScores = new int[ getNumberOfScores() ];
for ( int i = 0; i < getNumberOfScores(); i++ ) {
    testScores[i] = getScore(i);
}

Of course, that's just the tip of the array iceberg, but I think it gets across the main points:

Arrays:

  1. Are just ordered, indexed lists of the data types we already understand
  2. Can be passed as single labels but all the internal values are readily available
  3. Contain 'meta' information about their contents, particularly how many items they hold
  4. Can make many data processing tasks easier to code and understand
If you're having trouble with this concept, please let me know.  It's good to have a firm grounding in basic aggregate data structures like this before we move on to more complex topics.  Much of what we do in programming involves understanding some concept, then not using it directly, but instead using higher level structures that are better handled if you know what's going on underneath.

View Code

The Old Switcheroo

Let's talk about the 'switch' statement for a bit.  This is a form of conditional that is better suited to some tasks than if/else.

First, let's look at the structure of a switch:

switch(variable) {
    case value1:
        //Code to execute for value1
        break;

    case value2:
        //Code to execute for value1 or value2

    case value3:
        //Code to execute for value3
        break;

    default:
        //Code for any value other than the three above

}
// Done with switch

In detail.

If variable is equal to value1, we execute the code under 'case value1', then we 'break', which means we exit the current block (jump to 'Done with switch').

If variable is equal to value2, we execute the code under 'case value2', then continue to execute the code under 'case value3' before we hit the 'break' and head out to 'Done with switch'.

If variable has any other value, we execute the code under 'default'.

This is basically equivalent to:

if (variable == value1 || variable == value2) {
    if (variable == value1) {
        //Code to execute for value1;
    }
    //Code to execute for value1 or value2
}
else if (variable == value3) {
    //Code to execute for value3
}
else {
    Code for any value other than the three above
}

But I think in this case the switch is a bit easier to follow.

Sadly, the switch works with only a limited number of data types.  Basically it will work for atomic types that represent integer values, the objects that wrap those atomic types (like Integer or Long), and String.  It will also work with Enumerations, but I haven't written about those yet...  I guess I should get to those soon.

So, let's combine a for loop with a switch statement to get some output, shall we?

        for (int switchValue = 0; switchValue < 5; switchValue ++) {
            switch (switchValue) {
                case 0:
                    System.out.println("Just starting out.");
                    break;

                case 1:
                    System.out.println("Making progress.");
                    break;

                case 4:
                    System.out.println("All done.");
                    break;

                case 5:
                    System.out.println("We will never get here.");
                    break;

                default:
                    System.out.println("Getting there...");
                    break;
            }
        }


This runs a simple loop that goes from 0 to 4.  Along the way, we run a switch to execute various blocks of code based on that value.  The output from this block of code looks like this:

Just starting out.
Making progress.
Getting there...
Getting there...
All done.

If you follow the code, I hope this makes sense to you.  The 'default' statement catches anything that isn't 0, 1, 4 or 5, so it runs a couple of times in the middle.  If this is not clear, drop me a line and I'll try to improve it.