Keith Vetter 2019-01-18 : for a recent programming challenge I needed to enumerate all 880 unique 4x4 magic squares. I thought I'd share the code. The biggest challenge is in catching rotations and reflections, and I solve this by controlling the position of the digits 1 and 2.
##+##########################################################################
#
# magic_squares.tsh -- Finds all 4x4 magic square (880)
# by Keith Vetter 2019-01-18
#
# To avoid rotations and reflections, we normalize by putting 1 into either
# the first or second position in the top row or the second position in the
# second row, and 2 into the upper right quadrant.
#
proc FindMagic {} {
set nums {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16}
set all {}
foreach row1 [OneRow $nums $nums $nums $nums] {
lassign $row1 a b c d
set nums2 [Minus $nums $row1]
foreach col1 [OneRow $a $nums2 $nums2 $nums2] {
lassign $col1 . e i m
if {$a == 1 && 2 in $col1} continue ;# Symmetry
set nums3 [Minus $nums2 $col1]
foreach row2 [OneRow $e $nums3 $nums3 $nums3] {
lassign $row2 . f g h
if {1 ni [list $a $b $f]} continue
set nums4 [Minus $nums3 $row2]
set j [expr {34 - $d - $g - $m}]
if {$j ni $nums4} continue
set nums5 [Minus $nums4 $j]
foreach row3 [OneRow $i $j $nums4 $nums4] {
lassign $row3 . . k l
set nums6 [Minus $nums5 $row3]
set n [expr {34 - $b - $f - $j}]
if {$n ni $nums6} continue
set o [expr {34 - $c - $g - $k}]
if {$o ni $nums6 || $o == $n} continue
set p [expr {34 - $a - $f - $k}]
if {$p == $n || $p == $o || $p ni $nums6} continue
if {$b != 1 && 2 in [list $e $i $j $m $n $o]} continue
lappend all [list $row1 $row2 $row3 [list $m $n $o $p]]
}
}
}
}
return $all
}
proc OneRow {aValues bValues cValues dValues} {
# Returns list of all possible 4 values for a row summing to 34
# By symmetry, neither c nor d can be 1
set all {}
foreach a $aValues {
foreach b $bValues {
if {$a == $b} continue
foreach c $cValues {
if {$c == 1 || $c == $a || $c == $b} continue
set d [expr {34 - $a - $b - $c}]
if {$d == 1 || $d ni $dValues || $d in [list $a $b $c]} continue
lappend all [list $a $b $c $d]
}
}
}
return $all
}
proc Minus {setA setB} {
# Computes setA - setB
set result {}
foreach item $setA {
if {$item ni $setB} {
lappend result $item
}
}
return $result
}
################################################################
#
# Display result code below
#
proc ShowOne {sln} {
ShowMany [list $sln]
}
proc ShowAll {slns} {
set stride 6
set stride1 [expr {$stride - 1}]
for {set idx 0} {$idx < [llength $slns]} {incr idx $stride} {
ShowMany [lrange $slns $idx $idx+$stride1]
}
}
proc ShowMany {slns} {
unset -nocomplain ROW
foreach sln $slns {
lassign [ToString $sln] row1 row2 row3 row4
lappend ROW(1) $row1
lappend ROW(2) $row2
lappend ROW(3) $row3
lappend ROW(4) $row4
}
set TL \u250c
set TR \u2510
set BL \u2514
set BR \u2518
set U \u2502
set M \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
puts [string repeat "$TL$M$TR " [llength $slns]]
puts $U[join $ROW(1) "$U $U"]$U
puts $U[join $ROW(2) "$U $U"]$U
puts $U[join $ROW(3) "$U $U"]$U
puts $U[join $ROW(4) "$U $U"]$U
puts [string repeat "$BL$M$BR " [llength $slns]]
}
proc ToString {sln} {
lassign [concat {*}$sln] a b c d e f g h i j k l m n o p
set result {}
lappend result [format {%2s %2s %2s %2s} $a $b $c $d]
lappend result [format {%2s %2s %2s %2s} $e $f $g $h]
lappend result [format {%2s %2s %2s %2s} $i $j $k $l]
lappend result [format {%2s %2s %2s %2s} $m $n $o $p]
return $result
}
proc Check {sln} {
lassign [concat {*}$sln] a b c d e f g h i j k l m n o p
# if {1 ni [list $a $b $e $f]} { puts "not normalized" ; return }
if {$a + $b + $c + $d != 34} { error "first row"}
if {$e + $f + $g + $h != 34} { error "second row"}
if {$i + $j + $k + $l != 34} { error "third row"}
if {$m + $n + $o + $p != 34} { error "fourth row"}
if {$a + $e + $i + $m != 34} { error "first col"}
if {$b + $f + $j + $n != 34} { error "second col"}
if {$c + $g + $k + $o != 34} { error "third col"}
if {$d + $h + $l + $p != 34} { error "fourth col"}
if {$a + $f + $k + $p != 34} { error "1 diag"}
if {$d + $g + $j + $m != 34} { error "2 diag"}
}
puts stderr "computing..."
set all [FindMagic]
ShowAll $all
return