Monday, July 15, 2013

PyTutorial: It's All About Class

This is the 7th in a series of posts.  If you want to go the the start, go here.

Classes are a great tool that help you organize your program better.  While functions helped you break up your program into goals and subgoals, classes help you break up your program into relationships.  Romantic, isn't it?

At its core, a class puts together pieces of information that relate to one another, as well as functions that act on that information.  BTW, for some reason, in tech-speak, functions that are part of classes are called methods.  The pieces of information that are contained in classes are called data members (and sometimes called attributes as well).

Let's take a look at some example classes:

The above four classes are independent of one another.  As you can see, each class contains some pieces of information, as well as some actions that the class can perform.  Of course, I just made these up off the top of my head, and clearly you can think of more information and actions that can belong in each class.
Assignment 7.1: Think of some more pieces of information and actions that the above classes can have.
Have you noticed an odd-ball in the crowed?  While Person, Car, and Bird contain very concrete information (first name, fuel capacity, color, etc.) the Game class contains abstract pieces of information (rules, board...).  At this point, abstract information isn't going to be very useful for us, but in the next lesson, I'll show you how abstraction can be very useful for classes.  For now, let's concentrate on the more concrete pieces of information.  Although Python doesn't let you explicitly state the types of variables, in a diagram like the one above we can specify the types of each information (just for clarity).  We can also give some more information on some of the class methods:

As you can see, "First Name" in the Person class is of type "str", while fuel efficiency is "float".  Also, Person's calculate Age method returns an 'int'.  Again, this isn't meant to be complete, just to give you a better idea of how classes organize information and actions.
Assignment 7.2: what are the relevant data types of the new information and actions that you came up with earlier?
Assignment 7.3: similar to the above examples, figure out what information and actions a "Bank Account" class could have. 
Assignment 7.4: can you think of any other classes?  What information would they have? What actions? 
One of the amazing things about classes is that they are, in essence, new data types!  While up to now, you were limited to working with integers, strings, lists, and maps, now you can create new data types (such as Person, Car, Bird, Game, etc.)  You can say that a variable, say, x, is a Person!  In tech-speak, you would say that x is an object of type Person.  But an object is just a fancy name for a variable.  Nothing more, nothing less.

Let's dive in!

Now that you have some background as to what classes are, let's take a look at how to use classes in Python:

Try taking a look at the code to figure out what it does.. And then, of course, type it in and see what happens!  :)

In this example, we've created a Person class, similar to the one in the examples above, but one that only has data-members (information).  Later on, I'll show you how to add methods (actions) to it as well.

So what's going on here? At first, we're defining a new "Person" class.  Similar to functions, when you define a class, nothing actually happens.  With functions, you have to call it in order to make it run, where as with classes, you have to instantiate it.  To instantiate is just tech-speak for creating an instance, or an actual copy with the relevant information, of that class.  BTW, in tech-speak, an instantiated container is called an instance or an object.  I'll use both terms here interchangeably. As a side-note, this is why working with classes is often called object-oriented programming.

In our example, such information includes "First_Name", "Last_Name", "Year_Of_Birth" and "Gender".  In order to tell Python that every Person object needs to have these four data members, we needed to write a special method called "__init__" (that's two underscores, "init", followed by two more underscores.)  Every class, no matter what it's called, should have such an "__init__" method.  The __init__ method tells Python how to construct a new Person object.  It is called whenever a new Person object is created.  Because it is used to construct new objects, in tech-speak, the __init__ method is also called the class's "constructor."

In our example, all the constructor does is create four variables, assigning None to each of them.   This isn't a requirement, of course.  You can have it start you with any values you wish!  To make these variables "belong" to the new object, you must prefix them with "self." (that's a "self" followed by a dot.)  But more on this later.  For now, just keep in mind that you need an __init__ method to create new objects.

You'll soon see how powerful and flexible classes actually are!  But first, I want you to notice that we actually created two instances (a.k.a. objects) of the class Person!  The first is p1, and the other is p2.  Both of them contain the same data members (i.e. First_Name, Last_Name, Year_Of_Birth, and Gender), but each contains different values for those data members.  Can you see that?

BTW, after running the above script, you can do stuff like this and see what happens:
>>> type(Person)
>>> type(p1)
>>> type(p2)
>>> print p1
>>> print p2
>>> id(p1)
>>> id(p2)

If you want to get rid of an object that you've created, just use the del keyword, which is the same way that you would get rid of any variable that you have:
>>> del p1
>>> print p1    # now you would get an error message, because p1 no longer exists!

I find it a bit strange that Person is of type "classobj", but such is life.  Don't be confused by it.  As I mentioned above, In tech-speak, classes are the general definitions, or "concepts", of how data-members and methods relate to one another, while objects are actual manifestations (with real information) of such general definitions.  In our example, the class is "Person", where as the objects are "p1" and "p2".

Of course, just like any other pieces of data, you can put objects inside lists and maps, like so:
>>> x = [3, "hi", Person()]
>>> x.append(p1)
>>> y = {"hi": p2}

etc, etc.  Pretty neat, eh? Play around a bit with classes, see what you can do.
Assignment 7.5: Similar to the Person class, implement the "Car" class.  At this point, as with the Person class, only write the code for the data members (forget the methods for now.)  Play with it a little, creating objects, and reading / writing values from their data members.
Assignment 7.6: Do the same for the "Bird" class.
Assignment 7.7: Also for the "BankAccount" class, and for the other classes that you came up with. 

Classes can contain objects

Now that you've seen how classes can group together pieces of information, you may wonder: is it possible to have one class contain objects of another class?  For instance, in the above sample classes, we have a Car class.  What if we want a Car to have an owner?  More specifically,  we may want that owner to be a an instance of the Person class!

Well, of course it's possible!  :) Not only that, it's possible for a class to contain instances of the same class!  For example, we may want a Person object to have a parent, which would also be an instance of a Person class!  Take a look:

 I've extended the above diagram to include in my classes references to other classes!  Specifically:

  • The Person class has parents and children (which is a list of Person objects), as well as a list of pet Bird instances.
  • The Car class has a Person owner, as well as a driver and passengers.
  • The Bird class can now poop on a Car class as well as on a Person class. :)
  • The Game class has a player of type Person. 
In Python, using instances of classes within a class is just like using any other variable:

Do you see how in my class definition, a person can have at most two parents, but an arbitrary number of children?  This has been a design choice, instead of having Parent1 and Parent2, I could have just as well had a list called Parents.
Assignment 7.8: change the Person class to have a list of Parents instead of Parent1 and Parent2.
Assignment 7.9: change the Car class to include Owner, Driver, and Passengers.
Assignment 7.10: modify the BankAccount class as well as any other class you came up with to include references to other objects.
Assignment 7.11: change the BankAccount class to support multiple owners (perhaps a list of such owners?)
Pretty neat, huh? :)

