Enigmatic Code

Programming Enigma Puzzles

Enigma 1537: A name game

From New Scientist #2700, 21st March 2009 [link]

A school class consisted of Elsa, John, Marty, Paul, Sheila, Smack and Suzy. I explained to them that a 3-by-3 magic square consists of an array of nine different numbers such that any row, column or diagonal has the same sum. I asked each of them to make one using only whole numbers between 1 and 26 inclusive.

They each succeeded and I then asked them to replace their numbers by letters using A=1, B=2, C=3 and so on. Amazingly, in every case but one the child’s magic square contained the letters of their name.

Whose did not?

[enigma1537]

Advertisements

2 responses to “Enigma 1537: A name game

  1. Jim Randell 18 April 2012 at 12:31 pm

    Here’s my original Perl solution. It runs in 1.8s.

    use strict;
    
    use Enigma qw/distinct/;
    
    # make a magic square
    my ($N11, $N12, $N13,
        $N21, $N22, $N23,
        $N31, $N32, $N33);
    
    my ($SUM, %LETTERS, %NAMES, %DONE, $done);
    
    %DONE = ();
    
    for $N11 (1..26) {
      for $N12 (1..26) {
        next unless distinct($N12, $N11);
        for $N13 ($N11+1..26) {
          next unless distinct($N13, $N11, $N12);
          $SUM = $N11 + $N12 + $N13;
          for $N21 (1..26) {
            next unless distinct($N21, $N11, $N12, $N13);
            $N31 = $SUM - $N11 - $N21;
            next if $N31 < 1 or $N31 > 26 or $N31 < $N11 or $N31 < $N13;
            next unless distinct($N31, $N11, $N12, $N13, $N21);
            for $N22 (1..26) {
              next unless distinct($N22, $N11, $N12, $N13, $N21, $N31);
              next unless $N13 + $N22 + $N31 == $SUM;
              $N23 = $SUM - $N21 - $N22;
              next if $N23 < 1 or $N23 > 26;
              next unless distinct($N23, $N11, $N12, $N13, $N21, $N22, $N31);
              $N32 = $SUM - $N12 - $N22;
              next if $N32 < 1 or $N32 > 26;
              next unless distinct($N32, $N11, $N12, $N13, $N21, $N22, $N23, $N31);
              $N33 = $SUM - $N13 - $N23;
              next if $N33 < 1 or $N33 > 26 or $N33 < $N11;
              next unless distinct($N33, $N11, $N12, $N13, $N21, $N22, $N23, $N31, $N32);
              next unless $N11 + $N22 + $N33 == $SUM;
              next unless $N31 + $N32 + $N33 == $SUM;
    
              # turn the digits into letters
              %LETTERS = ();
              for ($N11, $N12, $N13, $N21, $N22, $N23, $N31, $N32, $N33) {
                $LETTERS{chr(64 + $_)} = 1;
              }
              %NAMES = ();
              for (qw/ELSA JOHN MARTY PAUL SHEILA SMACK SUZY/) {
                $done = 1;
                for (split '', $_) {
                  unless ($LETTERS{$_}) {
                    $done = 0;
                    last;
                  }
                }
                next unless $done;
                $DONE{$_}++;
                $NAMES{$_}++;
              }
              next unless keys %NAMES;
    
              print "[$SUM]\n";
              print "$N11,$N12,$N13\n$N21,$N22,$N23\n$N31,$N32,$N33\n";
              print "[", join(',', sort keys %NAMES), "]\n\n";
            }
          }
        }
      }
    }
    
    for (qw/ELSA JOHN MARTY PAUL SHEILA SMACK SUZY/) {
      printf "%s: %d\n", $_, $DONE{$_} || 0;
    }
    

    Solution: Paul’s magic square does not contain the letters of his name.

    • Jim Randell 18 April 2012 at 12:34 pm

      And here’s a Python solution. It runs in 45ms.

      It makes use of the fact that the centre number is a 3 x 3 magic square is one third the magic constant (see my comments in the Enigma 1680 posts for proof of this). I’ve also included a new irange() function (inclusive range) in my enigma.py library, for those cases when you want a range that includes both endpoints.

      from itertools import combinations
      from enigma import irange, printf
      
      ns = set(irange(1, 26))
      
      names = ('ELSA', 'JOHN', 'MARTY', 'PAUL', 'SHEILA', 'SMACK', 'SUZY')
      
      # consider the square:
      #  a b c
      #  d e f
      #  g h i
      
      # to eliminate duplicate squares we will assume b < d, f, h and a < c
      
      count = dict((n, 0) for n in names)
      # pick b
      for b in irange(1, 23):
        ns1 = ns.difference((b,))
        # pick a and c
        for (a, c) in combinations(ns1, 2):
          if c < a: (a, c) = (c, a)
          # determine the magic constant
          s = a + b + c
          # e is s / 3 (see solution to Enigma 1680)
          (e, r) = divmod(s, 3)
          if r > 0: continue
      
          # and the other values follow
          g = s - (c + e)
          h = s - (b + e)
          i = s - (a + e)
          d = s - (a + g)
          f = s - (c + i)
          if not(b < d and b < f and b < h): continue
      
          # check they are all different and valid
          ns2 = ns1.difference((a, c, d, e, f, g, h, i))
          if len(ns2) != 17: continue
      
          # turn the numbers into letters
          ls = set(chr(64 + n) for n in (a, b, c, d, e, f, g, h, i))
      
          for n in names:
            if set(n).issubset(ls): count[n] += 1
      
      for (k, v) in count.items():
        if v > 0: continue
        printf("{k} occurs in {v} squares")
      

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: