Category Archives: technology

Counting the bits at YouTube

Jonah is nearly done with fifth grade. In the fall he begins middle school. For years I’ve known that if I’m ever going to visit his classroom for a “what my dad does at work” presentation, it would have to be before middle school, which is when the coolness of “what my dad does at work” presentations falls off a cliff.

I made it just under the wire. For a long time all I had were good intentions and a half-started slide deck, work on which always took a backseat to this and that. Finally, a few weeks ago I gave his classroom the presentation below.

It was a hit. YouTube has a lot of cachet with 10-year-olds. It helped that I made some of the presentation interactive; there was a novelty factor to having the class work out some simple but enormous numbers. They stayed engaged for the full forty-five minutes, volunteering answers, laughing in the right places, and asking smart questions.

At the end I distributed light-up YouTube yo-yos to everyone, which was an even bigger hit. Hopefully it cemented Jonah’s reputation as the coolest kid to know. But his classmates were into the talk even before they knew there was swag coming.

I invite you to reuse or repurpose the slides below. I plan to give the talk again in two years when Archer is in fifth grade, so any constructive feedback that I can incorporate before then would be welcome.

How (and why) to program, part 2

This entry is part 2 of 2 in the series How (and why) to program

It’s National Computer Science Education Week! That must mean it’s time for part 2 of my How (And Why) To Program series. Today I will discuss a tricky but powerful concept in computer science: recursion.

Briefly, recursion means accomplishing a task by performing it in terms of smaller versions of the same task. For example, each morning I execute my “drive to work” routine, which is really my “drive from point A to point B” routine, where point A is home and point B is work. To do that, I first do “drive from point A to point B” where point A is home and point B is the Golden Gate Bridge (which is about halfway to work for me), followed by “drive from point A to point B” where point A is now the Golden Gate Bridge and point B is work. Each of those steps, of course, can be decomposed into smaller “drive from point A to point B” tasks.

One classic example for illustrating recursion in computer code is the Fibonacci sequence — the mathematical sequence in which each number is the sum of the two before it. You might already see the weakness in that definition: what can the first and second numbers in the sequence be, if they don’t have two numbers before them that can be added together? This is a key feature of recursive functions: at some point they reduce the problem into parts so small that they reach the “base case,” where the recursive rule breaks down. It happens that the base case of the Fibonacci sequence says the first two numbers are 0 and 1. From there, the recursive rule takes over to give the numbers that follow: 1, 2, 3, 5, 8, 13, 21, 34, and so on.

Let’s look at a “function,” which is the computer programming equivalent of a recipe: you give it some inputs, and it gives you an output, the result of processing the inputs in specific ways. Our function is called fibonacci and it takes one input, or “argument”: a number, which we’ll call n. The result of fibonacci(n) will be the nth number in the Fibonacci sequence, where the first two numbers — fibonacci(0) and fibonacci(1) (recall that in programming, lists and sequences of things are almost always numbered beginning at zero) — are 0 and 1.

As before, code samples are presented in the Python programming language, though the same concepts we’re discussing apply to most other programming languages too.

def fibonacci(n):
  if n == 0 or n == 1:
    return n
  else:
    return fibonacci(n-1) + fibonacci(n-2)

We start with “def fibonacci(n),” which simply means “define a function named fibonacci taking one argument called n.” The body of the function follows. First it checks for the base case: does the caller (whoever is invoking this function) want one of the first two Fibonacci numbers? If so, the function simply “returns” (or hands back to the caller) the value of n, since by coincidence the value of fibonacci(n) is n when n is 0 or 1.

If it’s not the base case, the function returns a different value: the sum of invoking fibonacci first on n-1 and then on n-2. Those recursive calls give the two prior Fibonacci numbers. For instance, if we invoke fibonacci(9), then n is 9 and fibonacci(n-1) is fibonacci(8), which is 21; and fibonacci(n-2) is fibonacci(7), which is 13. Adding those together gives 34, which is the correct result for fibonacci(9).

Enough about the Fibonacci sequence. It’s a contrived example and, though it explains recursion pretty well, it doesn’t demonstrate the real-world applicability of the technique. (It also happens that, for reasons I won’t go into here, recursion is a terribly inefficient way to compute Fibonacci numbers compared to other possibilities like iteration.)

A few days ago, my Mensa Puzzle-a-Day Calendar presented this riddle:

The letters of a certain three-letter word can be added in sequential order (though not necessarily with all three letters together in the same place) to each of the letter strings below to form common, uncapitalized English words. You don’t need to rearrange any of the letters below. Simply add the three needed letters in sequential order. What is the three-letter word, and what are the nine new words formed?

1. alp 2. wl 3. marit 4. ealus 5. urneman 6. cke 7. disintedl 8. traectr 9. epard