Python trusts you

Note that Python is very flexible in how it lets you write your code!  Nowhere in the code did I say that Parent1 must contain a Person object.  I just assigned it "None" at first, and later on, had the line:
>>> p3.Parent1 = p1

And since P1 was a Person object, all works as expected.  I could have wrote
>>> p3.Parent1 = "Dr. Doom"
or
>>> p3.Parent1 = 27

And Python would not have complained.  Python trusts you, the programmer, to know what you're doing. :)  Don't let it down. ;)
Assignment 7.12: Play around with the above example, mess around, go wild!
In fact, you can create data-members on-the-fly.  Just like when creating a new variable, all you need to do is assign a value to it.  Thus to create a new variable all you do is this:
>>> x = 37

The same goes for new data-members.  You can do something like this:
>>> p3.GrandMa = "Queen Elizabeth"

And later:
>>> print p3.GrandMa

Of course, if you never actually created the GrandMa data member, Python would give you an error message.  Try that as well.  Try something like "print p3.SomethingOrOther".

The main reason that I'm telling you all this is that with Python, you need to be very careful of typos.  For instance, if I did something like this:
>>> p1 = Person()
>>> p1.FirstName = "George"
>>> print p1.First_Name

I wouldn't get any error message, but "None" would get printed as the first name! (Instead of "George").  This is because of the typo that I have, inwhich I forgot to type in the underscore between "First" and "Name" in "First_Name".  Do you see that? Gotta watch out for stuff like this.  Python's incredible flexibiliy comes at a cost: you gotta make sure to tell it to do the correct thing!

Let's do stuff already 

OK! OK!  So far you saw the different ways that classes deal with information: that is, its data-members.  But remember that at the start of this tutorial I mentioned that classes also define the functions (a.k.a methods), that act on this information?!  Well, let's dive in!

Previously, we printed out the information about each Person object like this:
>>> print p1.First_Name, p1.Last_Name, p1.Year_Of_Birth

But say we wanted to write a "standard" way to print a Person object to the screen, we could do something like this:

There are a few key things that are going on here.  First, there's the method (basically a function) called PrintMe.  Notice that it's indented inside the Person class definition.  This makes it a part of the definition of the Person class.  Thus now PrintMe relates to the Person class.

Another key concept here is that the first parameter to any class method, in our case PrintMe, is always "self".  This is just a Python convention.  It could work just as well if instead of "self" you called it "this" (as is the case in the C++ programming language, btw) or "me", or "whatnot".  But please do the world a favor and stick with "self". :)   Did you notice that while we defined PrintMe to require 2 parameters (including self), when we called it, we only passed it 1 parameters?!  This is because "self" gets passed automatically, and is basically a reference to the actual object that PrintMe was called for. Thus in our example, when we run:
>>> p1.PrintMe("What")

"self" refers to "p1".  But, if later you do:
>>> p2.PrintMe("Not")

Then "self" refers to "p2".  Do you see that?

But why do we need this "self" parameter, anyways?  Well, using "self" lets your class method access the data members that belong to the object that its being asked to work on!  While in PrintMe we only accessed self.First_name once, when PrintMe() was called by "p1.PrintMe(...)", it accessed p1's data members.  But later, when it was called by "p2.PrintMe(...)", it accessed p2's data members.  That's what I mean by classes letting you write functions that act on its related information. 

This hidden parameter "self" is what makes class methods different from regular functions.  Every time a class method is invoked (gets called), Python passes to it as a first parameter the reference to the object that called it!

Because a class method always gets the "self" parameter as its first input parameter, even if you want to write a class method that doesn't require any input parameters, when you define it, you still need to include the "self" parameter.  For instance, if you wanted to modify PrintMe() so it doesn't need a title, you can do something like this:
Now you can call it without passing it any input parameters, like this:
>>> p1.PrintMe()

Do you see that?  Try it!
Assignment 7.13: add a class method to Person that only prints the first and last name of the object.  Call it, PrintName.
So far, we only read the values of the data members from within the class methods.  But what if we want to change them?   Well, that's really simple:

Assigning a value to self.Last_Name changes the value of the object's (the one that "self" refers to) "Last_Name" data member.  Thus we can do something like this now:
>>> p1.ChangeLastName("Kennedy")
>>> p1.PrintMe()

Nice! :)
Assignment 7.14: add a class method to Person that changes the person's first name. Call it, say... ChangeFirstName.  :)
Assignment 7.15: modify ChangeFirstName so that it prints to the screen the previous first name as well as the new one.  It should print something like: "Changing the first name from xxx to yyy".
Assignment 7.16: modify ChangeFirstName yet again to check to see whether or not the new first name is any different from the one at hand.  If it's the same, it should print a message such as "New first name is the same as the old, not changing anything".  If it's different, then it should print what you printed before:  "Changing the first name from xxx to yyy"
Just like regular functions, class methods can return values using the "return" keyword.
Assignment 7.17: write a class method, "GetYOB" for the Person class, that returns the Year_Of_Birth of the particular object.
Were you able to figure it out? It's pretty straight forward.  Well <SPOILER ALERT>, in case you didn't, it should look something like this:

Pretty simple, huh?  And to use it, all you need to do is something like this:
>>> print p1.GetYOB()

That's all there's to it, really!  Now it's your turn:
Assignment 7.18: add a class method, call it "GetAge", that returns that person's age.  (You can assume that today is 2013, or whatever this year is...)
Very good.

Why so special?

Remember our little constructor, __init__?  Let's get back to it now.

One of the first things that you want to do with a new object (of just about any class) is actually give it some information to work with.  In our Person example, we did stuff like this:
>>> p1 = Person()
>>> p1.First_Name = "Elvis"

But this is a bit problematic.  Why?  Well, working this way leaves the p1 object in an incomplete state.  At first (right after the p1 = Person() line), all its data members are "None".  What kind of person is that? Doesn't make much sense to me either.   But even after the next line, we get a Person object with partial information: it only contains a first name, and nothing else!

This is considered bad style, because it leaves your objects in confusing states.  Wouldn't it be nice if we could give all the required information right when we instantiate our object?  How about something like this:
>>> p1 = Person("Elvis", "Gato", 1987)

Well, as you have probably expected, classes let us do exactly that! :)  Check this out:

