Today’s entry on Jeff Atwood’s weblog, “Coding Horror” talks about the time-honored probability puzzler known as the Monty Hall problem. If you are unfamiliar with the problem, it deals with devising the optimal strategy for playing a game that was common on the game show “Let’s Make a Deal“, starring Monty Hall, and has a solution that is commonly perceived as unintuitive.
Suppose you’re on a game show, and you’re given the choice of three doors: Behind one door is a car; behind the others, goats. You pick a door, say No. 1, and the host, who knows what’s behind the doors, opens another door, say No. 3, which has a goat. He then says to you, “Do you want to pick door No. 2?” Is it to your advantage to switch your choice? (Whitaker 1990)
This, much like airplane on a treadmill, and 0.999… == 1 are common topics of long, drawn-out arguments on the Internet. But what I want to talk about is not the problem itself (which has been done to death), or Jeff’s post. Rather, I want to discuss a variant problem humorously called the “Monty Fall Problem” proposed by Professor Jeffrey S. Rosenthal of the University of Toronto, which is included in an article titled “Monty Hall, Monty Fall, Monty Crawl”, which was linked from the Coding Horror article.
Today brings us, at long last, the latest installment in my article series on exploration of the Lottery Problem.
In the previous article, I discussed the design decisions that were made in the greedy lottery wheel generator in order to get around the computational problems inherent in even a simple greedy generator for realistically-sized lotteries. In this article, I will walk through the mechanics of generating all tickets and matches, selecting a ticket for the wheel, and updating ticket meta-data in order to allow the next selection.
You can read the whole article here. In essence, this is a walkthrough, using text, diagrams, and pseudocode, of how exactly I went about implementing a greedy algorithm based lottery wheel generator. This article addresses my solutions to problems of time complexity, much as the previous article largely addressed the solutions to problems of space complexity.
The actual generator was implemented in, of course, C++. However this article contains no actual code, only pseudocode, so as to put emphasis on the algorithms and not the details. Besides, full details of any program can only really be understood by reading code itself, not descriptions of it with snippets. I have still not yet made available the source code to this generator, although that will be forthcoming. Hopefully I will have it cleaned up enough to be suitable for public consumption before the next article in the series is posted.
I thought of a quick addendum to Monday’s article on covariant, templatized copy constructors. The end of that article said
[T]here is a large caveat that you may have noticed. Due to the the inversion of the inheritance hierarchy around the cloning mixins, we have completely lost the ability to construct derived objects using anything but the default constructor. If you need to construct a
Derived (which is really a
Cloneable<Derived_>) and pass an argument to the
Derived_ constructor, you cannot.
And then I go on to suggest using a factory pattern that is aware of the quirky type hierarchy described in that post. But in fact there is a relatively elegant way around this problem without using factories.
I’d like to address something that I’ll call the “I’ll tell you when you’re older” phenomenon in computer science education. This mostly has to do with programming, but there are also situations in theory education where this happens as well. Specifically here I’m going to discuss how it can affect the choice of a teaching language.
As is often the case with something so complex and with such a storied history, programming languages have quirks, gotchas, and many layers of operation. Therefore, it turns out to be extremely tempting when introducing an aspiring programmer to his or her first computer language to try as much as possible to avoid confusing the student by eliding material that isn’t relevant to the lesson at hand. Essentially, this means deflecting tangential questions, or anticipated questions, with the educational equivalent of the commonly experienced childhood situation where your parent promises to explain to you what some mature or risque term means “when you’re older.” In this sense, the instructor is requiring the student to wait until a later point in the course before explaining the meaning of a particular concept or construct.
In the abstract, this seems to be an advisable thing to do, but (though I certainly haven’t done formal study to back up this assertion) I suspect that it encourages the dreaded practice known as cargo-cult programming. Cargo-cult programming is the software engineering manifestation of an appeal to authority. It entails the use of patterns or code fragments when one does not really understand what they do or why they are being used, simply because one has been instructed to use them, or has seen an instructor silently do so.
It is my belief that a programmer, be he a student or a professional, should never, ever write a line of code without understanding what it does or why they wrote it. This maxim is fundamentally incompatible with the “I’ll tell you when you’re older” educational tactic because that tactic necessarily entails that the student use something without having understood it. So this tactic should be avoided whenever possible, and it should absolutely be avoided in a student’s very first lesson.
One problem that programmers new to C++ often run into when they begin to write non-trivial programs is that of slicing.
In object-oriented programming, a subclass often holds more information than its superclass. Thus, if we assign an instance of the subclass to a variable of type superclass, there is not enough space to store the extra information, and it is sliced off.
The simple way to avoid slicing is to avoid making copies. Simply pass polymorphic objects around by reference (or by pointer) rather than by value, and one never has to worry about slicing. But sometimes the need to copy is unavoidable. In particular, when the need arises to make a copy of a derived object and all you have is a base pointer, things get hairy. How do you know which derived class the pointer’s target really belongs to, in order to make a proper, deep, non-sliced copy?
Fortunately, this problem has a well-known solution pattern called the virtual copy constructor idiom. One implements a virtual
clone() method that makes a proper deep, non-sliced copy. There’s nothing wrong with this idiom, but it does entail the inelegance of having to repeat near-identical code for the
clone() method in each derived class.
C++ programmers encountering this pattern for the first time often get the bright idea that this code can be centralized through the use of templates. Doing this while maintaining all the advantages of a non-templatized virtual copy constructor turns out to be much trickier than it first appears. This post will explain the problem, and how to do something that I have not yet found described on the Internet: implement the virtual copy constructor idiom using templates without sacrificing covariance.
In Monday’s post, “Out-of-Band Error Reporting Without Exceptions”, I described a method for elegantly obtaining three out of five major advantages of C++ exceptions without actually using them. Now I’ll tell you how to go about grabbing a fourth advantage: exception type hierarchies.
C++ exceptions are hierarchical, and their hierarchy is a type hierarchy such that a more-derived exception can be treated, if desired, as one of its base exception types. For example, a
FileNotFoundException might be derived from
FileIOException. Some code may choose to handle
FileNotFoundExceptions distinctly from other
FileIOExceptions. Other code might delegate all
FileIOExceptions to a common handler. In C++ this is done with implicit typecasting in the
catch statements in a
Wouldn’t it be useful if we could do something similar with the
CheckedValue classes that were defined in Monday’s post, instead of just having an error/not-an-error indicator and a human-readable message? Well, we can. It’s easy to use but a little tricky to set up.
The newest article in my series on the Lottery Problem describes the design decisions I made when making my first attempt at an implementation of an efficient wheel generator based on the simple greedy algorithm for set cover. The design decisions described were influenced by three key insights I had when considering how one might attempt to generate a close-to-optimal wheel for a 6-from-49 lottery in a reasonable amount of time.
- Once the relationships between tickets have been completely expressed, the actual numbers that constitute the tickets are of no significance and can be discarded.
- It is only necessary to keep track of how many uncovered tickets an unselected ticket could potentially cover if selected. It is unnecessary to know exactly which tickets those are.
- Every ticket initially covers the same number of other tickets, and this number can be computed through combinatorics alone.
You can read the full article here. It primarily addresses the challenges involved in expressing the problem within a reasonable amount of memory, and gives an overview of how the computation will be approached. The computation itself will be described in detail in the next article.
Let’s say, for the sake of argument, that you’re working in C++ and you happen to be in an environment where exceptions don’t work. In my case, it was an embedded environment where the miniaturized C++ standard library didn’t support them, but in your case it may be that you can’t use them for performance reasons, or they’re against policy or something else.
As opposed to the old C-style return codes, C++ exceptions offer five major advantages:
- They are out of band (that is, a function returning an integer need not assign a special value, e.g. -1, to mean “error”).
- They can carry information about the specific error that occurred, as opposed to simply a code with a general meaning.
- They exist in a type hierarchy. A
FileNotFoundException may be derived from a
FileIOException, and a handler for the latter can handle the former.
- If you ignore them, your program blows up. This is a good thing – an undetected error is far worse than a fatal error.
- They automatically unwind the stack to find a handler.
And these are all reasons why, unless you have a good reason not to, you should prefer using exceptions. But in the case when you can’t, it turns out that point 5 is really the only advantage that you have to do without. There’s a relatively elegant method that you can use to achive the first four advantages without using exceptions at all.