(To illustrate the puzzle: the letters of what three-letter word can be inserted in both “hoyde” and “ckear” to produce common English words? The answer is “new,” to produce “honeydew” and “neckwear.”)

Staring at the puzzle for a while, I was unable to solve it. So I sat down and wrote a program to solve it for me. How’s that for real-world applicability?

Once again I relied on the file /usr/share/dict/words (or sometimes /usr/dict/words, or /usr/lib/dict/words) that is a standard feature of some operating systems; it’s simply a list of many common English words (and many uncommon ones, plus some frankly questionable ones), one per line. Reading that file, I produced two sets of words: one set of all the words, and one set of all three-letter words. Here’s how that looks:

three_letter_words = set()
all_words = set()
wordlist = open('/usr/share/dict/words')
for word in wordlist:
  word = word[:-1]
  all_words.add(word)
  if len(word) == 3:
    three_letter_words.add(word)
wordlist.close()

(Very similar code is explained in detail in part 1 of this series.)

With those two word sets in hand, and the nine letter-strings from the puzzle, this was my strategy: try all possible ways of inserting the letters of all the three-letter words in each of the letter-strings. For any three-letter word, if none of its combinations with a given letter-string produces a valid word, remove the three-letter word from further consideration. In other words, beginning with all possible three-letter words, we whittle them away as they become disqualified. In the end, the only three-letter words left should be ones that combine, one way or another, with all of the nine letter-strings to produce valid words.

So, for example, the three-letter words “see” and “era” both can be added to the letter-string “alp” to produce valid words (“asleep” and “earlap”). But the three-letter word “new” can’t be, so after running through all the three-letter words on the letter-string “alp,” “see” and “era” will still be in the set three_letter_words, but “new” won’t be.

Here’s how that strategy looks:

for string in ("alp", "wl", "marit", "ealus",
               "urneman", "cke", "disintedl",
               "traectr", "epard"):

This starts a loop that will run nine times, once for each letter-string, giving each letter-string the name “string” on its turn through the body of the loop.

  three_letter_words_to_discard = list()

This creates an empty list called three_letter_words_to_discard. It’s empty now but as we progress we will fill it with words to remove from the three_letter_words set.

(If you’re wondering why I sometimes use lists for collections of things, and sometimes use sets, gold star! The answer is that they are two different kinds of data structure, each one good at some things and bad at others. A set is very fast at telling you whether a certain item is in it or not; a list is slow at that. On the other hand, a list keeps things in the same order in which you added them; a set doesn’t do that at all.)

  for three_letter_word in three_letter_words:

This starts a nested loop. It’ll run through the complete list of three_letter_words each of the nine times that the outer loop runs.

    combinations = combine(three_letter_word, string)

Here we presume there’s a function called combine that takes the current three-letter word and the current letter string, and produces the complete list of ways that the letters of three_letter_word can be interspersed with the letters of string. For example, combine(“abc”, “def”) should produce the list ["abcdef", "abdcef", "abdecf", "abdefc", "adbcef", "adbecf", "adbefc", "adebcf", "adebfc", "adefbc", "dabcef", "dabecf", "dabefc", "daebcf", "daebfc", "daefbc", "deabcf", "deabfc", "deafbc", "defabc"]. That’s where recursion is going to come into play. We’ll get to writing the combine function in a moment.

    good_combinations = list()
    for combination in combinations:
      if combination in all_words:
        good_combinations.append(combination)

With the list of combinations in hand, we now look through them to see which of them are valid words, if any. We set good_combinations to be a new empty list where we’ll accumulate the valid words we find. We loop through the combinations, testing each one to see if it’s a member of the set all_words. If one is, we add it to the list good_combinations.

    if good_combinations:
      print three_letter_word, "+", string, "=", good_combinations
    else:
      three_letter_words_to_discard.append(three_letter_word)

After the “for combination in combinations” loop, we check to see whether good_combinations has anything in it. (“If good_combinations” is true if the list has something in it, and false otherwise.) If it does, we print out the current three-letter word, the current letter-string, and the list of valid words they make. If it doesn’t, then three_letter_word goes into our list of three-letter words to discard.

  for word in three_letter_words_to_discard:
    three_letter_words.remove(word)

After the “for three_letter_word in three_letter_words” loop, this small loop does the discarding of disqualified three-letter words.

Why not simply discard those words from three_letter_words in the preceding loop, as we run across them? Why save them up to remove them later? The answer is that when you’re looping through the contents of a data structure, it’s a bad idea to add to or remove from the data structure. The loop can get confused and lose its place in the structure. It may end up running twice with the same list member, or skip a member entirely. It’s safe to make changes to the membership of the data structure only after the loop finishes.

Finally, after the outermost loop has finished, it’s time to see which three-letter words remain in our set:

print three_letter_words

And that’s all! All except the tricky part: the combine function. Here is how it starts:

def combine(string1, string2):