There are a few key things that are going on here.  First, there's our special method (basically a function) called __init__.  What makes__init__ so special is that unlike regular class methods, this one isn't called directly!  Nowhere in the code do you see something like this:
>>> p1.__init__("Jonathan", "Gandi", 2002)

Instead, it gets called behind the scenes whenever you create a new object, like this:
>>> p1 = Person("Jonathan", "Gandi", 2002)    # actually calls __init__!!!

As with any class method, the first parameter to __init__ is always "self".   And as usual, I hope that you noticed that while we defined __init__ to require 4 parameters (including self), when we created a new object, we only passed it 3 parameters?!  As before, this is because "self" gets passed automatically, and is basically a reference to the actual object that is being created.  Thus in our example, when we run:
>>> p1 = Person("a", "b", 2)
"self" is the new object that is being created, and is later assigned to "p1".  But if later you do:

>>> p2 = Person("c", "d", 7)
Then "self" is another object being created, and gets assigned to "p2".  Do you see that?


Note that I have the arrow pointing from self to p1.  This is because self is first created right before the constructor gets run (behind the scenes).  Also note that the constructor doesn't "return" anything, yet p1 gets the value of self (the new object.)  This is another special feature of the constructor: it never returns anything, yet a new object is returned!

For the record, the parameter names "fn", "ln", and "yob" are completely arbitrary.  In fact, you can have them have the same names as the class's data member, and it would still work just fine:

Try it!  As usual, Python can figure out what you want to do based on the "self" parameter.  It knows that when you do something like "self.First_Name" you want to access the object's "First_Name" data member, while if you just do plain "First_Name", you want to access whatever variable (in our case, it's the function's input parameter) is named "First_Name".  Do you see that?  Play around with the above example and see what happens.
Assignment 7.19: modify the above program to print the id of self, and compare it with the id of p1.  Do you see how they're the same?
Also, try running something like this now:
>>> p2 = Person()

You should get an error because now that you defined __init__, creating a Person object requires that you pass it the required parameters.   As mentioned earlier, besides the hidden "self" parameter, class methods are just like any old function, and thus can be defined with default argument values or keyword arguments.

In a nutshell, default arguments let you specify default values for function parameters.  For example, if you had a function:
>>> def myfunc (a, b, c):
...

and you wanted c to be 42 by default, you can define it like so:
>>> def myfunc (a, b, c=42):
...

now, the caller can call myfunc with or without providing a value for c:
>>> myfunc(1, 2, 3)    # c gets the value 3
>>> myfunc(1, 2)        # c gets the value 42

Keyword arguments let you be more explicit when you call a function, especially if it has many default arguments.  Instead of relying on the parameter position in that function's definition to pass in a value, you tell Python explicitly which parameter gets the value by giving its name. With myfunc as defined just above (with the default argument for c), you can do:

>>> myfunc(1, 2, c=3)    # being explicit that c gets 3
>>> myfunc(a=1, b=2, c=3)  # explicit about all of them
>>> myfunc(1, b=2, c=3)   # explicit only about b & c
>>> myfunc(c=3, a=1, b=2)   # with keyword arguments, the order doesn't matter.

Typically, though, keyword arguments are only used with parameters that have default values, as they are not always required.  Look over the Python docs again to review both default arguments as well as keyword arguments.  See how they work for regular functions, and then apply them to your Person class.
Assignment 7.20: change Person's __init__ method so that "yob" can be an optional parameter, that it, it has some default value.  HINT: its default value should be "None".  Now try calling it with no year of birth, or with one as a keyword parameter.  
That was fun, wasn't it?

A nice trick that you can do with classes is define another special method: "__str__".  Doing this lets your code become a little more elegant when printing the object to the screen.  For instance, wouldn't it be nice to be able to do something like this:
>>> print "person 1=", p1

Well, you can already do that!  Try it!  The problem is that you get something UGLY.  And of course, we want something pretty! :)  So let's define __str__() to help us:

Now you can do something like "print p1" and it should have nice results!  (Try it!)

If you want to make it print something even fancier, you can have it take advantage of the GetAge() method that you wrote as an exercise:

Do you see how here, we're using self to call another method of the same class?  That is, the line:
age = self.GetAge()
Calls GetAge() on the object that self is referring to.  Thus if we're doing "print p1", then it calls the GetAge() on p1's information.

Here's a little challenge:
Assignment 7.21: add a method to Person, call it "SetAge", that given the age (not birth year) of a person, it modifies the Year_Of_Birth.  (Again, you can assume that this is 2013 or something).

Sometimes it's better to work a little harder...

Say you wanted to change the first name of p1.  You can easily achieve this by assigning a value directly, like this:
>>> p1.First_Name = "Foozie"

That's all good and fun, so long as you know what you're doing.  But what if you wanted to have better control in your object?  Say you only wanted to allow a name that is made of all letters?  Or you wanted to make sure that the first character is capitalized?  In that case, you need to have greater control over how First_Name is assigned a value.  That is normally achieved by writing a simple "helper" method that sets the value appropriately.  In the most simple case, we would write:
class Person:
    ...
    def SetFirstName(self, new_value):
        self.First_Name = new_value
    ... 
On the one hand, this may seem like useless extra-code.  But the moment you want to place certain checks and limitations on First_Name, this function becomes quite handy.  Because now all you have to do is change this function to make sure that First_Name only gets assigned appropriate values (for instance, that the first letter is capitalized, etc.)  If you're used to directly assigning values to the data members, you may have a harder time fixing your code. 
Assignment 7.22: add a SetFirstName() method to Person that takes in a string as an input parameter (as well as self, of course), and sets the First_Name to that value.  No special check. Hint: that's pretty much what I just showed you... :)
And now comes the fun part:
Assignment 7.23: modify SetFirstName() to make sure that the first name is at least 2 characters long.  If it's not, just return from the method without changing First_Name.
Assignment 7.24: again modify SetFirstName() to make sure that the first name only contains letters.  HINT: you can go through all the characters in the string by doing something like "for c in s:"
I can keep going here, but you get the hint. :)  Accessing the object's data members using Get...() and Set...() are just good habits to get into.  This is especially true when working on a project with other people.  It gives you more control (and flexibility) over how an object is manipulated.  As in the SetFirstName() example, writing "set" and "get" functions give you an opportunity to monitor and manipulate desired changes to your object.  Of course, sometimes you don't need to do this, especially when you have full control of the data input, and you pretty much know what you're doing.  But when writing more complicated code that depends on user input, this is usually a good idea to go this way.

And now it's time for some bigger challenge!  (HURRAY!)

