About Me

19 July 2009

Einstein's Riddle

Here’s a sucky program I wrote to solve Einstein’s Riddle, which has nothing to do with Einstein. It is many, many times slower than other much bettersolutions.

Perhaps 'sucky' is a bit harsh. In its favor I believe it to be correct: it cycles through all 24,883,200,000 (= 5^5 x 4^5 x 3^5 x 2^5 x 1^5) possible solutions and finds the one and only combination that satisfies all the requirements. It is also a straight-forward representation of the stated riddle, and was therefore quick to write. On the downside, it does take 10 minutes to find the solution, which does not compare well with the solutions referred to above.

// arbitrarily assign the leftmost house an index of 0, and the rightmost 4;// e.g. the nationality of the owner of the left-most house would be given by// nations[0], and the pet he keeps would be given by animals[0], and the pet// his neighbour (on his right) keeps would be given by animals[1], and so onconst int num_houses = 5;nation nations[num_houses];colour colours[num_houses];animal animals[num_houses];cigarette cigarettes[num_houses];drink drinks[num_houses];

// set the combination of all house variables to the next permutationbool permute(){ if (next_permutation(nations)) return true; init(nations); if (next_permutation(colours)) return true; init(colours); if (next_permutation(animals)) return true; init(animals); if (next_permutation(cigarettes)) return true; init(cigarettes); if (next_permutation(drinks)) return true; init(drinks); // we have cycled through all permutations of all variables return false;}

// return the number of the house of the given colourinline int find_colour(colour c){ int i = 0; while (colours[i] != c) ++i; return i;}

// return the number of the house whose owner is of the given nationalityinline int find_nation(nation n){ int i = 0; while (nations[i] != n) ++i; return i;}

// return the number of the house whose owner smokes the given brand of cigaretteinline int find_cigarette(cigarette c){ int i = 0; while (cigarettes[i] != c) ++i; return i;}

- Einstine13 run time is 0.012s (real 0.012s, user 0.001s, sys 0.002s) on a 2.8GHz Intel E8235 processor. (But an "int main() {}" runs in 0.010s (real), so should we subtract that from 0.012s and call it 2ms? I'm not sure. It doesn't really matter; what matters is that Einstine13 is in the same ballpark as the fastest solutions given by others, and not in the next county as Einstine09 was.) Einstine09 runs in 350.808s (real) on the same machine, compiled with the same compiler and -O3 option. Choosing a better algorithm gave us a 30,000× speed improvement (350.808 / 0.012 = 29,234).

- There are 5 different attributes (such as colour, nationality) and each attribute has 5 unique values. There are 5! permutations of 5 unique values so the total number of possible solution states is (5!)5 = 24,883,200,000. Einstine09 calls is_solution() for each and every one of these states and is sloooow. Einstine13 calls the possible() functions a total of just 18,360 times and is much faster.

- I knew the problem was one of combinatorial search, like the eight queens problem. But I couldn’t see how to prune the search tree by applying all the rules at each branch because a rule might refer to something that hadn’t yet been decided: you can’t chop off a branch where the Winfield smoker doesn’t drink beer if you don’t yet know who the Winfield smoker is. And so on. My personal aha! moment came while reading Edi Weitz’s explanation of his Lisp solution: “Note that every condition has to return T if [...] not all of its parameters are defined yet.” Yes, it’s obvious to me too, now.

- Reading Edi Weitz’s commentary gave me one of the clearest glimpses I’ve had of the secret ingredient in Lisp and I recommend it to anyone struggling to understand why so many Lisp cognoscenti rave about the language. But I have to say that without his explanation I think I would have struggled to understand his code. My C++ solution is fast, but it's specific to solving the Einstein problem. Dr. Weitz's Lisp solution is also fast, but in more-or-less the same amount of code he has created a domain-specific language for solving any Einstine-like problem. Thank you Dr. Weitz for sharing it.