It takes two strings. We’ll give them generic names, string1 and string2, so as not to assume that either one is a three-letter word. As you’ll see, often neither one is.

Now, how to approach writing a recursive function? It’s usually a safe bet to start with the base case, the conditions under which combine isn’t recursive. The recursive step will involve passing shorter and shorter strings to combine, so the base case is when one or both of the strings is empty. Obviously if either string is empty, the result should be the other string — or more precisely, the list containing the other string as its one member (since we’ve already stipulated that the result of combine is a list of strings). In other words, combine(“”, “def”) should produce the list ["def"] — which after all is the result of interspersing the letters of “” among the letters of “def” — and combine(“abc”, “”) should produce ["abc"].

So here’s the body of combine so far. It’s just the base case:

  if len(string1) == 0:
    return [string2]
  elif len(string2) == 0:
    return [string1]

(Recall that “elif” is Python’s abbreviation for “else if.”)

Now for the case where string1 and string2 are both non-empty; the recursive case. The key to writing the recursive step of a function like this is figuring out (a) how to make the problem the same but smaller, and then (b) what to do with the result of computing the smaller solution.

One way to make the problem smaller is to lop off the first letter of string1. So if combine were originally invoked with the strings “abc” and “def,” the recursive call would invoke it with “bc” and “def.” Presuming combine works correctly — which is the counterintuitive assumption you must always make about the recursive step in a function like this — we’ll get back the list ["bcdef", "bdcef", "bdecf", "bdefc", "dbcef", "dbecf", "dbefc", "debcf", "debfc", "defbc"]. None of those belongs in the result list of combine(“abc”, “def”); but if we now restore to the beginning of each of those strings the same letter we lopped off, we get ["abcdef", "abdcef", "abdecf", "abdefc", "adbcef", "adbecf", "adbefc", "adebcf", "adebfc", "adefbc"]. This is halfway to the complete answer: it’s all the strings in the result list that begin with the first letter of string1. We only need to add all the strings in the result list that begin with the first letter of string2, and we’re done. We do this by treating string2 the same way we just treated string1: we lop off its first letter in another recursive call to combine, then paste it back on to each string in the result. Continuing the example, this means calling combine(“abc”, “ef”), which produces ["abcef", "abecf", "abefc", "aebcf", "aebfc", "aefbc", "eabcf", "eabfc", "eafbc", "efabc"]. Sticking the “d” back onto the beginning of each of those strings gives ["dabcef", "dabecf", "dabefc", "daebcf", "daebfc", "daefbc", "deabcf", "deabfc", "deafbc", "defabc"], and adding this list to the list from the first recursive call gives the complete solution.

In Python, the first letter of string is denoted string[0]. The rest of string, without its first letter, is denoted string[1:]. So here’s the complete version of combine, with the (double) recursive step added in.

def combine(string1, string2):
  if len(string1) == 0:
    return [string2]
  elif len(string2) == 0:
    return [string1]
  else:
    recursive_result1 = combine(string1[1:], string2)
    recursive_result2 = combine(string1, string2[1:])
    result = []
    for string in recursive_result1:
      result.append(string1[0] + string)
    for string in recursive_result2:
      result.append(string2[0] + string)
    return result

This is the crazy magic of recursion: at each step, you simply assume the next-smaller step is going to work and give you the result you need. All you have to get right is the base case and the way to process the recursive result, and — well, look:

hol + alp = ['alphol']
has + alp = ['alphas']
sae + alp = ['salpae']
her + alp = ['halper']
see + alp = ['asleep']
eta + alp = ['aletap']
era + alp = ['earlap']
soe + alp = ['aslope']
yin + alp = ['alypin']
pus + alp = ['palpus']
een + alp = ['alpeen']
kas + alp = ['kalpas']
ecu + alp = ['alecup']
ist + alp = ['alpist']
doh + alp = ['adolph']
pal + alp = ['palpal']
cul + alp = ['calpul']
ped + alp = ['palped']
Moe + alp = ['Malope']
clo + alp = ['callop', 'callop']
gos + alp = ['galops']
tid + alp = ['talpid']
yum + alp = ['alypum']
pon + alp = ['palpon']
hin + alp = ['alphin']
joy + alp = ['jalopy']
hol + wl = ['wholl', 'wholl']
sae + wl = ['swale']
soe + wl = ['sowel', 'sowle']
joy + wl = ['jowly']
joy + marit = ['majority']
joy + ealus = ['jealousy']
joy + urneman = ['journeyman']
joy + cke = ['jockey']
joy + disintedl = ['disjointedly']
joy + traectr = ['trajectory']
joy + epard = ['jeopardy']
set(['joy'])

Predicting the present

One day long ago, when the IBM PC was still new, my friend Mike asked me to imagine my ideal computer. I described something very like the IBM PC, but with more memory and a bigger hard drive — 50 megabytes, say, instead of 10 or 20. I couldn’t imagine any use for much more than that. (Today of course you can’t even buy a thumb drive that tiny.) I grudgingly allowed that a bitmap display might be more useful than the 80-column-by-24-line character terminal that PC’s had, but that was all I would consider adopting from the then-brand-new Apple Macintosh, which I dismissed as a silly toy unworthy of Real Programmers.

“Why?” I asked Mike. “What’s your ideal computer?”

Mike described something no bigger than an 8.5×11 sheet of paper and no more than an inch or so thick, whose entire surface was a full-color display. It could be carried in the hand or slipped into a backpack. “What about the CPU, where would that go?” I asked. I wasn’t getting it. Mike patiently explained that the whole system — CPU, RAM, video driver, power supply — was inside that little slab. I scoffed. Cramming everything into such a small space was obviously impossible, and no battery that could fit in such a thing would ever have enough power to spin a floppy disk drive for long. “Anyway, even if you could build it,” I told him, “it wouldn’t be as convenient as you’d like. You’d have to carry around a keyboard too and plug it in every time you wanted to use it.” No you wouldn’t, said Mike. The display could be touch-sensitive. The keyboard could be rendered on the screen as needed and input accepted that way.

This was 1984. What Mike described was pure science fiction. (In 1987 that became literally true, when the touch-controlled “padd” became a staple prop on Star Trek: The Next Generation.) Yet here I am, the proud new owner of a Nexus 7, the latest in high-powered touch-sensitive computing slabs that put even Mike’s audacious vision to shame.

It wasn’t the first time I’d had a failure of technological vision, nor was it the last.

Several years earlier, before even the IBM PC, I was spending a lot of afterschool hours at my friend Chuck’s house, and a lot of those hours on his dad’s home computer, one of the only ones then available: the beloved but now mostly forgotten Sol-20. (The TRS-80 and the Apple ][ were brand new and just about to steal the thunder from hobbyist models like the Sol-20.) It had a small black-and-white monitor that could display letters, numbers, typographical marks, and a few other special characters at a single intensity (i.e., it really was “black and white,” not greyscale). It looked like this:

The display was so adequate for my meager computing needs of the late 1970′s that when the computer magazines I read started advertising things like Radio Shack’s new Color Computer (that’s what it was called — the “Color Computer”), I dismissed them as children’s toys.

Once, Chuck and I entertained the idea of making a little science fiction movie. A scene in Chuck’s script had a person’s face appearing on a computer monitor and speaking to the user. It was his plan to film this scene using his father’s computer. I said, “How are we going to make a face appear on a computer monitor?” I had only ever seen letters and numbers blockily rendered on it. Chuck pointed out that the monitor was really just a small TV. “Oh yeah,” I said, feeling stupid. It ought to be able to display anything a TV could. Of course we’d have to hook it up to a different source; obviously no computer could handle rendering full-motion video. Yet here I am, a software engineer at YouTube.

There’s more. In the mid 80′s, my sometime boss Gerald Zanetti, the commercial food photographer and computing technophile, once described his vision for composing and editing photographs on a high-resolution computer display. If a photograph included a bowl of fruit, he explained, he wanted to be able to adjust the position of an orange separately from the grapes and the bananas surrounding it. I said that such technology was far in the future. I’d seen graphics-editing programs by then, but they treated the image as a grid of undifferentiated pixels. Separating out a foreground piece of fruit from other items in the background simply was not feasible. Yet just a couple of years later Photoshop exactly realized Zanetti’s vision.

In the mid 90′s, when the web was new, my friend and mentor Nathaniel founded a new company, First Virtual, to handle credit card payments for Internet commerce. At the time there was no Internet commerce. Nathaniel and company invented some very clever mechanisms for keeping sensitive credit-card information entirely off the Internet while still enabling online payments. But I felt their system was too complicated to explain and to use, that people would prefer the familiarity and convenience of credit cards (turns out I was right about that), and that since no one would (or should!) ever trust the Internet with their credit card information, Internet commerce could never amount to much. Yet here I am, receiving a new shipment of something or other from Amazon.com every week or two.

Oh well. At least I’m in good company. I’m sensible enough finally to have learned that however gifted I may be as a technologist, I’m no visionary. Now when someone describes some fantastical new leap they imagine, I shut up and listen.

Whither went we?

This blog was offline for a few days last week and may be again soon. You can read all about it in “Pain; or the saga so far.” Fair warning: lots of geeky technical stuff at that link.

How (and why) to program, part 1

This entry is part 1 of 2 in the series How (and why) to program

On May 15th, listeners to the NPR program Weekend Edition were given this challenge by puzzlemaster Will Shortz:

Create a 4-by-4 crossword square with four four-letter words reading across and four different four-letter words reading down. Use the word “nags” at 1 across and the word “newt” at 1 down. All eight words must be common, uncapitalized words, and all 16 letters must be different.