Below is an incomplete class definition for a car (based on the car example from earlier on).  I did you the little favor of writing the constructor for you, as well as providing you with empty methods that you should fill with actual code.  Note that sometimes I had to use the keyword pass, which basically does nothing at all, just because Python requires something to exist inside a function, even if it's just the keyword pass.  :)

But before going through the details, let's think about what is a car?

Well, a car may have a make, model, etc.  But it may also have a certain fuel capacity, fuel efficiency, and distance traveled.  Using the American system, I'll call these gallons, miles per gallon, and mileage. :)  I sincerely apologize to the rest of the world.  All these are information about a car.  But what can a car do?  Well, a car can drive, right?  And when it drives, it uses fuel as well as increases mileage!  Another useful thing that a car can do is fill up gas.  Since the information about a car as well as what it does all relate to each other, we can put them together inside a single class, like so:

Assignment 7.25: complete the Car class.  That is, implement all the required class methods. :)
Assignment 7.26: I have owner just be the name of a person.  Can you modify it so that the constructor accepts as its first parameter (besides self) a Person object?
Assignment 7.27: I've left out who the driver is and who are the passengers in the car, add them in now. 
Assignment 7.28: now implement the Bird as well as the BankAccount classes (include their methods) 

The classes that you've always known...

Remember lists, and how you can do stuff like:
>>> mylist = []
>>> mylist.append(34)

Now think about the above car example, and how you can do:
>>> mycar = Car(...)
>>> mycar.fill_up(20)

Looks rather similar, doesn't it? 
A little too similar? :)

Well, it should!  That's because in Python, everything is some sort of class!  It's just that to make things more readable for the eager programmer, Python has some special syntax (the way the code is structured) for some built-in classes!  These not only include lists and maps, but also numbers and strings!  That's why you can do things like:

>>> s = "hello"
>>> better_s = s.capitalize()
>>> print better_s

Try it!  capitalize() is just a method that belongs to the string class!  The string class has many more little methods that you can use.  In fact, you can read all about them here!  lower(), find(), and replace() are all pretty useful.  Read about them and try them out!  

Even numbers are classes!  Actually, they only have one method, and it's not very useful, but it's a class nonetheless. Since you're just dying to know, well, you can do something like this:
>>> x = 20
>>> print x.bit_length()

Which will tell you how many bits, or the smallest, most basic type of information that a computer works with (usually represented as a "0" or "1"), are required to represent the given number (in our case, 20).  I'm not sure why you would need this, but it's there!  In fact, if you want to see the bit representation of the number 20, you can do this:
>>> print bin(20)

Lovely, isn't it?

But going back to the issue at hand, everything in Python is a class, and thus has class methods and data members that are used to make it work.  Neat, huh?  

The class ecosystem

In any real piece of software, you're going to have a bunch of classes that relate to one another.  You got a sense of how this works with some of the above examples.  For instance, a Car class had a data-member, owner which was of the Person class.  Let's go over a few case-studies to see more ecosystems in action:


The above describes a very simple text editor.  One that is implemented using only three classes!

The main class here, which is also the simplest, is the "Text Editor" class.  It keeps a list of open documents, and one active document.  It also lets the user create a new document as well as close an opened one.

The "Doc" class here is a container for an actual document.  It has a file name, an author, as well as some relevant dates.  But the most important piece of information here is the Content data-member, which is basically a list of "Char" objects (more on them soon.)  The Doc class also lets the user save the document, and of course type in a character ("Add Char") as well as delete a character ("Remove Char").

At the core of this text editor is the "Char" class, which describes a single character in a single document.  It has the character itself ('a', '3', '?', ' ', or what not...), as well as information about its size, font, and whether or not its bold or italicized.  You can imagine that this isn't a very efficient text editor, as every single character that the user types requires a lot of describing information.  

I didn't go into the mechanics of actually displaying the document here.  But in theory, the Char class can have a display method that displays that particular character to the screen.  This would let us have another display method, but one that is part of the Doc class, which would go through all the characters in its Content and call their display method, kind of like this:

Let's look at a more complicated example:
Here are some of the classes that can be used in a program that manages a very simple bank. :) 

Our simple bank consists of customers and employees, nothing more!  In it, we can manage (add/remove) our customers and employees.  But our bank also has some routine tasks that it needs to take care of.  For example, every day, the bank has to pay the relevant interest in all its accounts.  Also, every month, the bank needs to charge the monthly fees to the accounts, as well as pay the monthly wages.  Once a year, the bank needs to send a "thank you" letter to its customers, as well as build a yearly financial report.

To keep the Bank class's methods simple, they can take advantage of the functionality that the other classes offer!  For instance, our Employee class deals with salary and bonus payments.  Our Customer class deals with mailing statements and thank-you letters.  Each Customer instance also has a list of Account instances that deal with interest payments, monthly fees, and deposits / withdrawals. 

Finally, we have a "Contact Information" class, which is used by both the Employee and the Customer classes.  This class is responsible for making sure that the relevant address / phone numbers are valid.

Obviously, there's a lot more work that just that's involved in running a bank.  But these classes can give you an idea of how to organize the various pieces of information and actions that relate to each other.

Notice that we have a two-way dependency here: Customer objects contains Account objects, and Account objects contain a Customer object!  In essence, I have duplicated the one piece of information on which account belongs to which customer.  The reason I did this here is that it simplifies both figuring out which accounts any given customer has, and also who owns any particular account.

This approach, however, has one huge drawback: if, for some reason, a particular account changes ownership, then both the relevant Customer object as well as the relevant Account object need to be modified.  Otherwise, you get a data-integrity problem, because you have the same piece of information in two different places contradict each other!

It's not always obvious how to solve this problem.  One clear solution is to chose between either having Account objects in Customer objects, OR having Customer objects in Account objects, but not both.  Although it sounds simple, actually writing code with such restrictions in place may end up complicating matter much more than simply duplicating the information.   Fortunately, this is a very common problem, and there are tools out there to help us deal with it (namely database and their various object wrappers, but that is for another day.)  For now, just keep in mind of the issue of duplicating information, try to avoid it, but when that's too complicate, be aware of its pitfalls and make sure that your code is correct.

I've created a sample implementation of the above banking ecosystem classes.   Check it out to see how the above can be implemented in code: banking.py (in HTML).  Can you think of ways to extend it?  Or test it further?

In our final example, let's describe the classes that involve in setting up a Skype-like client (that is, the software that is installed on its users computers, not at the company's main servers.)


Our simple Skype-like program is made up of four classes.  The main class manages the whole interaction with the end-user: it enables log-in/log-out capabilities, address book management, and starting / ending chats and calls.  

It has the information on which user is currently logged in, as well as that user's address book.  It also needs to keep track of current chat and call sessions.

Both the currently logged in user as well as her address book information is managed by the Account class.  The Account class describes an account's username as well as her display name.  It also keeps track on whether a particular account is currently online or offline.

The two remaining classes, Chat and Call, manage actual chat and call conversations.  You can see that while a Chat can be with multiple other participants, a Call can only have one person on the other end.  You can also see some of the relevant class methods that would be required.

One potentially "stinky" issue here is that the Skype class is very heavy.  It's responsible for doing a lot of things, a lot of them having nothing to do with each other!  This may be a sign that we need to break it up into a bunch of simpler classes, perhaps into something like this:
Now all that the Skype class is responsible for is log-in/log-out functionality.  It has three new manager classes that help it manage the address book, chats, and calls.  Although we have way more classes now, each class has a clearly defined role, and all the information / actions that they contain actually do relate to each other!  I like this solution much better!

Can this get any better?

Surprisingly enough, it sure as heck can!  But I'm going to leave that for the next lesson :)

For now, I want you to appreciate how classes let you define the relationships between pieces of information and the methods that act upon them.  This capability enables you to organize your code in much smarter ways.  Instead of having a bunch of variables and a bunch of functions that act on them, with no obvious hint as to which functions relate to which variables, classes let you put all the relevant parts of the code into nice groups known as classes!

To get you thinking more about things this way, try implementing the following classes:
Assignment 7.29: write a class for the rectangle shape.  What should its data members be?  Besides its constructor and __str__(), what some of its methods be?  (I can think of area and perimeter calculations, but can you think of anything else? How about a method that returns True or False depending on whether or not the rectangle is also a square?)
Assignment 7.30: write a similar class for the circle shape.
Writing a similar class for the triangle is more challenging.  If you're not good with geometry, you can skip this one.  On the other hand, if you like geometry, can you figure out what its constructor should be to guarantee that such a triangle actually exists?  What should its data members and methods be?  If you need to calculate the sinus or cosine, you should use Python's built-in math library (more on this later).  But just so you can write the class, you can do something like this:
>>> import math
and then:
>>> x = math.sin(2)
>>> y = math.cos(2.3)
etc.   You can read about the complete list of geometrical functions here.

But on to different kinds of classes...
Assignment: can you think of a computer class?  What would be its data members?  What are some simple things that you can do with a computer?
Assignment: how about a balloon?  You can fill it, but has it popped? :)
Assignment: write a class for a wallet that contains money. 
Assignment: how about an item in a grocery store?  What can be its data members?  What can be its methods?  (How about whether or not it expired?)
Assignment: how about the grocery store itself?  I can imagine it having an inventory of items.  Perhaps ItemInventory should be a class on its one?  One that contains a list of similar items, and can tell you how many non-expired ones exist, or let you throw away all expired items, etc.  Then the grocery store can have a list of ItemInventory objects, one for each kind of item in the grocery store.  At least this is how I'm imagining it.  Can you implement a grocery store like this or in some other fashion that makes sense to you?
Assignment: now have a person class, one that has a wallet class.  Add a method to the grocery store that takes in a person, and tries to make a purchase of some sorts.  This may get a bit complicated, but don't lose heart.  I'm not trying to get you to actually write some important grocery store code, just to better get a feel for how classes interact with each other.  The specific details are less important here.
Can you think of any other classes that you can implement?  Go ahead and play with classes for a while... In the next lesson, I'll show you some more crazy stuff that you can do with classes.....  :)

Did you think I forgot about Minesweeper?

I don't think so.

Although in this lesson I didn't teach you anything that would change the way the player interacts with the game, I did teach you some new ways that you can organize your code!  Can you think of how you can re-write Minesweeper in an object-oriented manner?

What classes would you have in there?
How would they relate to one another?
What data members and methods would each class have?

Think about it.

There's no single "right" way to organize your code.  Ultimately the way you organize it has to make sense to you.  Here are some ideas that I have for organizing Minesweeper into classes:
  • You can have a cell class.  What would its key data members and methods be?
  • How about grid class? 
  • Does a gameplay class make sense?  One that would be responsible for getting user input, and putting together various parts of the game?
Again, think about the way to organize the game that makes the most sense to you.   I recommend that you first write draw a high-level view of the various classes that you want to have (similar to the diagrams at the start of this less.)  Then drill down and add some details by writing an outline of the classes that you want to have (similar to what I did in the Car class example above.)   You don't have to do this, but it's just a way to get you thinking about the different classes, what they do,  and how they interact with one another.  Go ahead and try it!

Now that you have an outline of the classes that you want, go ahead and try to "fill them up" with code!  This shouldn't be sooo bad since you already have all the code in functional form.  You just need to make little adjustments to it so it'll work in the object-oriented way of thinking.  

Were you able to do it?

What do you think?  Compare your code that uses functions with the one that uses classes.  Do you like the way classes help organize your code?    :)



Wednesday, July 3, 2013

PyTutorial: Into the Realm of the Infinite

This is the 6th in a series of posts.  If you want to go the the start, go here.

Up to this point, every little program that we wrote took some time to run, but not too much.  You can probably imagine writing a program that runs for a bit longer, by, for instance, looping on a larger list, or doing a loop within a loop within a loop within a loop....  Something like that.  But can you imagine making something that runs.... forever?

Well, usually forever isn't really what you want to achieve, what is more likely is that you want to make something run indefinitely.  For instance, say you want to make a computer game, you may want it to run until the player wants to quit.  It may take a few seconds (if it's boring), or, as I experienced once playing Civ 2, well, a few days.  Yea, it was pretty sad.  But the idea is that you want the program to run until something happens, and then quit.

Of course, at its core, a computer program is just a bunch of loops and ifs.  The for loop that I showed you thus far doesn't let you do something like this.  What you want instead, is a loop that while a condition is true, keeps looping!  In python (and many other languages), this is called, wait for it.... wait for it... a while loop!  :)
Can you figure out what that does?  Type it in and try it out!  Basically, a while loops has a condition (in our case, it's "x > 5"), and while that condition is True, the body of the loop gets executed.  Of course, in Python, the body is the part that's indented after the while:
Of course, the example above isn't very interesting because it can be written as a for loop.  In fact, whenever you can use a for loop instead of a while loop, you probably should.  But I'll go into why a bit later.  For now, I just want to you get a feel for what a while loop looks like, and how it works.
Assignment 6.1: rewrite the above while loop as a for loop.  HINT: re-read the documentation on the range function to see what you can do with it... :)
One of the built-in functions that Python has to offer is called "raw_input".   Go ahead and read about it.  Basically lets you ask the user to type something in the keyboard.  To see how you can use while loops indefinitely, check this out:
Can you follow what it does?  Type it in and see what happens!  You're probably asking yourself: why is there a "True" after the while, and what is this whole "break" business? Well, I'm glad you asked me!  :)

