Matrix patterns
A grid filled in a clockwise spiral and read back along it, an odd magic square by the Siamese method with every row, column and diagonal sum checked, and the last grid turned and flipped - a magic square stays magic all eight ways.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A grid filled in a clockwise spiral and read back along it. An odd magic square made by the Siamese method, with every row, column and diagonal added up. And the last grid turned and flipped: a magic square stays magic all eight ways. It starts with the 3 x 3 magic square.
main.eml- the menu, reading sizes, showing grids and sumsgrids.eml- blank grids, the spiral both ways, the magic square, the sums, the check, and the turns and flips
How each part works:
- A grid is a list of rows, each made as its own list: repeating one row list would make every row the same list, so writing one square would change a whole column.
- Spiral fill: 1, 2, 3, ... along the top edge, down the right, back along the bottom, up the left, then the four edges move in by one.
- Spiral reading, as in the corpus case
spiral-matrix-traversal, walks the same path. Two guards stop a last single row or column being read twice, which the 1 x 7 and 5 x 1 grids test. - Magic square, Siamese method, for an odd n: 1 goes in the middle of the top row. Each next number goes one up and one right, wrapping round the edges; if that square is taken, it goes one down instead. Every row, column and both diagonals then add up to n(n^2 + 1)/2: 15 for 3, 65 for 5, 175 for 7.
- Turns and flips: a quarter turn clockwise, a mirror left to right, upside down, and a transpose (rows become columns). They move whole rows, columns and diagonals, so a magic square stays magic. Its 8 views are all different.
What is checked: menu choices 1 to 5; 1 to 12 rows and columns for a spiral; an odd size from 3 to 15 for a magic square; turn, mirror, upside down or transpose. An empty answer cancels.
Sessions: sessions/basic.in gives: - the 3 x 3 magic square, then spirals of 4 x 5, 1 x 7 and 5 x 1, each read back in order; - the 5 x 5 magic square with its sums, then turned, mirrored, transposed (back to the start) and turned upside down, magic each time; - the 7 x 7 magic square: every sum 175.
sessions/bad-input.in gives: - menu choices 0 and x; - rows 0, 13 and abc, then a cancelled column count; - sizes 4, 17 and 1 for a magic square, then a cancel; - the move "rotate", then a cancel; - a 3 x 3 spiral turned: not a magic square.
Built on the verified corpus case spiral-matrix-traversal (four shrinking edges, with the guards that stop a last row or column being read twice).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Matrix patterns ==
Spirals, magic squares, and grids turned and flipped.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 0
Pick a number from 1 to 5.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> x
Pick a number from 1 to 5.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 1
rows (1 to 12)> 0
Type a whole number from 1 to 12.
rows (1 to 12)> 13
Type a whole number from 1 to 12.
rows (1 to 12)> abc
Type a whole number from 1 to 12.
rows (1 to 12)> 3
columns (1 to 12)>
Cancelled.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 2
odd size (3 to 15)> 4
Type an odd whole number from 3 to 15.
odd size (3 to 15)> 17
Type an odd whole number from 3 to 15.
odd size (3 to 15)> 1
Type an odd whole number from 3 to 15.
odd size (3 to 15)>
Cancelled.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 3
turn, mirror, upside down or transpose> rotate
Type turn, mirror, upside down or transpose.
turn, mirror, upside down or transpose>
Cancelled.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 1
rows (1 to 12)> 3
columns (1 to 12)> 3
1 2 3
8 9 4
7 6 5
Read back along the same spiral: 1 to 9 in order, each once.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 3
turn, mirror, upside down or transpose> turn
7 8 1
6 9 2
5 4 3
Not a magic square - and turning or flipping cannot make one.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 5
Bye.
What was typed (22 lines)
0
x
1
0
13
abc
3
2
4
17
1
3
rotate
1
3
3
3
turn
5
basic
interpreter: byte-equal== Matrix patterns ==
Spirals, magic squares, and grids turned and flipped.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 4
8 1 6
3 5 7
4 9 2
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 1
rows (1 to 12)> 4
columns (1 to 12)> 5
1 2 3 4 5
14 15 16 17 6
13 20 19 18 7
12 11 10 9 8
Read back along the same spiral: 1 to 20 in order, each once.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 1
rows (1 to 12)> 1
columns (1 to 12)> 7
1 2 3 4 5 6 7
Read back along the same spiral: 1 to 7 in order, each once.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 1
rows (1 to 12)> 5
columns (1 to 12)> 1
1
2
3
4
5
Read back along the same spiral: 1 to 5 in order, each once.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 2
odd size (3 to 15)> 5
17 24 1 8 15
23 5 7 14 16
4 6 13 20 22
10 12 19 21 3
11 18 25 2 9
rows 65 65 65 65 65
columns 65 65 65 65 65
diagonals 65 and 65
Every row, column and both diagonals add up to 65 = n(n^2 + 1)/2, with 1 to 25 each once: magic.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 3
turn, mirror, upside down or transpose> turn
11 10 4 23 17
18 12 6 5 24
25 19 13 7 1
2 21 20 14 8
9 3 22 16 15
Still magic: turning and flipping move whole rows, columns and diagonals.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 3
turn, mirror, upside down or transpose> mirror
17 23 4 10 11
24 5 6 12 18
1 7 13 19 25
8 14 20 21 2
15 16 22 3 9
Still magic: turning and flipping move whole rows, columns and diagonals.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 3
turn, mirror, upside down or transpose> transpose
17 24 1 8 15
23 5 7 14 16
4 6 13 20 22
10 12 19 21 3
11 18 25 2 9
Still magic: turning and flipping move whole rows, columns and diagonals.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 3
turn, mirror, upside down or transpose> upside down
11 18 25 2 9
10 12 19 21 3
4 6 13 20 22
23 5 7 14 16
17 24 1 8 15
Still magic: turning and flipping move whole rows, columns and diagonals.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 2
odd size (3 to 15)> 7
30 39 48 1 10 19 28
38 47 7 9 18 27 29
46 6 8 17 26 35 37
5 14 16 25 34 36 45
13 15 24 33 42 44 4
21 23 32 41 43 3 12
22 31 40 49 2 11 20
rows 175 175 175 175 175 175 175
columns 175 175 175 175 175 175 175
diagonals 175 and 175
Every row, column and both diagonals add up to 175 = n(n^2 + 1)/2, with 1 to 49 each once: magic.
1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit
choice> 5
Bye.
What was typed (23 lines)
4
1
4
5
1
1
7
1
5
1
2
5
3
turn
3
mirror
3
transpose
3
upside down
2
7
5
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P078 matrix patterns: a grid filled in a spiral and read back, an odd
# magic square by the Siamese method with all its sums, and the last grid
# turned and flipped - a magic square stays magic all eight ways.
import grids
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def number(s):
if len(s) == 0 or len(s) > 2:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def lpad(s, n):
while len(s) < n:
" " + s => s
return s
def ask(prompt, low, high):
while True:
trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> ")) => a
if a == "":
return -1
number(a) => n
if n >= low and n <= high:
return n
("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0
def show(g):
# Every column as wide as the widest number in the grid, plus a space.
1 => w
for row in g:
for x in row:
if len(str(x)) > w:
len(str(x)) => w
for row in g:
"" => line
for x in row:
line + lpad(str(x), w + 1) => line
line ^0
def joined(xs):
"" => s
for x in xs:
if s != "":
s + " " => s
s + str(x) => s
return s
def spiral(g):
ask("rows", 1, 12) => r
if r == -1:
"Cancelled." ^0
return g
ask("columns", 1, 12) => c
if c == -1:
"Cancelled." ^0
return g
grids.spiral_fill(r, c) => g
show(g)
grids.spiral_order(g) => path
True => inorder
for k in [0:len(path) - 1]:
if path[k] != k + 1:
False => inorder
if inorder:
("Read back along the same spiral: 1 to " + str(r * c) + " in order, each once.") ^0
else:
("Read back along the same spiral: " + joined(path)) ^0
return g
def square(g):
while True:
trim(input("odd size (3 to 15)> ")) => a
if a == "":
"Cancelled." ^0
return g
number(a) => n
if n >= 3 and n <= 15 and n % 2 == 1:
grids.magic(n) => g
show(g)
report(g)
return g
"Type an odd whole number from 3 to 15." ^0
def report(g):
len(g) => n
grids.sums(g) => s
("rows " + joined(s[0])) ^0
("columns " + joined(s[1])) ^0
("diagonals " + str(s[2]) + " and " + str(s[3])) ^0
int(n * (n * n + 1) / 2) => target
if grids.is_magic(g):
("Every row, column and both diagonals add up to " + str(target) + " = n(n^2 + 1)/2, with 1 to " + str(n * n) + " each once: magic.") ^0
else:
("Not magic: n(n^2 + 1)/2 = " + str(target) + " is not every sum, or a number is missing.") ^0
def move(g):
while True:
trim(input("turn, mirror, upside down or transpose> ")) => how
if how == "":
"Cancelled." ^0
return g
if how == "turn" or how == "mirror" or how == "upside down" or how == "transpose":
if how == "turn":
grids.turned(g) => g
elif how == "mirror":
grids.mirrored(g) => g
elif how == "upside down":
grids.upside_down(g) => g
else:
grids.transposed(g) => g
show(g)
if len(g) == len(g[0]) and len(g) >= 3 and len(g) % 2 == 1:
if grids.is_magic(g):
"Still magic: turning and flipping move whole rows, columns and diagonals." ^0
else:
"Not a magic square - and turning or flipping cannot make one." ^0
return g
"Type turn, mirror, upside down or transpose." ^0
grids.magic(3) => grid
"== Matrix patterns ==" ^0
"Spirals, magic squares, and grids turned and flipped." ^0
True => running
while running:
"" ^0
"1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
spiral(grid) => grid
elif choice == "2":
square(grid) => grid
elif choice == "3":
move(grid) => grid
elif choice == "4":
show(grid)
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import grids
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def number(s):
if len(s) == 0 or len(s) > 2:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def lpad(s, n):
while len(s) < n:
s = " " + s
return s
def ask(prompt, low, high):
while True:
a = trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> "))
if a == "":
return -1
n = number(a)
if n >= low and n <= high:
return n
print("Type a whole number from " + str(low) + " to " + str(high) + ".")
def show(g):
w = 1
for row in g:
for x in row:
if len(str(x)) > w:
w = len(str(x))
for row in g:
line = ""
for x in row:
line = line + lpad(str(x), w + 1)
print(line)
def joined(xs):
s = ""
for x in xs:
if s != "":
s = s + " "
s = s + str(x)
return s
def spiral(g):
r = ask("rows", 1, 12)
if r == -1:
print("Cancelled.")
return g
c = ask("columns", 1, 12)
if c == -1:
print("Cancelled.")
return g
g = grids.spiral_fill(r, c)
show(g)
path = grids.spiral_order(g)
inorder = True
for k in range(0, len(path)):
if path[k] != k + 1:
inorder = False
if inorder:
print("Read back along the same spiral: 1 to " + str(r * c) + " in order, each once.")
else:
print("Read back along the same spiral: " + joined(path))
return g
def square(g):
while True:
a = trim(input("odd size (3 to 15)> "))
if a == "":
print("Cancelled.")
return g
n = number(a)
if n >= 3 and n <= 15 and n % 2 == 1:
g = grids.magic(n)
show(g)
report(g)
return g
print("Type an odd whole number from 3 to 15.")
def report(g):
n = len(g)
s = grids.sums(g)
print("rows " + joined(s[0]))
print("columns " + joined(s[1]))
print("diagonals " + str(s[2]) + " and " + str(s[3]))
target = int(n * (n * n + 1) / 2)
if grids.is_magic(g):
print("Every row, column and both diagonals add up to " + str(target) + " = n(n^2 + 1)/2, with 1 to " + str(n * n) + " each once: magic.")
else:
print("Not magic: n(n^2 + 1)/2 = " + str(target) + " is not every sum, or a number is missing.")
def move(g):
while True:
how = trim(input("turn, mirror, upside down or transpose> "))
if how == "":
print("Cancelled.")
return g
if how == "turn" or how == "mirror" or how == "upside down" or how == "transpose":
if how == "turn":
g = grids.turned(g)
elif how == "mirror":
g = grids.mirrored(g)
elif how == "upside down":
g = grids.upside_down(g)
else:
g = grids.transposed(g)
show(g)
if len(g) == len(g[0]) and len(g) >= 3 and len(g) % 2 == 1:
if grids.is_magic(g):
print("Still magic: turning and flipping move whole rows, columns and diagonals.")
else:
print("Not a magic square - and turning or flipping cannot make one.")
return g
print("Type turn, mirror, upside down or transpose.")
grid = grids.magic(3)
print("== Matrix patterns ==")
print("Spirals, magic squares, and grids turned and flipped.")
running = True
while running:
print("")
print("1) spiral 2) magic square 3) turn or flip the last grid 4) show it 5) quit")
choice = trim(input("choice> "))
if choice == "1":
grid = spiral(grid)
elif choice == "2":
grid = square(grid)
elif choice == "3":
grid = move(grid)
elif choice == "4":
show(grid)
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
grids.eml
eml# P078 matrix patterns - a grid filled in a spiral and read back the same
# way, an odd magic square by the Siamese method with every sum checked,
# and the grid turned and flipped. A grid is a list of rows.
def blank(rows, cols):
# A rows x cols grid of zeros, each row its own list ([[0] * cols] *
# rows would make every row the same list).
[0] * rows => g
for r in [0:rows - 1]:
[0] * cols => g[r]
return g
def spiral_fill(rows, cols):
# 1, 2, 3, ... clockwise from the top left, turning inwards: along the
# top edge, down the right, back along the bottom, up the left, and the
# four edges move in by one.
blank(rows, cols) => g
1 => n
0 => top
rows - 1 => bottom
0 => left
cols - 1 => right
while top <= bottom and left <= right:
for c in [left:right]:
n => g[top][c]
n + 1 => n
top + 1 => top
for r in [top:bottom]:
n => g[r][right]
n + 1 => n
right - 1 => right
if top <= bottom:
for k in [0:right - left]:
n => g[bottom][right - k]
n + 1 => n
bottom - 1 => bottom
if left <= right:
for k in [0:bottom - top]:
n => g[bottom - k][left]
n + 1 => n
left + 1 => left
return g
def spiral_order(g):
# The corpus case spiral-matrix-traversal: read the same path. The two
# guards stop a last single row or column being read twice.
len(g) => rows
len(g[0]) => cols
[] => out
0 => top
rows - 1 => bottom
0 => left
cols - 1 => right
while top <= bottom and left <= right:
for c in [left:right]:
out + [g[top][c]] => out
top + 1 => top
for r in [top:bottom]:
out + [g[r][right]] => out
right - 1 => right
if top <= bottom:
for k in [0:right - left]:
out + [g[bottom][right - k]] => out
bottom - 1 => bottom
if left <= right:
for k in [0:bottom - top]:
out + [g[bottom - k][left]] => out
left + 1 => left
return out
def magic(n):
# The Siamese method for an odd n: 1 in the middle of the top row, then
# each next number one up and one right, wrapping round the edges; if
# that square is taken, one down instead.
blank(n, n) => g
0 => r
int((n - 1) / 2) => c
for k in [1:n * n]:
k => g[r][c]
(r - 1 + n) % n => up
(c + 1) % n => over
if g[up][over] != 0:
(r + 1) % n => r
else:
up => r
over => c
return g
def sums(g):
# [row sums, column sums, main diagonal, other diagonal].
len(g) => n
[0] * n => rs
[0] * n => cs
0 => d1
0 => d2
for r in [0:n - 1]:
for c in [0:n - 1]:
rs[r] + g[r][c] => rs[r]
cs[c] + g[r][c] => cs[c]
d1 + g[r][r] => d1
d2 + g[r][n - 1 - r] => d2
return [rs, cs, d1, d2]
def is_magic(g):
# Every row, column and both diagonals add up to n(n^2 + 1)/2, and the
# numbers are 1 to n^2 once each.
len(g) => n
int(n * (n * n + 1) / 2) => target
sums(g) => s
for x in s[0] + s[1] + [s[2], s[3]]:
if x != target:
return False
[0] * (n * n + 1) => seen
for row in g:
for x in row:
if x < 1 or x > n * n or seen[x] == 1:
return False
1 => seen[x]
return True
def turned(g):
# A quarter turn clockwise: row r becomes column (rows - 1 - r).
len(g) => rows
len(g[0]) => cols
blank(cols, rows) => out
for r in [0:rows - 1]:
for c in [0:cols - 1]:
g[r][c] => out[c][rows - 1 - r]
return out
def mirrored(g):
# Flipped left to right.
len(g) => rows
len(g[0]) => cols
blank(rows, cols) => out
for r in [0:rows - 1]:
for c in [0:cols - 1]:
g[r][c] => out[r][cols - 1 - c]
return out
def upside_down(g):
# Flipped top to bottom.
len(g) => rows
blank(rows, len(g[0])) => out
for r in [0:rows - 1]:
for c in [0:len(g[0]) - 1]:
g[r][c] => out[rows - 1 - r][c]
return out
def transposed(g):
# Rows become columns: flipped over the main diagonal.
blank(len(g[0]), len(g)) => out
for r in [0:len(g) - 1]:
for c in [0:len(g[0]) - 1]:
g[r][c] => out[c][r]
return out
Python projection (grids.py)
def blank(rows, cols):
g = [0] * rows
for r in range(0, rows):
g[r] = [0] * cols
return g
def spiral_fill(rows, cols):
g = blank(rows, cols)
n = 1
top = 0
bottom = rows - 1
left = 0
right = cols - 1
while top <= bottom and left <= right:
for c in range(left, right+1):
g[top][c] = n
n = n + 1
top = top + 1
for r in range(top, bottom+1):
g[r][right] = n
n = n + 1
right = right - 1
if top <= bottom:
for k in range(0, right - left+1):
g[bottom][right - k] = n
n = n + 1
bottom = bottom - 1
if left <= right:
for k in range(0, bottom - top+1):
g[bottom - k][left] = n
n = n + 1
left = left + 1
return g
def spiral_order(g):
rows = len(g)
cols = len(g[0])
out = []
top = 0
bottom = rows - 1
left = 0
right = cols - 1
while top <= bottom and left <= right:
for c in range(left, right+1):
out = out + [g[top][c]]
top = top + 1
for r in range(top, bottom+1):
out = out + [g[r][right]]
right = right - 1
if top <= bottom:
for k in range(0, right - left+1):
out = out + [g[bottom][right - k]]
bottom = bottom - 1
if left <= right:
for k in range(0, bottom - top+1):
out = out + [g[bottom - k][left]]
left = left + 1
return out
def magic(n):
g = blank(n, n)
r = 0
c = int((n - 1) / 2)
for k in range(1, n * n+1):
g[r][c] = k
up = (r - 1 + n) % n
over = (c + 1) % n
if g[up][over] != 0:
r = (r + 1) % n
else:
r = up
c = over
return g
def sums(g):
n = len(g)
rs = [0] * n
cs = [0] * n
d1 = 0
d2 = 0
for r in range(0, n):
for c in range(0, n):
rs[r] = rs[r] + g[r][c]
cs[c] = cs[c] + g[r][c]
d1 = d1 + g[r][r]
d2 = d2 + g[r][n - 1 - r]
return [rs, cs, d1, d2]
def is_magic(g):
n = len(g)
target = int(n * (n * n + 1) / 2)
s = sums(g)
for x in s[0] + s[1] + [s[2], s[3]]:
if x != target:
return False
seen = [0] * (n * n + 1)
for row in g:
for x in row:
if x < 1 or x > n * n or seen[x] == 1:
return False
seen[x] = 1
return True
def turned(g):
rows = len(g)
cols = len(g[0])
out = blank(cols, rows)
for r in range(0, rows):
for c in range(0, cols):
out[c][rows - 1 - r] = g[r][c]
return out
def mirrored(g):
rows = len(g)
cols = len(g[0])
out = blank(rows, cols)
for r in range(0, rows):
for c in range(0, cols):
out[r][cols - 1 - c] = g[r][c]
return out
def upside_down(g):
rows = len(g)
out = blank(rows, len(g[0]))
for r in range(0, rows):
for c in range(0, len(g[0])):
out[rows - 1 - r][c] = g[r][c]
return out
def transposed(g):
out = blank(len(g[0]), len(g))
for r in range(0, len(g)):
for c in range(0, len(g[0])):
out[c][r] = g[r][c]
return out