Here is the starting grid as described by Will Shortz:

1N 2A 3G 4S
5E
6W
7T

This puzzle is a ripe one for solving (some might say cheating at…) by computer. All we need is a list of four-letter words and a way to test every combination of words in the grid for validity; so let’s get to it. For this example I will use the Python programming language for its conciseness and readability.

First, the list of words. On Linux and Mac OS X (and most other Unix-based operating systems) there is a file called /usr/share/dict/words, or sometimes /usr/lib/dict/words or /usr/dict/words, which is nothing but a more-or-less comprehensive list of English words, one per line. We’ll read that file to get our list of four-letter words, discarding words that are shorter or longer. In fact, since we know that every word in the grid begins with the letters in NEWT and NAGS — namely, N, E, W, T, A, G, and S — we can even discard four-letter words not beginning with one of those seven letters. And we can discard N words too, because NEWT and NAGS are already given; we don’t need to search for N words. The words we keep will be stored in six different “sets,” one for each of the six remaining starting letters.

Here’s the first section of our code: creating our six empty word-sets and giving each one a name.

a_words = set()
g_words = set()
s_words = set()
e_words = set()
w_words = set()
t_words = set()

Now to get the words we want out of the “words” file. First we “open” the file to get a special placeholder we’ll call wordlist.

wordlist = open('/usr/share/dict/words')

We’re going to read one line of the file at a time. The placeholder remembers our position in the file in between reads.

To read one line of the file at a time and do something with it, we’ll write a “loop” that begins like this:

for word in wordlist:
  ...stuff...

This will do stuff once for each line of the file, with word referring to the contents of that line. Unfortunately the first line of that stuff is a necessary but confusing bookkeeping detail:

for word in wordlist:
  word = word[:-1]
  ...more stuff...

This confusing bit of code is here because each line of the file — each line of every file, in fact — ends with an invisible “newline” character that means “this line is over, start a new one.” Since we want to deal only with the visible content of the line while doing more stuff, we need to discard that newline character, and

word = word[:-1]

is how you do that. (For our purposes right now it’s not terribly important how that works, but you can read it roughly as, “replace word with everything-in-word-except-the-last-character.”)

We’ll begin more stuff with a test to make sure that the word we’re looking at is four letters long:

for word in wordlist:
  word = word[:-1]
  if len(word) == 4:
    ...the rest of the stuff...

Here, len(word) means “the length of word” (i.e., the number of characters it contains), and == is for testing whether two things are equal. (A single = means “make this equal that,” as we’ve already seen a few times.) The rest of the stuff will only happen if len(word) is 4.

If it is 4, then we want to save the word in the correct set — either the set of A words, or the set of G words, etc. Here’s what the complete loop looks like:

for word in wordlist:
  word = word[:-1]
  if len(word) == 4:
    first_letter = word[0]
    if first_letter == 'a':
      a_words.add(word)
    elif first_letter == 'g':
      g_words.add(word)
    elif first_letter == 's':
      s_words.add(word)
    elif first_letter == 'e':
      e_words.add(word)
    elif first_letter == 'w':
      w_words.add(word)
    elif first_letter == 't':
      t_words.add(word)

After determining that len(word) was 4, we gave the name first_letter to the first letter of word (which is written as word[0], because most things in programming are counted beginning at 0 rather than at 1). We then tested first_letter to see if it was a, and added it to a_words if it was; if it wasn’t, we tested it to see if it was g, and added it to g_words if so; etc. The strange word elif appearing in the code above is simply Python’s abbreviation for “else if.”

If first_letter isn’t one of the six we care about — or if word isn’t 4 characters long to begin with — then nothing happens and we loop back around to the for word in wordlist line to get the next word.

After reading all the lines from wordlist, the loop exits and the program does whatever comes next, which is:

wordlist.close()

This is the way to say we’re finished using that placeholder and don’t need it anymore. Doing so releases resources, such as memory space, that the computer can now use for other purposes. (In a little program like this, that doesn’t matter much, so it’s OK to leave out wordlist.close(). But in large programs you can “leak” memory and other resources if you do things like fail to close files when you’re finished with them.)

OK. We’ve got our lists of candidate A words, G words, S words, E words, W words, and T words. Now the strategy will be to try every possible combination of A, G, and S words for 2, 3, and 4 Down; and then, for each of those possible combinations, check that the resulting words in 5, 6, and 7 Across make sense; and additionally that no letter is repeated anywhere in the grid.

To try every combination of A, G, and S words, first we start by trying every A word:

for a_word in a_words:
  ...stuff...

As we’ve already seen, this is a loop that will do stuff once for each entry in a_words (with a_word referring to that entry each time through the loop). Now if we nest another loop inside this loop, like so:

for a_word in a_words:
  for g_word in g_words:
    ...more stuff...

then more stuff will happen once for each possible combination of a_word and g_word. That is, first a_word will be aced (let’s say), and while a_word is aced, g_word will range from gabs to gyro; and when the g_words loop is finished, a_word will advance to aces, and g_word will again range from gabs to gyro, and so on though all the four-letter A words, each one running through all of the four-letter G words, like the digits of an odometer.

From that you should be able to guess that we need to nest another loop inside our nested loop, this one for the S words.

for a_word in a_words:
  for g_word in g_words:
    for s_word in s_words:
      ...test this combination...

Now to test this combination once for each possible combination of A word, G word, and S word. The first test is to see whether the words created by placing a_word in 2 Down, g_word in 3 Down, and s_word in 4 Down result in sensible words at 5 Across, 6 Across, and 7 Across.

Let’s construct the word at 5 Across — the E word — from the second letters of a_word, g_word, and s_word.

e_word = 'e' + a_word[1] + g_word[1] + s_word[1]

(Remember that counting the letters in a word begins at 0, so the second letter of each word is numbered 1.)

Let’s construct the W word and the T word the same way — w_word from the third letter of each Down word, and t_word from each Down word’s fourth letter.

w_word = 'w' + a_word[2] + g_word[2] + s_word[2]
t_word = 't' + a_word[3] + g_word[3] + s_word[3]

Now if a_word is ammo and g_word is gulp and s_word is shot, then e_word will be emuh, w_word will be wmlo, and t_word will be topt, which don’t make sense! But we can easily check whether the Across words make sense by seeing if they can be found in the e_words, w_words, and t_words sets that we created earlier. So:

for a_word in a_words:
  for g_word in g_words:
    for s_word in s_words:
      e_word = 'e' + a_word[1] + g_word[1] + s_word[1]
      w_word = 'w' + a_word[2] + g_word[2] + s_word[2]
      t_word = 't' + a_word[3] + g_word[3] + s_word[3]
      if e_word in e_words and w_word in w_words and t_word in t_words:
        ...remaining test...

If we get as far as the remaining test (which is to ensure that no letter is duplicated anywhere in the grid), we know that e_word and w_word and t_word are real words.

We can ensure no letter is duplicated by using another set called letters_used. The plan: go through the letters of each Down word one by one, checking whether the letter is in the set. If it’s not, then add it to the set and move to the next letter. If it is, then we’ve already seen that letter once and it’s duplicated.

We know the first Down word is newt, so we can create our set with those letters in it.

letters_used = set(['n', 'e', 'w', 't'])

(We could also have created this set like this:

letters_used = set()
letters_used.add('n')
letters_used.add('e')
letters_used.add('w')
letters_used.add('t')

which matches the way we created and used the other sets above, but the first way does the same thing more concisely.)

Now to use that set to find duplicates.

any_duplicates = False
for letter in a_word + g_word + s_word:
  if letter in letters_used:
    any_duplicates = True
    break
  else:
    letters_used.add(letter)

Here, break means “leave the loop immediately.” There’s no need to keep looping once we know there are duplicates.

By the time the loop finishes, either we’ve looped through all the letters and found no duplicates, or we exited the loop with break because we did find duplicates. We check any_duplicates to see which of those two things happened. If it’s True there were duplicates; but if it’s False then we’ve found a valid solution and can print it to display it to the user.

if any_duplicates == False:
  print a_word, g_word, s_word, e_word, w_word, t_word

To recap, here’s the complete program.

a_words = set()
g_words = set()
s_words = set()
e_words = set()
w_words = set()
t_words = set()

wordlist = open('/usr/share/dict/words')

for word in wordlist:
  word = word[:-1]
  if len(word) == 4:
    first_letter = word[0]
    if first_letter == 'a':
      a_words.add(word)
    elif first_letter == 'g':
      g_words.add(word)
    elif first_letter == 's':
      s_words.add(word)
    elif first_letter == 'e':
      e_words.add(word)
    elif first_letter == 'w':
      w_words.add(word)
    elif first_letter == 't':
      t_words.add(word)

wordlist.close()

for a_word in a_words:
  for g_word in g_words:
    for s_word in s_words:
      e_word = 'e' + a_word[1] + g_word[1] + s_word[1]
      w_word = 'w' + a_word[2] + g_word[2] + s_word[2]
      t_word = 't' + a_word[3] + g_word[3] + s_word[3]
      if e_word in e_words and w_word in w_words and t_word in t_words:
        letters_used = set(['n', 'e', 'w', 't'])
        any_duplicates = False
        for letter in a_word + g_word + s_word:
          if letter in letters_used:
            any_duplicates = True
            break
          else:
            letters_used.add(letter)
        if any_duplicates == False:
          print a_word, g_word, s_word, e_word, w_word, t_word