Remember how I said that so long as the condition in our while loop is True, then the loop keeps going?  Then what better way to make sure that it's always true, and thus have the loop goes on indefinitely, than actually giving "True" as the condition?  :)  You see, this is my way of asking Python to loop for ever and ever and ever...  Since True is always True, then the loop will always run!

But in our case, we don't actually want it to run forever, we just want it to run until someone types in "moo", right?  Well, we need to have a way to break free and get out of this endless loop of ours, right?  Well, to our convenience, Python gives us the "break" command, which tells it to forget everything and just get out of the loop already.   By the way, this also works in for loops:
See that?  What do you expect to see happen?  Type it in and try it out!
Assignment 6.2: write a for loop that prints out the numbers from 10 to 100, but let the user stop any time in the middle by typing in "foo".
 Got it?  Great!  But before we continue on with while loops, I want to introduce you to break's twin sister, continue:
Type in the above code and see what happens!  Can you figure it out?

Basically, continue tells Python to drop what it was doing in the loop's body, and keep going with the next item in the loop!  Thus in the above example, because "print a" happens after the continue in cases in which a > 3 and a < 7, it doesn't get run for them.  Whereas break tells Python to stop whatever it was doing in the loop body and exit the loop, continue tells Python to stop whatever it was doing in the loop body and keep going with the next item in the loop.  See the difference?

Continue also works in while loops.  Of course, there's no "next item" in while loops, so what it does is stop processing the body of the loop, and then go back to the start.  There, as always, it first checks if the condition is True, and if so, it continues...

Can you figure what the above does?  Can you?  Type it in!

It basically does what the previous code did, but with a while loop instead of a for loop.  Do you see how it works?  Great!
Assignment 6.3: write a while loop that keeps printing compliments (such as "you look good today") to the screen.  Of course, give the user an option to stop this whenever they had enough... :)
Do you remember that I previously mentioned that whenever you can use a for loop instead of a while loop, you probably should?  The reason for this is that the additional flexibility that while loops give you to write indefinite loops come with a hefty price:  a little subtle coding mistake can give you a loop that "gets stuck" forever.  Check out what happens when all you do is move the "a = a + 1" part of the loop body to the end of the body, like this:
Can you see the problem here?  It may not be very obvious.  Of course that's part of the problem, that it's not very obvious.  :)  Type it in and run it!  Just be prepared to go to the menu in the Python Shell, choose "Shell", and then "Restart Shell".  :)  Go ahead and do this.  Now! :)

Great!  Did you notice something odd...?  Perhaps you noticed that the program just sort of got "suck".  It didn't finish like before...  That is, you didn't get the ">>>" prompt in the end, as usual.  Why was this?  Well, welcome to the realm of the infinite!  :) You just got yourself what we call in the industry, an "endless loop".

To get a better feel for what's actually going on, move the "print a" from towards the end of the body to the very start, like so:
What do you see now?  Do you see a whole bunch of "4" getting printed to the screen?  Can you figure out why this happens?

When a equals 4 (or more specifically, when a > 3 and a < 7), the loop reaches a "continue".  Remember how continue tells Python to stop everything in the loop and go back to the beginning?  Well, it now skips over the "a = a + 1" part, and thus a remains 4 forever!   Nice, huh?

You're probably thinking to yourself: this really sucks!  And you would be right.  That really does suck.  In fact, a lonely programmer can spend endless hours wasting trying to figure out why some code like this gets stuck.  In a simple program like we have above, this may not be too bad, but you can imagine a more complicated piece of code, one with a bunch of loops and functions, and what not, and all of a sudden, the code gets stuck.  Good luck trying to figure it out.  Especially if the code is already running on some customer's computer.  Yea, that will be fun for you, I promise.  :)

Thus remember our old friend, unit testing, which should help find most of these problems.  Still, some problems may still slip though the cracks.  This is the motivation for my recommendation to use a for loop whenever possible.  Because a for loop has a set number of items that it can go through, code that uses it is usually not only more elegant, but also safer.  Safer from endless loops like the one that you saw above.

So why ever use while loops anyways, you ask?  Well, as I started the tutorial, many times you do want things to run indefinitely!  In those situations, a while loop is what you need to use.  Just make sure to test it and make sure that it always has a way to actually stop running!

A little practice

As you saw, while loops are useful when you want to make a loop run indefinitely, waiting on a user to make some sort of decision to end the loop.  But indefinitely doesn't necessarily mean waiting on a human to make some decision.  Sometimes, the number of times that the loop needs to run isn't very obvious for the programmer.  

For example, take the series of square numbers.  That is: 0*0, 1*1, 2*2, 3*3, 4*4... 
0, 1, 4, 9, 16, 25, 36, 49, 64, ...
If I asked you to write a loop that prints the first n items in this series, you can definitely use a for loop.
Assignment 6.4: write a function, call it square_n, which takes in an input parameter n, and returns the nth item in the square series.  square_n(5) returns 25, square_n(7) returns 49, etc.  You don't need a loop to solve this.  Try solving it without a loop, but then try again with a loop, just for the exercise. 
But what if instead of wanting the nth items in the series, I want a function that returns the first item in the series that is greater than a particular value?  For instance, if that value is 10, then I would want the number 16.  Or if the value is 20, I would want 25.  For 5 it would be 9, for 49 it would be 64, etc.

You can probably see that it's not clear when a particular value is reached in the series (assuming you don't have the ability to find the square root of a number, of course).  That is, you would need to loop through the items in the series for as long as it takes until you get the number that you want!  Since you don't know how long its going to take ahead of time, you need to use a while loop here!
Assignment 6.5: write a function, call it square_greater_than, that given a value n, returns the first item in the square series that is greater than n.  Remember to unit test this is various values, just to make sure it works well!! 
That wasn't easy, but it should demonstrate the value in using a while loop.  Did you get an endless loop in the process?  It happens... While loops are harder to work with than for loops, but sometimes, it's the only option you have.  Fun!

As another example, take the Fibonacci Series.  If you never hear of it, it's basically the list of numbers in which each item in the list is the sum of the two previous numbers.  Read on it on Wikipedia, it's actually kidn of neat.  By definition, it starts with 0 and 1.  Thus, the list is:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
Can you see how each item in the list is the sum of the two previous ones (for instance, 13 = 5 + 8, etc.)  Great!
Assignment 6.6: write a function, call it fib_n, which takes in an input parameter n, and returns the nth item in the Fibonacci Series. 
Of course you know what's coming up, don't you? :)
Assignment 6.7: write a function, call it fib_greater_than, that given a value n, returns the first item in the Fibonacci Series that is greater than n.
Very good.

