#! /usr/bin/perl -wl use strict; use warnings; my @names = qw(JOHN MARTY PAUL SHEILA SMACK SUZY ELSA); for my $name (@names) { my @d = map {ord($_) - ord("A") + 1} split //, $name; my @solution = find_square(@d); if (@solution) { printf "%s:\n %2d %2d %2d\n %2d %2d %2d\n %2d %2d %2d\n", $name, @solution; printf "\n %s %s %s\n %s %s %s\n %s %s %s\n\n", map chr($_+ord("A")-1), @solution; } else { print "$name: no solution\n"; } } # All solutions look like: # # x+y x-z x-y+z # x-2y+z x x+2y-z # x+y-z x+z x-y # # Up to rotation and reflection we may insist that: # # 0 < z < y # z != y-z # # If 2z < y they are, in order: # # x-2y+z, x-y, x-y+z, x-z, x, x+z, x+y-z, x+y, x+2y-z # # Otherwise if y < 2z they are: # # x-2y+z, x-y, x-z, x-y+z, x, x+y-z, x+z x+y, x+2y-z # # This falls in the range 1..26 if 0 < x-2y+z and x+2y-z < 27. # # So 4y-2z < 25 with z < y means y < 13. sub find_square { my @d = sort {$a <=> $b} @_; my $min_range = $d[-1] - $d[0]; # As shown above, y < 13, but the lower limit is more complicated. # So work down from there. my $y = 13; Y: while (1) { $y--; last Y if 4*$y-2 < $min_range; # y < z is trivial, but the lower limit is complicated. # Again we were down from the top. my $z = $y; Z: while (1) { $z--; next Y if 4*$y-2*$z > 25; next Y if $z < 1; my @in_order; if ($z+$z > $y) { @in_order = ( -2*$y + $z, -$y, -$z, -$y + $z, 0, $y - $z, $z, $y, 2*$y - $z, ); } elsif ($z+$z < $y) { @in_order = ( -2*$y + $z, -$y, -$y + $z, -$z, 0, $z, $y - $z, $y, 2*$y - $z, ); } else { next; } # Sanity check for our logic. Can be removed. for (1..$#in_order) { if ($in_order[$_-1] >= $in_order[$_]) { print "y: $y, z:$z\n"; die "Out of order: @in_order"; } } # i is the index $d[0] matches at, which tells us $x. I: for my $i (0..$#in_order) { my $x = $d[0] - $in_order[$i]; # Sanity check our number range. # If we're out of range and increasing $i will not help, # then move to a different ($x, $z), otherwise only # increment $i. next Z if $x + $in_order[0] < 1; next I if $x + $in_order[-1] > 26; next Z if $x + $in_order[-1] < $d[-1]; # We match the lowest number, can we find the rest? # We search with "zipping" logic. my $p_d = my $p_o = 1; while ($p_d < @d) { if ($x + $in_order[$p_o] < $d[$p_d]) { # Perhaps the next number will match our digit? $p_o++; } elsif ($x + $in_order[$p_o] > $d[$p_d]) { # $d[$p_d] is not anywhere in our square. next I; } else { # One more digit matched. $p_o++; $p_d++; } } # If we got here we have a solution! Return it in the # right order for a square. return ( $x + $y, $x - $z, $x - $y + $z, $x - 2*$y + $z, $x, $x + 2*$y - $z, $x + $y - $z, $x + $z, $x - $y, ); } } } return; } #### x11 x12 x13 x21 x22 x23 x31 x32 x33 #### S = 4*S - 3*S = (x21 + x22 + x23) +(x11 + x22 + x33) +(x12 + x22 + x32) +(x13 + x22 + x31) -(x11 + x12 + x13) -(x21 + x22 + x23) -(x31 + x32 + x33) = 3*x22 #### From choice of x, y, and z we have: x11 = x + y x12 = x - z x22 = x We already showed that the sum of every column, row and diagonal is 3x Finish the top row: 3x = S = x11 + x12 + x13 = (x + y) + (x - z) + x13 = 2x + y - z + x13 So x13 = x - y + z Now let's do the bottom row: 3x = S = x13 + x22 + x31 = (x - y + z) + (x) + x31 So x31 = x + y - z 3x = S = x12 + x22 + x32 = (x - z) + (x) + x32 = 2x - z + x32 So x32 = x + z 3x = S = x11 + x22 + x33 = (x + y) + (x) + x33 = 2x + y So x33 = x - y Now do the 2 sides 3x = S = x11 + x21 + x31 = (x + y) + x21 + (x + y - z) = 2x + 2y - z + x21 So x21 = x - 2y + z 3x = S = x13 + x23 + x33 = (x - y + z) + x23 + (x - y) = 2x - 2y + z + x23 So x23 = x + 2y - z #### x11 + x12 + x13 = (x + y) + (x - z) + (x - y + z) = 3x x21 + x22 + x23 = (x - 2y + z) + (x) + (x + 2y - z) = 3x x31 + x32 + x33 = (x + y - z) + (x + z) + (x - y) = 3x x11 + x21 + x31 = (x + y) + (x - 2y + z) + (x + y - z) = 3x x12 + x22 + x32 = (x - z) + (x) + (x - z) = 3x x13 + x23 + x33 = (x - y + z) + (x + 2y - z) + (x - y) = 3x x11 + x22 + x33 = (x + y) + (x) + (x - y) = 3x x13 + x22 + x31 = (x - y + z) + (x) + (x + y - z) = 3x