Put this program in a file named puzzle.py and run it with python puzzle.py. On my computer, /usr/share/dict/words contains 470 four-letter words starting with a, 359 starting with g, and 634 starting with s, and it took about two minutes and a quarter for this program to test all of the 470×359×634 combinations. (We could make this program much faster, but at the expense of clarity and simplicity.) In the end it produced this solution:

achy grip sumo ecru whim typo

which is the same solution that Will Shortz gave on the air a week later.

(Actually, my computer produced two solutions, because /usr/share/dict/words includes a lot of questionable junk in its quest to be comprehensive. The other solution was “achy goup sld. ecol whud typ.”)

Update: As a couple of friends have pointed out, the Mac OS X version of /usr/share/dict/words does not include all the words needed to find the solution! I ran mine on Linux, which does.

Ibid bugfixes

The current version of my backup tool ibid is 52. Since the last time I wrote about it, I’ve added a new option, –check-names, for when you can’t rely on device/inode pairs (e.g., when moving your files onto a new filesystem); and fixed some important bugs, including ones that created unnecessary duplicate backups of files and unnecessary “update” records.

Download ibid here.

What a decade and a quarter can do

Halloween, 1998: I take a weekend trip to San Diego with my girlfriend, Andrea, and a few other friends. Money’s tight, in part because I haven’t drawn a salary from my struggling startup company for over two years, but that’s OK: our friends, most of whom are graduate students, are all broke too, so we crash in the living room of a couple we’ve come to see. We visit the San Diego Zoo, where I meet and feed a baby giraffe. My friend Paul captures the event (and much else from the weekend) on an amazing new device: an SLR camera body that has been partially hollowed out and fitted with a digital sensor and a small LCD display on the back (since the prism is gone and the viewfinder no longer works). It’s borrowed from the university where he works, which custom-built it for about ten thousand dollars. He shares the resulting digital photos with the rest of us by putting them on his department’s web server, but only temporarily because they take up so much disk space that he has to delete them after a few weeks.

1.25 decades later: Andrea is now my wife. We visit San Diego again. Thanks in part to income from my startup company, we have the means to stay in a hotel, and not only to visit the San Diego Zoo but to spring for their Safari Park’s “Roar and Snore” overnight camping experience. For his part, Paul is now an Academy-Award-winning computer-graphics researcher. Digital cameras on a par with his custom-built experimental rig from 1998 can now be had for around a hundred bucks and are so ubiquitous that they’ve all but killed the consumer film business. Disk space, likewise, is cheap enough that the company I work for has made a lucrative business out of giving away essentially unlimited amounts of it for free. I meet and feed not one giraffe, but two…

…and two amazing people who didn’t even exist 1.25 decades ago feed some giraffes too.

Quitting time

On this date fifteen years ago, several employees of NCD Software, formerly Z-Code, resigned simultaneously. I was one of them.

Two years earlier, Z-Code’s founder, Dan, sold out to Network Computing Devices over the objections of most of his staff. NCD, whose line of business had no discernable overlap with Z-Code’s, proceeded to drive Z-Code and itself right into the ground. Dan was the first casualty, lasting only a few months after the merger. NCD’s CEO and top VP, informally known as “the Bill and Judy show,” followed not long after. A lot of clueless mismanagement ensued. The energy of our once terrific engineering team dissipated before our eyes. We tried to turn things around, to make our bosses understand (for instance) that you can’t just tell an e-mail software team to make their e-mail suite into one of those newfangled web browsers that the new CEO had heard so much about, or that if you don’t pay your salespeople a commission for selling the company’s software, they won’t sell the company’s software.

Each time management did something boneheaded, we convened a session of “The Alarmists’ Club,” which met at lunch over beers and tried to think of ways to effect change at NCD. After enough of those proved fruitless, our discussions turned to how we could do things better ourselves. And so some time early in 1996 we sought the advice of a Silicon Valley lawyer about how to leave NCD en masse with minimal legal repercussions. The bulk of the advice was to put off discussion of any new venture until after the separation was complete; and to be aware that NCD was liable to use veiled threats, emotional pleas, and vague promises in an attempt to get us not to leave.

On 14 February 1996, NCD did all these things. We had prepared our terse resignation letters, offering two weeks notice, and delivered them in the morning. Within a couple of hours, Mike Dolan, one of the bigwigs from NCD headquarters in Mountain View, made the trip to the Z-Code offices in Novato to meet with us individually.

I was not yet 30, and when Dolan, an industry veteran, leaned on me in our one-on-one meeting I was definitely cowed. But my co-resigners and I had coached one another on how to withstand exactly the sort of combined intimidation and guilt trip that I was now getting, and so I stuck to my guns, kept the pointless justifications to a minimum, and refrained from blame or recrimination.