Calling on the infinite

There's another way to do things endlessly, that that's using functions!  How is that possible? You ask.  Well, all you really need to do is have a function call itself!  In tech-speak, this is called recursion.  Check this out:

What do you think will happen?  Type it in and try it out! :)
Can you figure out how this worked?  Recursion consists of four main parts.  The first is the starting position, or initial condition in tech-speak.  In our case, it's the number 10 that we passed to x in x(10).  That's what we have to start out with.  There is also the end-condition, which tells us how this recursion is going to end.  In our case, it's the check that a < 0.  You can imagine that without a good end-condition, well, similar to a while loop, you end up with an endless recursion.  There's also the step, in which we go from one situation to another.  In our case, it's when x calls itself, but with a value one less than what it got, that is, when it called x(a-1).  Of course, there's also the part of the function that actually does something useful.  In our case, it's just printing the value in a.  Do you see that?

You can see that we basically achieved a loop, without using a loop!  Nice, huh?

Of course, using recursion to print the numbers 10...0 is a bit of overkill.  I just showed it to you so you can see how it works.  But in the above example, you're much better off using a for loop, and even a while loop is better than recursion in this case.  So when should recursion actually be used?  Well, as usual, this is a matter of elegance.  Some problems simply become clearer when recursion is used!  Usually, this is the case when you can break up a larger problem into a bunch of smaller, yet similar problems. 

Take our good old Fibonacci Series.  Do you see how each item in the series is the sum of the two previous items?  In fact, when you wrote fib_n, you did just that.  You had some for (or perhaps even a while) loop that saved the previous two values and that way calculated the next.  But can you think of a way to use recursion to achieve the same end?  That is, to write a function, fib_r, which takes in an input parameter n, and returns the nth items in the Fibonacci Series. but does so without a loop at all!  Can you achieve this by having fib_r call itself?

Think about it.

How can one do this?  Well, to get the nth value in the Fibonacci Series, what do we need to know?  We need to know the value of the n-1 item in the series, as well as the value of the n-2 item in the series.  If we had these two values, all we need to do is add them up, and that way we end up with the value of the nth item!  Easy!  Of course, we need to be careful not to end up with endless recursion!  Let's think about this in terms of what we had above:
  • Initial Condition: the request for the nth item in the Fibonacci Series
  • End Conditions:
    • If ask for an n < 1, return None
    • If ask for n == 1, return 1
    • If ask for n == 2, return 1
  • Step:
    • Call itself to get the n-1 item
    • Call itself to get the n-2 item
  • Action:
    • Add together the sum of the n-1 and n-2 items, and return the sum.
Now, let's put all this together into a function:
Try it out!
What's nice about recursion here is that it makes the logic of how to calculate the Fibonacci series pretty straight forward.  One disadvantage of using recursion here instead of a for loop actually has to do with performance.  Can you figure out why?  Add a print statement at the start of fib_r that prints to the screen the value of n that it is being called with, and the call the function with fib_r(7) or something.  What do you see?  Try to figure out what happens. 

Now it's your turn.  Use recursion to calculate the factorial of a number, n (that is, n! in mathematics.)  For example, 5! is 5 * 4 * 3 * 2 * 1 = 120.  
Assignment 6.8: write a function, factorial_r, which, using recursion, returns the factorial of a given number, n.
That wasn't too bad, was it?

BTW, recursion is only infinite in theory.  In practice, it's very limited.  In fact, try to write a recursive function that doesn't have an end condition.  Go ahead and try it!  If that happens, you should get some error such ash:
RuntimeError: maximum recursion depth exceeded while calling a Python object
The reason for this error is that every time you call a function, it takes up memory.  When the function returns, the memory is freed up!  Thus recursion can't really go on forever (as opposed to a while loop) because eventually memory runs out.   Python tries to give you some information about what happened by limiting the depth of recursion that it allows, and it can thus give you a clear error.  It is possible to change this limit, but then you just end up running out of memory, and get an error message like this one:
MemoryError: stack overflow
The stack is the part of the memory that is set aside specifically so functions will be able to call each other.  The heap, on the other hand, is the part of the memory that is set aside for you to store information such as lists and maps and numbers and strings.  Usually, the heap is much-much-much larger than the stack.  But you can reach that limitation as well, can you figure out how?   You can reach this limitation by writing an endless while loop that all it does is append some value to a list.  This way the list keeps growing and growing, eventually, it will take up all the available memory, and you should get some sort of memory error.  Try it!  When I did this, I got a less informative error message:
MemoryError
That was that.  :)

Usually, you won't ever reach these limitation, but it's just something to keep in mind.  If you do need to work with a lot of information, you may need to use files, which can be quite huge.  There are many ways to do this, and that's for another time, but the main disadvantage of using files is that they're slow compared to using memory.  On the other hand, files are your way of keeping information from one time the program runs to the next (imagine you want to keep a list of "high scores" to a game that you write, etc.)  I'll go into this later, but I just want you to know that you may reach these memory limitations, but that there are ways to work around those limitations as well.

I want to end this discussion of recursion with this little funny funny:

Nice Python...

