Nice problem! Here is a brute force solution. First we define some convenient helper functions:
import itertools
def digit_matrix_to_digit_positions(M):
positions = { d: [] for d in range(0,10)}
for i in range(0, M.nrows()):
for j in range(0, M.ncols()):
positions[M[i,j]].append((i,j))
return positions
def digit_paths_for_number(digit_positions, number):
digits = map(int, str(number))
return itertools.product(*[digit_positions[d] for d in digits])
def is_digit_path_allowed(path):
if len(path) == 0: return False
if len(path) == 1: return True
# determine direction of path
first, second = path[0], path[1]
direction = (second[0]-first[0], second[1]-first[1])
# check if direction is valid
if not all(d in [-1,0,1] for d in direction): return False
# check if the rest of the path goes in this direction
if len(path) == 2: return True
return all((path[i][0]-path[i-1][0], path[i][1]-path[i-1][1]) == direction for i in range(2, len(path)))
def digit_matrix_contains_numbers(M, numbers):
digit_positions = digit_matrix_to_digit_positions(M)
for number in numbers:
if len(filter(is_digit_path_allowed, digit_paths_for_number(digit_positions, number))) == 0:
return False
return True
Now we can e.g. verify your example:
sage: M = Matrix(ZZ, [[0,0,1,8,2], [3,6,4,9,5]])
sage: digit_matrix_contains_numbers(M, [x^2 for x in range(0, 11)])
True
sage: filter(is_digit_path_allowed, digit_paths_for_number(digit_matrix_to_digit_positions(M), 100))
[((0, 2), (0, 1), (0, 0))]
To find the smallest digit matrix with this property, we iterate over $n\times m$ digit matrices with $N=nm \geq 9$:
numbers = [x^2 for x in range(0, 11)]
needed_digits = set(sum((map(int,str(y)) for y in numbers), []))
found = False
N = len(needed_digits)
while True:
for n in divisors(N):
m = N/n
if m < n: continue # equivalent to m x n anyway
print 'Trying', n, 'x', m
for matrix_entries in itertools.product(range(0,10), repeat=n*m):
if len(needed_digits & set(matrix_entries)) < len(needed_digits): continue
matrix = Matrix(ZZ, [[matrix_entries[i*m+j] for j in range(0,m)] for i in range(0,n)])
if digit_matrix_contains_numbers(matrix, numbers):
found = True
print 'Found one!'
print matrix
break
if found: break
if found: break
N += 1
The output after several hours is:
Trying 1 x 9
Trying 3 x 3
Trying 1 x 10
Trying 2 x 5
Found one!
[0 0 1 3 2]
[9 4 6 8 5]
So $2 \times 5$ (or $5 \times 2$ after transposition) is minimal. You can also let the code print all the solutions, if you want.
It would be interesting to see other approaches.
General note : this marvelous book has a nice set of discussions about various optimization problems...