Home

Google's Geek Aptitude Tests


Google's highly-publicized Geek aptitude tests have created quite a bit of buzz in Silicon Valley and other places where geeks congregate. The MathWorld website has a nice writeup on some of the problems as well as Mathematica-based ways of solving them. A typical (if you can apply the term to such an eclectic mix of puzzles) example is:

Solve this cryptic equation, realizing of course that values for M and E could be interchanged. No leading zeros are allowed.

WWWDOT - GOOGLE = DOTCOM

i.e. one needs to find a distinct decimal digit corresponding to each distinct alphabetic letter in the puzzle such that the resulting numbers satisfy the given equality. MathWorld gives several Mathematica-based algorithms for solving the problem, each one running faster than the previous one. This is an interesting optimization problem - here is a simple (only lightly optimized in the purely arithmetic sense, e.g. it needs no integer multiplies) C program that uses a brute-force approach to solving the problem:


#include "stdio.h"

int main()
{

/* Brute-force code for solving the cryptogram "wwwdot - google = dotcom" */
int w,d,o,t,g,l,e,c,m,wwwdot,google,dotcom;

wwwdot = google = dotcom = 0;
for(w=0;w<10;++w){
  for(d=0;d<10;++d){
    for(o=0;o<10;++o){
      for(t=0;t<10;++t){
        for(g=0;g<10;++g){
          for(l=0;l<10;++l){
            for(e=0;e<10;++e){
              for(c=0;c<10;++c){
                for(m=0;m<10;++m){
                  if(wwwdot - google == dotcom)
                  {
                    /* Make sure distinct letters all correspond to distinct digits: */
                    if(!(
                         w==d||w==o||w==t||w==g||w==l||w==e||w==c||w==m
                             ||d==o||d==t||d==g||d==l||d==e||d==c||d==m
                                   ||o==t||o==g||o==l||o==e||o==c||o==m
                                         ||t==g||t==l||t==e||t==c||t==m
                                               ||g==l||g==e||g==c||g==m
                                                     ||l==e||l==c||l==m
                                                           ||e==c||e==m
                                                                 ||c==m
                    ))
                    printf("%6d - %6d = %6d\n",wwwdot,google,dotcom);
                  }
                  dotcom++;
                }
                dotcom +=  90;
              }
              dotcom -= 1000;
              google += 1;
            }
          }
          google += 100000;
        }
        wwwdot++;
        google -= 1001000;
        dotcom +=    1000;
      }
      google += 11000;
      dotcom +=    10;
    }
    google -= 110000;
    dotcom -=    100;
  }
  dotcom -= 1000000;
  wwwdot +=  110000;
}

return 0;
}

This needs about 15 CPU-seconds on a 1GHz Alpha ev6 workstation to find the 2 solutions to the problem:
777589 - 188103 = 589486
777589 - 188106 = 589483
We can clearly cut a bit of runtime off this by restricting W, G and D to be strictly greater than zero, but what we really want is an asymptotically faster algorithm than exhaustive search. The obvious one that presents itself is to restrict the 9 parameters W,D,O,T,G,L,E,C,M to all possible permutations of the numbers 0,1,2,3,4,5,6,7,8,9, and where we discard the last element of each permutation, since we have only 9 parameters to assign to. Here is some simple example code that implements this:

#include "stdio.h"

void nPerm(int a[], int ncurr, int ndim, int *count);
void dumpCurrPerm(int a[], int ncurr, int ndim);
void swap2int(int a[], int pos1, int pos2);
void checkGoogEq(int a[]);

int main()
{
  const int n = 10;
  int a[n];
  int count = 0;

  // Init the permutation array:
  for(int i = 0; i < n; ++i)
  {
    a[i] = i;
  }
  // ...and generate the n! permutations:
  nPerm(a, n, n, &count);

  printf("Generated %d permutations.\n", count);

  return 0;
}

/* Recursive function to generate all possible permutations of the integers {0, 1, 2, ... , n-1} */
void nPerm(int a[], int ncurr, int ndim, int *count)
{
  /* Recursion terminates when ncurr = 1: */
  if(ncurr == 1)
  {
    /* dumpCurrPerm(a, ndim);    Uncomment this to see the actual permutations */
    ++(*count);
    checkGoogEq(a);
  }
  else
  {
    /* Swap a[ncurr-1] with each of a[0], ... , a[ncurr-2] in turn and recurse: */
    for(int i = 0; i < ncurr; ++i)
    {
      swap2int(a, i, ncurr-1);
      nPerm(a, ncurr-1, ndim, count);
      /* Undo the swap in preparation for the next one: */
      swap2int(a, i, ncurr-1);
    }
  }
}

void dumpCurrPerm(int a[], int ncurr, int ndim)
{
  for(int i = 0; i < ndim; ++i)
  {
    printf(" %d", a[i]);
  }
  printf("\n");
}

void swap2int(int a[], int pos1, int pos2)
{
  int tmp;
  tmp=a[pos1]; a[pos1]=a[pos2]; a[pos2]=tmp;
}

void checkGoogEq(int a[])
{
  int w,d,o,t,g,l,e,c,m,wwwdot,google,dotcom;
  /* Discard the last element of the permutation here: */
  w = a[0];
  d = a[1];
  o = a[2];
  t = a[3];
  g = a[4];
  l = a[5];
  e = a[6];
  c = a[7];
  m = a[8];

  wwwdot = w*111000 + d*100 + o*10 + t;
  google = g*100100 + o*11000 + l*10 + e;
  dotcom = d*100000 + o*10010 + t*1000 + c*100 + m;

  if(wwwdot - google == dotcom)
    printf("%6d - %6d = %6d\n",wwwdot,google,dotcom);
}
This needs less than a half-second to run through all possible 10-permutations and find the 2 solutions on the aforementioned hardware, compared with nearly 3 seconds for the fastest Mathematica-based algorithm posted on the MathWorld website (they didn't say what hardware they ran on, but mention that it was a "relatively fast machine.") The above code is arguably simpler than the best-of-breed Mathematica code. Of course there are further straightforward optimizations one could do within the permutation code - one could attempt to implement a nonrecursive permutation generator to eliminate the computational overhead associated with the recursion, for instance - but those would at best give modest improvements.

More interesting (albeit not asymptotically faster) would be a version of the permutation algorithm that only changes some minimum number of elements of the permutation between iterates, i.e. an N-permutational analog of a binary Gray code. That would minimize the amount of arithmetic needing to be performed in each call to the above checkGoogEq function - one would simply modify that function to take added arguments that key which permutation elements have changed in the latest iterate, and then use an incremental-update scheme to appropriately modify the variable(s) affected by the change. At this moment I don't know if such a "Gray permutation" algorithm exists - I haven't been able to find any references in my (admittedly not very deep) literature searches. If anyone knows of such an algorithm, I would appreciate it if you would be kind enough to send me a reference.


Related Links and Followups:


http://hogranch.com/mayer/google.html -- Last Revised: 21 Aug 2005
Send mail to ewmayer@aol.com