Previously, I sent you to the Python website to read a little about Python's built-in functions.  Now, I want you to see some more things you can do with the language.  Again, you don't need to memorize everything, but it's good to be aware of a few things:
  • Some more details on different ways you can define functions.  The topics that I find most relevant are:
    • Default argument values: if no input parameter value is given, can use some predefined value instead.
    • Keyword arguments: instead of giving input parameters as a comma-separated list, in which the order is very critical, can pass the input parameters as name-value pairs, similar to maps.
    • Arbitrary argument lists: not super useful, but lets you define a function without specifying  exactly how many parameters it needs.
  • A way to "slice" strings and lists.  Read what they have to say, but basically slicing is a way to get parts of a string or list.  Say you have the string, s, you already know how to get a particular character in it, say the 3rd, by calling s[2].  If you want the part of the string that is between the 3rd and 5th index, you call call the string like this: s[2:5].  Play with it a little and see how it works.  The same is true for lists.  The online tutorials give some examples as well.
    • Some more things you can do with lists, and some new data structures such as sets.  A lot of what's in there is interesting, but some things to keep in mind as you go through it:
      • Some of the things here will be completely new to you.  That's ok, I'll go over some of these stuff later, but you can just play around with their examples and see what happens.
      • In one example, they use the '%' operator.  All it does is give you the remainder of a division.  For example, '11 % 3' equals 2.  That's because 11 / 3 = 3 with remainder 2.  '10 % 5' equals 0, because 10 / 5 equals 2 with no remainder.
      • In another example they use the '**' operator.  That gives you some number to the power of another.  For example '3 ** 2' is 3 squared, which equals 9.  '4 ** 3' is 4 to the power of 3, which equals 64.
    Play around with what looks interesting to you, just to get a feel of what you can do and how it works.

    What this lesson needs is more Minesweeper!

    In the previous lesson, I had you write a lot of the "core" functions that any Minesweeper game would need.  Now I want you to complete them, and actually end up with your very first Minesweeper game.  One that you will remember and treasure forever.  It's not going to look pretty (I'll show you how to do that later), but it'll actually work.  It will be a game that anyone can play!  Sure, it'll be mostly just you playing it, but it's going to be fun.  Trust me.  Well, let's get started!

    A little user-interface

    Anyone playing your game (that is, you), is going to want to interact with it. (duh!)  Part of that interaction is actually seeing the Minesweeper grid.  Sure, you wrote PrintGrid() in the previous lesson, but that was just for debugging purposes.  You want to be able to print the grid to the screen in a way that makes sense to the gameplay.  This means:
    • Make it look as simple and elegant as possible, that is, make it look as much like a player would expect the game to look!
    • A cell that no wasn't clicked on can be represented by, say, a question mark: '?'
    • A cell that a player marked as flagged should be represented by, say, an asterisk: '*'
    • A cell that has been clicked on, but has a mine, should be displayed as an 'X'
    • A cell that has been clicked on, but has no mine, should display the number of neighbors that it has that have a mine.  Thus, this can be anywhere from the number 0 to 8.  Of course, this isn't exactly how the real game is played, but we'll get there. :)
    • Anything else that you can think of
    You can write the above function, call it, say, DisplayPlayerGrid(grid).  Of course, part of the requirement here is to be able to display the numbers of neighbors that contain a mine.  There are a few ways to achieve this:

    One way is to write a function, say, CalculateNeighborsWithMines(), that takes in a grid, as well as the x and y coordinates of the cell you're interested in, and returns (after checking) the number of neighbors that have a mine. 

    Another is to figure out in advanced how many neighbors have a mine.  This is possible because in Minesweeper, neither the grid doesn't change nor do the mines move around.  If you want to go with this approach, I recommend changing the information in each cell to include the number of neighbors.  Then, after calling AddMinesToGrid(), call another function UpdateNeighborsWithMines(), that takes in a grid, and updates each cell with the correct number of neighbors (it can use the function CalculateNeighborsWithMines() if you want.)

    There are more ways to do this, and you can also make little changes to the above two approaches.  Basically, however way you want to approach this problem is fine, just make sure that it makes sense to you.

    BTW, this is great function (they all are, really), for unit-testing.  It's a complicated task, but inputs and expected outputs are pretty straight forward.  So be a pro and remember to unit-test this code!! :)

    Letting the player actually do stuff...

    Of course, just displaying the grid isn't much of a game.  What is missing is a way for the player to affect the game.  In other words, we need some user input.  You already know about Python's raw_input() function, and this is a good place to use it! No matter what you type in, raw_input() will always return a string.  If you want to turn it into a number, you can use the int() function:
    >>> x = "3"
    >>> num = int(x)
    >>> type(num)
    >>> print num

    Of course, if x isn't a string that can be converted into a number (for instance, if x is "hi"), then you'll get an error message.  I'll tell you all about how to handle such errors later, for now, just be nice and type in the kind of values that are expected.  If you're really curious, and simply can't wait for the lesson after the next, you can read all about handling such exceptions (tech-speak for errors) right here.

    Let's get back to the game!

    Can you think of how the game should be played?  What sort of interaction will the player have with the game?  I can imagine something like this:

    • Print some welcome message and explain the rules
    • Ask the user to select a grid size (or just offer one size)
    • Ask the user to select the difficulty, that is, the probability that each cell will contain a mine.  (or just offer one such probability)
    • In a loop:
      • Show the user the player-friendly grid
      • Ask the user for a desired action.  Her available choices will be:
        • Flag/Unflag a position
        • Check a position
        • Quit the game
      • Act on the player's desired action:
        • If Flag/Unflag was selected:
          • Ask for the x,y coordinates to update
          • Update the coordinate
        • If check a position was selected:
          • Ask for the x,y, coordinates
          • Check that coordinate
          • Check to see if the game was lost or won.
            • If it was lost:
              • Write something sad and quit the program
            • If it was on:
              • Write something happy and quit the program
        • If quit the game was selected, well, um... quit the game! :)
    The above is called pseudo-code.  Pseudo-code is tech-speak for writing a program in a logical, yet very high-level manner.  There's no rule specifying what is "high enough", it's just supposed to be a tool to help you think and design larger programs.  Use it if it helps, don't if it doesn't. :)  

    So the above pseudo-code is just what I think should happen in a normal game of Minesweeper.  If you think differently, that's fine!  Just change the pseudo-code and write whatever you what!  Heck, it's your game!! :)

    Go ahead and write the entire game!!  Take the time you need, but it should be fun!  

    Come back when you're done! :)

    Were you able to do it?  Congratulations!  I knew you would. ;)  Now go out and celebrate!

    Are you ready for a little challenge?
    Remember your good old AddMinesToGrid() function?  Sure, it works, but it doesn't work exactly how real Minesweeper games do now, does it? Do you see where I'm going with this?  I had you write it so that as an input parameter, you can specify the probability that each cell will have a mine.  That's great, but not exactly what we want.  

    In a real Minesweeper game, you can specify the exact number of mines that you want to have in the grid!!  Right?? Right???  Well, in order to write this correctly, you'll probably want to use a while loop.  And since I didn't teach you about while loops in the previous lesson, I didn't have you write the correct version of AddMinesToGrid() then.  But now is a good time to have you do that! :)  So go ahead, modify AddMinesToGrid() to take both a grid as input, and also a number that specifies the exact number of mines to place inside it!  Of course, they need to be placed randomly in the grid, so this is going to be a little challenging.  But I believe in you.  :)

    Whew!  That was tough, wasn't it?  But it was well worth it.

    And there you have it!  You have written your first real Minesweeper game!!  That's just plain awesome.  In some future lesson I'll teach you how to also make it look good.  But making the game, not matter what it looks like, is no small achievement!

    Go get some rest. :)

    In the next lesson, I'll introduce you to classes.  Classes are just ways to organize your program better.  In a way, they let you group together information and functions that relate to one another....  Exciting!