x32, x33, x34, x41, x42, x43, x44]
if len(data) != len(set(data)): continue
if is_magic(data, 4):
print data
cnt += 1
print cnt
В результате, программа проработала всего лишь около часа (вместо 3х лет!), всего было выведено 7040 квадратов размерностью 4х4. Разумеется, большинство из них являются поворотами или отражениями друг друга. Было доказано что уникальных квадратов всего 880.
Кстати, приведенную выше программу можно еще оптимизировать.
Во-первых, заменим операции поиска во множестве вида if x21 in set([x11, x12, x13]) прямым сравнением if x21 != x11 and x21 != x12… Программа получается немного более громоздкой, зато такие операции выполняются быстрее - программа работает быстрее примерно на 10%.
Во-вторых, как можно увидеть, операции со множествами выполняются в цикле множество раз. Например, в коде
for x11 in digits:
for x12 in digits - {x11}:
for x13 in digits - {x11, x12}:
каждый раз формируется множество digits - {x11}, затем digits - {x11, x12} и т.д. Если вынести множества в отдельные переменные, число операций можно сократить, хотя это и делает код более громоздким:
for x11 in digits:
s11 = digits - {x11}
for x12 in s11:
s12 = s11 - {x12}
for x13 in s12:
При таком способе, на каждом шаге из множества вычитается только одна новая цифра, а не все заново.
И наконец, как было показано выше, сумма чисел магического квадрата может быть вычислена заранее по известной формуле - для N=4 она равна 34. Это позволяет избавиться от цикла по x14, заменив его простым выражением x14 = s - x11 - x12 - x13, что ускорит программу примерно в 10 раз. В итоге наших действий, программа работает всего лишь несколько минут вместо часа, прирост скорости весьма заметный.
Полный текст программы можно найти в архиве с файлами под названием “07 - magic4.py”.
Вспомним теперь магический квадрат Дюрера, в его строке есть цифры 1514, соответствующие году создания гравюры. С помощью программы можно решить еще одну задачу: посмотреть сколько всего возможно квадратов с такими цифрами. Здесь число вариантов перебора меньше, т.к. еще 2 цифры фиксированы. Оказывается, помимо “авторского”, возможны всего 32 варианта, например:
1 15 14 4 2 15 14 3
5 11 8 10 5 10 7 12
12 6 9 7 11 8 9 6
16 2 3 13 16 1 4 13
Интересно, что верхний ряд помимо цифр 15 и 14 может содержать либо 1,4 либо 2,3, других вариантов нет. Разные варианты содержат лишь перестановки этих цифр.
Если же говорить о квадратах большей размерности, то число вариантов перебора для них получается слишком большим. Так для квадрата 5х5, даже если выразить крайние члены через соседние, получаем 4х4 остающихся клеток, что даст нам те же самые 16! вариантов перебора. В архиве с книгой приложен файл “07 - magic5.c” для расчета квадратов 5х5, но автору так и не хватило терпения дождаться всех результатов. Заметно ускорить вычисления можно, лишь перенеся вычисления на GPU, об этом будет рассказано отдельно.
В качестве примера алгоритма построения квадратов любой нечетной размерности без полного перебора, можно назвать так называемый сиамский метод, известный уже с 17 века. Его описание можно найти в Википедии, пример программы на языке Python можно найти в приложении к книге.
Для N=9, программа сгенерировала следующий квадрат:
47 58 69 80 1 12 23 34 45
57 68 79 9 11 22 33 44 46
67 78 8 10 21 32 43 54 56
77 7 18 20 31 42 53 55 66
6 17 19 30 41 52 63 65 76
16 27 29 40 51 62 64 75 5
26 28 39 50 61 72 74 4 15
36 38 49 60 71 73 3 14 25
37 48 59 70 81 2 13 24 35
Существует множество других алгоритмов построения, например метод Франклина, Россера, Рауз-Болла, желающие могут поискать их самостоятельно.
И наконец, можно вспомнить так называемые “пандиагональные” магические квадраты. Это квадраты, в которых учитываются суммы также “косых” диагоналей, которые получаются если вырезать квадрат из бумаги и склеить его в тор. Желающие могут добавить в программу вывод таких квадратов самостоятельно.
8. Магический квадрат из простых чисел
Существует еще одна разновидность магического квадрата - составленного из простых чисел. Пример такого квадрата показан на рисунке:
29
131
107
167
89
11
71
47
149
Приведенную выше программу легко модифицировать для такого расчета: достаточно лишь заменить множество digits = set(range(1,16+1)) на другое, содержащее простые числа.