Что это дает? Очень многое. Вместо перебора 16 вариантов суммарным количеством 16!=20922789888000 мы должны перебрать лишь 9 вариантов, что дает 9!=362880 вариантов, т.е. в 57657600 раз меньше! Как нетрудно догадаться, мы фактически выразили крайние строки квадрата через соседние, т.е. уменьшили размерность поиска с 4х4 до 3х3. Это же правило будет действовать и для квадратов большей диагонали.
Обновленная программа выглядит более громоздко (в ней также добавлены проверки на ненулевые значения и проверки на уникальность элементов), зато расчет происходит в разы быстрее. Здесь также используется возможность работы со множествами в языке Python, что легко позволяет делать перебор нужных цифр в цикле:
set(range(1,16+1)) - множество чисел [1..16]
set(range(1,16+1)) - set([x11]) - множество чисел [1..16] за исключением x11.
Также добавлена простая проверка на минимальность суммы: очевидно, что сумма всех элементов не может быть меньше чем 16+1+2+3 = 22.
digits = set(range(1,16+1))
cnt = 0
for x11 in digits:
for x12 in digits - set([x11]):
for x13 in digits - set([x11, x12]):
for x14 in digits - set([x11, x12, x13]):
s = x11 + x12 + x13 + x14
if s < 22: continue
for x21 in digits - set([x11, x12, x13, x14]):
for x22 in digits - set([x11, x12, x13, x14, x21]):
for x23 in digits - set([x11, x12, x13, x14, x21, x22]):
x24 = s - x21 - x22 - x23
if x24 <= 0 or x24 in [x11, x12, x13, x14, x21, x22, x23]: continue
for x31 in digits - set([x11, x12, x13, x14, x21, x22, x23, x24]):
for x32 in digits - set([x11, x12, x13, x14, x21, x22, x23, x24, x31]):
for x33 in digits - set([x11, x12, x13, x14, x21, x22, x23, x24, x31, x32]):
x34 = s - x31 - x32 - x33
x41 = s - x11 - x21 - x31
x42 = s - x12 - x22 - x32
x43 = s - x13 - x23 - x33
x44 = s - x14 - x24 - x34
if x34 <= 0 or x41 <= 0 or x42 <= 0 or x43 <= 0 or x44 <= 0: continue
data = [x11, x12, x13, x14, x21, x22, x23, x24, x31, x32, x33, x34, x41, x42, x43, x44]
if len(data) != len(set(data)): continue
if is_magic(data, 4):
print data