Thursday, July 7, 2011

Adventures in junior programming, episode 3

Haven't been posting on this blog much lately. Since I last posted I got a new full time job at a high tech marketing firm, and also taught myself the fundamentals of both Android and iPhone programming. Interesting stuff, probably good fodder for a future post. But for now, I'd like to go over recent efforts to teach my son more programming.

We let it slip during the school year but we've picked it up again over the summer. We're doing about an hour of work several nights a week. It seems like teaching a nine year old to program irregularly is like pushing the stone of Sisyphus, since every time we take it up again, I have to re-teach concepts that seemed to stick the last time but didn't. Nevertheless, my feeling about programming has always been that you have to learn to "think like a programmer" first, and once you have burned this style into your memory, it becomes much easier to pick up any language or platform in existence.

So what is the core of thinking like a programmer? As far as I can sum up for a kid, it boils down to a few key elements which need to be practiced over and over under a variety of conditions:

  1. Output
  2. Tinkering with variables
  3. Input
  4. Loops
  5. Conditionals
  6. Logical operators
  7. Functions and classes, which fall broadly under the category of "splitting up the work into manageable smaller chunks."

I've fallen into sort of a systematic bootstrapping pattern of teaching, which goes something like this. I have milestones in mind for things Ben should know how to do well, and they should be second nature. The current milestone is: "Write a Python program that counts to ten." Once he can do this without hesitation and without complaining that he needs more hints, we'll bump it up and move on to another milestone.

Each session, I ask him to hit the milestone. If he can't do it well enough, I'll point out what he's missing and go over how it works again. After we get through this, if it's appropriate, I'll introduce a new concept, which we'll drill in the future until that becomes the new milestone.

I've also been making him follow the program logic step by step. It's not as intuitive as you might think. For instance, I ask him to explain HOW the computer goes through the stages of counting to ten, and this is the kind of response I'm looking for:

"First I set the counter to one. One is less than ten, so I enter the loop. I print the counter value. Then I add one to the counter, making it two. Now I return to the beginning of the loop. Two is less than ten, so I enter the loop..."

The kind of response I get takes frequent shortcuts, like "I keep doing that until it's ten." That may be technically correct, but it doesn't capture the essence of thinking through every step, which is critical to catching bugs. If you wind up writing a program that only counts to nine, or goes into an infinite loop, you can keep modifying lines until you get lucky, but if you can see what it's doing at every step, then you're less likely to make mistakes in the first place.

I don't even see the code. All I see is blonde, brunette, red-head...


Programming can seem tedious and repetitive, but at its best you get those "light bulb" moments when it's suddenly crystal clear what you want your program to be doing and how. And sometimes it's even more fun to write the code that solves a puzzle than to grind out the solution on your own.