We maintained our solidarity, and because NCD declined our offer of two weeks’ notice, that was our last day there. We left feeling victorious, though what exactly we had won was never clear, and our sense of triumph was tempered by having effectively sandbagged our erstwhile coworkers.

After enjoying a few days of freedom it was time to start planning our new enterprise. But that’s another story…

SIEVE

In a recent e-mail exchange with my friend Kurt, we were discussing the problem of orbital space junk and the difficulty of cleaning it up. It’s a subject we’ve batted around on and off for many years, wondering about a workable and economical solution but never managing to find one. It’s been in the news more lately, as the crisis has grown more acute and inventors have trotted out different proposals, each more outlandish than the last.

In the middle of this exchange, after years of coming up with nothing, I suddenly invented my own solution, an idea I now offer publicly as the second in my occasional save-the-world series. It’s called SIEVE: Scanning, Illuminating, Even Vaporizing Engines.

It involves deploying into low earth orbit thousands of semi-autonomous robots. Each SIEVE unit is small and light and costs no more than a few hundred dollars of off-the-shelf components. Specifically, these components:

  • A solar panel for power;
  • Gyros for orientation;
  • A radio for coordination with other SIEVE units;
  • A camera;
  • A simple computer;
  • A Mylar mirror; and
  • A small rocket engine.

Each unit, when in sunlight, is in one of three modes: Scanning, Illuminating, and Vaporizing.

In Illuminating mode, the unit orients itself so that the mirror reflects sunlight through a given volume of space.

In Scanning mode, the unit trains its camera on a region of space that other nearby units are Illuminating and searches for debris.

In Vaporizing mode, numerous units all aim their mirrors to shine sunlight on a piece of debris, one previously identified by Scanning and Illuminating units and whose orbital trajectory has been plotted. Focusing enough sunlight on the debris for a long enough time should heat it to the point of vaporizing. If the debris can be fully vaporized, great; it should be harmless in that form. If it can’t, it might still expel enough vapor to slow its orbit (a la the laser broom idea) to the point where it falls back into the atmosphere.

The rocket engine is only needed twice: once to insert the unit into a distinct orbit when initially deployed, and once to deorbit the unit at the end of its service life.

Care will have to be taken that the SIEVE robots do not themselves become hazards to space navigation. And that they don’t go into Michael Crichton mode, become sentient, and decide the Earth is a gigantic ball of debris.

Elbows deep

Last week I replaced my six-year-old home server (which serves this website among many other functions) with a newer, faster, quieter computer. Transferring all the data and functions was a considerable effort in system administration. For the record, here are the steps I had to take.

  1. Download Fedora 12 install-CD image.
  2. Burn Fedora 12 install CD.
  3. Shut down sendmail and Apache.
  4. Dump MySQL database contents.
  5. Dump Postgresql database contents.
  6. Bring up new computer with temporary hostname.
  7. Install Fedora 12 on new computer.
  8. Create user accounts.
  9. Copy all data from old computer to new, under /old tree.
  10. Shut down old computer (permanently).
  11. Take over old computer’s hostname and IP address.
  12. Restore firewall config from /old.
  13. Restore DNS config from /old, bring up DNS.
  14. Restore sshd config from /old, bring up sshd.
  15. Restore Maildir trees from /old.
  16. Restore IMAP server config from /old, bring up IMAP server.
  17. Restore sendmail config from /old, bring up sendmail.
  18. Restore WordPress environment from /old.
  19. Bring up MySQL, restore contents from MySQL dump.
  20. Bring up Postgresql, restore contents from Postgresql dump.
  21. Restore Apache config from /old, bring up Apache.
  22. Restore Mailman environment from /old, bring up Mailman.
  23. Bring up apcupsd.
  24. Add printer.
  25. Set up network printing.
  26. Set up NFS.
  27. Resume backups.

Naturally not everything went according to plan. So in addition to the steps above I also had to solve:

  • Why all of my domains but one could be resolved;
  • Why the firewall was getting reset at startup;
  • Why inbound mail was not flowing;
  • Why the Ethernet interface had the wrong parameters at startup;
  • Why the monitor would not go into power-save mode;
  • How to get the Flash plugin running under x86_64;
  • Why the DVD-RW drive wasn’t visible some of the time.

Throughout all this, I frequently had to pause to locate and install needed software packages and Perl modules that weren’t part of the default Fedora setup. For good measure I also had to replace an external hard drive that was about to fail. (Thanks for the warning, Palimpsest!)

Happily all these things are now done, except that the monitor issue is a bona fide bug in the xorg video driver (duly filed) that someone else will have to deal with. Until then I just have to remember to switch the monitor off when I walk away.

This may all sound like deep wizardry, but it doesn’t feel like it to me. Having spent a lifetime coping and communing with these sometimes-cantankerous machines, it’s just busywork. Then I think of the number of other people in the world who could do all of this single-handedly and I become impressed with myself.