33 comments:

  1. So... how long will it be before he'll be able to handle the Ruby Koans? ^_^

    ReplyDelete
  2. Good stuff. I understand the Sisyphus angle. I've been working through Head-First Java, and over the last month I've had to set it aside for a week or so a couple of times now. When I pick it back up, I'm lost, wondering if I'm going to have to go back to Chapter One and start over.

    I'm thinking about your "think like a programmer" statement, and that may be where I've gone wrong with teaching myself programming in the past. A year ago if you'd asked me, 'Why did you stop with the programming?' I'd reply, 'My memory's not good enough to memorize all the commands.' But perhaps I'm just not thinking about the language properly.

    ReplyDelete
  3. James,

    It's a good point. The syntax SEEMS to be a sticking point, but it's really the behavior that is the thing to learn. Usually I can read some well-written code in an unfamiliar language and get a general sense of what is intended.

    Where are you at in your knowledge now? Does "Write a program than can count to ten" sound hard, familiar, or ridiculously easy to you? If it's right on the borderline for you, then maybe you should do what I'm doing with Ben. Make yourself a list of tricky but not too daunting problems, and practice quickly writing the items on your list each time you start. That could put you in the right head space so that you don't feel like you're starting with a confusing set of unfamiliar symbols each time.

    ReplyDelete
  4. Writing a program that counts to ten sounds borderline. When you say, "count," I presume you mean print to screen? Or do you just mean, 'start with a variable set to zero and increase it by one digit until it equals ten"?

    Either way sounds doable, and I can sketch out in my head the high-level steps needed. I'll fire up JavaBeans this afternoon and give it a whirl.

    ReplyDelete
  5. Yes, print it out. Make sure you use a loop rather than explicitly writing out all ten lines. Post the main section of the code here; you shouldn't need more than four lines or so to do it.

    If that seems to be too easy for you, then my next suggested task is this:
    Print the numbers from one to ten, but for the number six, print:
    "6 is my favorite number."
    and for other numbers print:
    "[x] is not my favorite number."

    That was the exercise I did with Ben most recently.

    ReplyDelete
  6. BTW, why JavaBeans? The only time I hear that used is in reference to a particular web server technology. Is it something different for you than plain old core Java? If programming is giving you trouble then you should definitely work on learning the basics of the language before moving on to something else.

    ReplyDelete
  7. I misspoke. I meant 'Netbeans IDE', the utility I use to program in Java. I like how it knows when I mis-type commands and leave off trailing braces.

    ReplyDelete
  8. Ah, ok. I use Eclipse and wasn't really familiar with NetBeans, but it sounds like it's considered a worthwhile competing IDE.

    ReplyDelete
  9. Here goes:

    class Countto10 {
    public static void main(String [] args) {
    int x = 0;
    for (int i = 0; i < 11; i++) {
    System.out.println(x);
    x = x + 1;
    }
    System.out.println("Done.");
    }
    }


    I have to admit I couldn't do this from memory. I had to get my book to finish. I knew what I wanted to do but got tripped up on the syntax ("Is it 'public static void main' or 'public void static main') etc.

    Now I'll do the 'favorite number' challenge.

    ReplyDelete
  10. Pretty good, but your use of the variable "x" is redundant. All you're doing is duplicating "i". You could trim two lines and avoid unnecessary memory usage (a small amount, of course) by changing the print line to "System.out.println(i);" and eliminating both of the remaining references to x.

    ReplyDelete
  11. Also, if you start with i=1 rather than 0, you'll be counting the way a normal person would. See the difference?

    ReplyDelete
  12. And if that is hard for you to remember, then I advise you to start with a fresh file and write it again every day until it sticks. Some of it is just muscle memory.

    ReplyDelete
  13. Good tips. So the first program runs as:
    class Countto10 {
    public static void main(String [] args) {
    for (int i = 1; i < 11; i++) {
    System.out.println(i);
    }
    System.out.println("Done.");
    }
    }

    and the second try as:
    class FavoriteNumber {
    public static void main(String [] args) {
    for ( int i = 1; i < 11; i++) {
    if (i == 6)
    System.out.println(i + " is my favorite number.");
    else
    System.out.println(i + " is not my favorite number.");
    }
    System.out.println("Done.");
    }
    }

    This is interesting.

    ReplyDelete
  14. Well, that seemed easy enough. Let me see if I can come up with something a bit more interesting.

    How are you with arrays and random numbers? Can you set up something that will:

    1. Fills an array of size 10 with random numbers between 1 and 100
    2. Prints the numbers in the array

    Note that it's important that you do this in two separate steps, so you should have two loops instead of one.

    ReplyDelete
  15. I've currently worked my way to the array chapter, so that should be doable, but I may have to look ahead for the random number generator.

    ReplyDelete
  16. I like using random numbers because my earliest interest in computers was for games, and random numbers are quite useful in all sorts of games: cards, dice, monster behaviors, etc. If you have another primary interest in the way you use computers, let me know.

    ReplyDelete
  17. All right, I'm stuck. Here's what I've got:

    class RandomTen {

    public static void main (String[] args) {
    Number [] n = new Number[9];

    int randomNum = (int)(Math.random() * 100);

    for (int i = 0; i < 10; i++) {

    n[i] = new randomNum();
    }
    for (int i = 1; i < 11; i++) {
    System.out.println(n);
    }
    System.out.println("Done.");
    }
    }

    NetBeans throws this error: "Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - Erroneous ctor sym type:
    at RandomTen.main(RandomTen.java:10)
    Java Result: 1"

    Line 10 has the following alert: 'cannot find symbol: class RandomNum location: class RandomTen"

    ReplyDelete
  18. This line:

    n[i] = new randomNum();

    Looks like you are trying to call a function called "randomNum()" but you have not actually defined one yourself, and it is not a standard part of the language. My hunch is that the book you are reading had a full code example that included a definition of this function, but you didn't see that or copy it.

    If you have encountered the concept of functions before (or as they are properly called in Java, "methods"), you should try to write this function yourself. If you haven't you should just change that line to "n[i] = (something else)". The something else will involve invoking the Math.Random library, so you will also need an import statement at the top.

    Any of that still opaque?

    ReplyDelete
  19. Okay, I see the problem. I shouldn't have been calling a method named 'randomNum' but merely assigning each object the value of the variable 'randomNum'. I mis-copied the example in my study book.

    Well, I thought I had it licked:
    class RandomTen {
    public static void main (String[] args) {
    Number [] n = new Number[9];
    for (int i = 0; i < 10; i++) {
    int randomNum = (int)(Math.random() * 100);
    n[i] = randomNum;
    }
    for (int i = 0; i < 10; i++) {
    System.out.println(n);
    }
    System.out.println("Done.");
    }
    }

    This has no warnings, but running it throws this error:
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 9
    at RandomTen.main(RandomTen.java:6)

    I guess I'm not clear on how to populate an array with values.

    ReplyDelete
  20. Yes, okay, you did fine, except for a few little problems.

    First, look at this line:
    Number [] n = new Number[9];

    Now look at this line:
    for (int i = 0; i < 10; i++) {

    See what you did there?

    In the first line, you told it: "n is an array of size 9." Then in the second line you told: "Do something with every line of n, from the first element to the tenth element."

    Except that there is no tenth element, because your array isn't that big.

    This is a common mistake where you have a defined size, but maybe you decided to change the number to something else and you forgot to change it in both places. In fact it's so common that it's got a name: This is a "magic number", an arbitrary number you picked (what if I had asked you to generate 20 numbers? Or 100?) and then you wrote out that specific number each time you used it.

    To avoid magic numbers -- which you always should! -- define an unchanging variable with a name like "SIZE" (all caps helps to remind you that this is a variable that only gets set once) and then only use SIZE to refer to the number. Never write "10" anywhere else again. That way if SIZE changes, you only have to change it in one place.

    If you fix this problem, you won't quite have the solution, because you've still got a bug in there. I won't tell you exactly what it is but here's a hint: your print statement is not printing what you think it is.

    ReplyDelete
  21. I thought creating an array of size 9 would create ten references (0 through 9), and then the for loop creates ten random numbers and fills the array.

    So changing line 4 to
    for (int i = 0; i < 9; i++) {
    solves that problem.

    So I'm not clear on what you mean by 'an unchanging variable named SIZE. I understand the principle behind it, but I'm not sure where I'm declaring and using SIZE. If you're going to change the number of numbers to be printed, then when do I declare it to be unchanging?

    And you're right, the print output is not right, just a repeat of:
    [Ljava.lang.Number;@addbf1

    Which I suppose is Java-speak for "I don't know what the heck you want me to print."

    Ooh, got it:
    class RandomTen {
    public static void main (String[] args) {
    Number [] n = new Number[9];
    for (int i = 0; i < 9; i++) {
    int randomNum = (int)(Math.random() * 100);
    n[i] = randomNum;
    }
    for (int y = 0; y < 9; y++) {
    System.out.println(n[y]);
    }
    System.out.println("Done.");
    }
    }

    And that works, except I see that only 9 numbers are printed, contrary to what I'm expecting. Changing every instance of '9' to '10' in my code results in a random ten numbers, as required. So now I'm more confused. Does the array not begin at 0?

    ReplyDelete
  22. Good job getting the code. One thing I'll note is that there's no reason to create a new variable called "y" on the second loop; using "i" a second time is perfectly acceptable.

    As to your question: You do have the right idea, but are missing some of the details.

    1. Yes, the array always starts with position 0. Nevertheless, "new Number[9]" will create an array of size 9, not 10. That just means it ends with 8. In other words, the last index will always be one less than the size of the array.
    2. Changing both numbers to 10 works as follows:
    * The array has 10 elements, ranging from index 0 to index 9.
    * You start counting at 0
    * Each time you go through the loop, you check to see if "i" is LESS THAN 10 (not less than or equal to!)
    * When "i" reaches 10, it stops, and it does not mess with the array anymore. So 9 is actually the last element processed.

    Regarding the SIZE variable, I have something like this in mind.

    class RandomTen {

    public static void main( String[] args )
    {
    Number SIZE = 10;
    Number[] n = new Number[SIZE];



    ... and so on.

    My final suggestion is just a teeny nitpick; when declaring a numeric value that is an integer, I usually define it as type "int" instead of "Number".

    ReplyDelete
  23. Okay, I understand the principle of declaring the number in question just once with SIZE. Similar to using CSS when marking HTML. That way when the boss rushes in and says, "I meant to say, '20' numbers, not '10,'" I can edit my code just once and not worry about if I got them all.

    I'm not clear on your teeny nitpick. My intention with:

    Number [] n = new Number[9];

    was not to create a variable but an array with 10 references. I used 'Number' simply because that was what I was working with, but I could have used:

    Widget [] q = new Widget[9];

    Would that not have worked?

    If I get time today I'll try working through these exercises again for the practice.

    ReplyDelete
  24. Yes, that's right, it could be any type of object, not just a Number or an int. The only difference is that an "int" is a scalar type (raw data) and doesn't have as much program overhead as creating a full blown object. You'll have to look that up to see what I mean -- but maybe I'm just holding out from the days when space meant more, but I think if you're creating an application with millions of numbers, that can still matter.

    ReplyDelete
  25. Here's more clarification on int vs. Integer/Number objects: http://illegalargumentexception.blogspot.com/2008/08/java-int-versus-integer.html

    ReplyDelete
  26. So I was able to duplicate CounttoTen and FavoriteNumber without stumbling too much. Trying again with RandomPick:

    class Randomness {
    public static void main(String[] args) {
    int SIZE = 10;
    int COUNT = 10;
    int[] n = new int[SIZE];
    for (int i = 1; i < SIZE; i++) {
    int randomNum = (int)(Math.random() * SIZE);
    n[i] = randomNum;
    }
    for (int i = 0; i < COUNT; i++) {
    System.out.println(n[i]);
    }
    System.out.println("Done.");
    }
    }

    I used SIZE and COUNT because there's really two numbers being worked here: "Pick X number(s) between 1 and Y." So COUNT is the quantity of numbers to be randomly chosen, and SIZE is the range from which to choose. So if Boss says, "Give me 5 numbers between 1 and 1000" I can make the two quick edits and I'm done.

    ReplyDelete
  27. So, fiddling with Randomness, I want to make some tweaks, and I'm not sure I'm doing it the efficient way. The problem is picking numbers between 1 and 10, but my code picks them between 0 and 9. The only solution to get from 1 and 10 is to edit line 11 as:

    System.out.println(n[i]+1);

    But I feel like I ought to be able to tweak the array to start with the right set of numbers to begin with, rather than an ad hoc add-on.

    ReplyDelete
  28. You're definitely on to something by having one permanent variable for the size of the array and one for the maximum random value, although I think it could be clearer if you stuck with "SIZE" for the former and something like "RAND_MAX" for the latter.

    However, you should test setting your constants wit different values and see if it really does what you want -- I'll betcha it doesn't right now, which means you have the good fortune to practice debugging some more. ;)

    Another pro tip is that most people put these "magic number declarations" as static properties of their class, like so:

    class Randomness {
    public static final int SIZE = 10;
    public static void main(String[] args) {
    //etc...

    That way you can not only access SIZE from inside the main method, but also from any other function you might write in the same class, and indeed, even from another class inspecting details of your Randomness class.

    In case you wondered, "static" means that the SIZE value is constant even if you have multiple instance of the Randomness class. "final" means that the value is set immediately and can never be changed.

    ReplyDelete
  29. Your instincts are good. Don't change the print statement. Change the statement where you set the values.

    int randomNum = (int)(Math.random() * SIZE)+1;

    ReplyDelete
  30. Okay, as you predicted, my program won't accept all inputs. If the setup is, say, "Pick 10 numbers between 1 and 5," then I get error output. The right number of numbers are displayed, but in between random lines is this: "Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5 at Randomness.main(Randomness.java:11)"

    Turns out if SIZE is any number higher than RAND_MAX, then the exception is thrown. Make RAND_MAX equal or greater than SIZE and everything is good.

    class Randomness {
    public static final int SIZE = 10;
    public static final int RAND_MAX = 5;
    public static void main(String[] args) {
    int[] n = new int[RAND_MAX];
    for (int i = 0; i < RAND_MAX; i++) {
    int randomNum = (int)(Math.random() * RAND_MAX +1);
    n[i] = randomNum;
    }
    for (int i = 0; i < SIZE; i++) {
    System.out.println(n[i]);
    }
    System.out.println("Done.");
    }
    }

    So in line 5 I'm creating an array of five slots, numbered 0 to 4. Lines 6-8 creates random numbers between 1 and 5 and then populate the array with them. I suppose lines 10-12 can't print more numbers than exist within the array.

    So then, I guess I need to print the random number immediately after it's created?

    class Randomness {
    public static final int SIZE = 5;
    public static final int RAND_MAX = 10;
    public static void main(String[] args) {
    int i = 0;
    int[] n = new int[RAND_MAX];
    for (int y = 0; y < SIZE; y++) {
    int randomNum = (int)(Math.random() * RAND_MAX +1);
    n[i] = randomNum;
    System.out.println(n[i]);
    }
    System.out.println("Done.");
    }
    }

    Okay, that seems to work for any values of SIZE and RAND_MAX, although this goes against what you said earlier, in that I would need two loops.

    My brain hurts, but it's a good hurt.

    ReplyDelete
  31. You did a good job identifying the conditions under which this bug occurs, but you haven't correctly identified the cause.

    Here's a tip:

    int[] n = new int[RAND_MAX];

    Are you SURE that's what you meant? (Now that you've given your constant numbers properly descriptive names, that should be easy to answer.)

    ReplyDelete
  32. Heh, you're right. I've confused SIZE (# of picks) with RAND_MAX (Range of randomness). Here's what I think is the final edit, unless Boss-man changes the parameters again.

    class Randomness {
    public static final int SIZE = 10;
    public static final int RAND_MAX = 10;
    public static void main(String[] args) {
    int i = 0;
    int[] n = new int[SIZE];
    for (int y = 0; y < SIZE; y++) {
    int randomNum = (int)(Math.random() * RAND_MAX +1);
    n[i] = randomNum;
    System.out.println(n[i]);
    }
    System.out.println("Done.");
    }
    }

    This appears to work for multiple values of SIZE and RAND_MAX.

    ReplyDelete
  33. Looks pretty good to me. Now it gets worse. >:) I'll write a new post.

    ReplyDelete