Finding the largest Magic Square in a matrix

Introduction

The Magic Square problem is quite a popular one when it comes to matrix manipulation interview problems. However, not too long ago I encountered a version of this problem that didn't let me sleep at night.

The Problem

In a matrix of size N x N find the largest Magic Square. If you don't know what a Magic Square is basically it is a square where all the rows, columns, and diagonals of length N equal the same number.

For example a square with only ones could be a Magic Square because no matter what way you count you are adding the same amount of ones. Here is a link to a program that generates Magic Squares

The Solution

I am sure there are multiple ways to solve this problem more efficiently, but I couldn't find them. I scavenged the internet to find a dynamic solution for this problem with no luck, so I resorted to the next best thing.

The solution that I came up with uses threads to find all squares of size k x k in the matrix of N x N. Then, it uses checks every sub-square of size k x k in the matrix. If the square is a Magic Square it's sum gets added to memory. At the end the program finds the largest sum that got saved, hence finding the largest Magic Square.

How to use

At the bottom there are a few different matrices of different sizes. Feel free to play around with them to test different inputs. If you dont feel like manually changing the matrices, you can randomly generate a matrix of size N x N with mat4.

mat4 is already set up, all you need to do is specify the range of the numbers you want in the matrix and the size. Remember it has to be a square matrix, so choose equal numbers for the sizes.

import logging
import threading
import numpy as np




def is_magic_square_of_size(matr, x):

    # Columns
    columns_sum = sum(matr[0])
    for i in range(1, x):
        new_sum = sum(matr[i])

        if new_sum != columns_sum:
            return "False", 0

    # Rows
    row_sum = 0
    for i in range(x):
        new_sum = 0
        for j in range(x):
            if (i < 1):
                row_sum += matr[j][i]
                new_sum += matr[j][i]
            else:
                new_sum += matr[j][i]
        if (new_sum != row_sum):
            return "False", 0

    # Diagonals
    left_dig = 0
    right_dig = 0
    for i in range(1,3):
        for j in range(x):
            if(i == 1):
                left_dig += matr[j][j]
            if(i ==2):
                right_dig += matr[x - 1 - j][j]

    if (left_dig != right_dig):
        return "False", 0
    elif(left_dig != 0):
        print("True", matr)
        return "True", left_dig
    else:
        return "False", 0

def creat_squares_of_size(x, matr):
    squares = []

    for i in range(0,(len(matr) - x) + 1):
        temp = []
        for j in range(0,(len(matr) - x)+ 1 ):

            for p in range(0,x):
                temp2 = []
                for q in range(0,x):
                    temp2.append(matr[p + i][q + j])
                temp.append(temp2)
            squares.append(temp)
            temp = []
    #print(squares)
    return squares


def thread_function(size_of_square, matrix):
    squares = creat_squares_of_size(size_of_square, matrix)
    sizes = []

    for square in squares:
        isSquare, sum = is_magic_square_of_size(square, size_of_square)
        if (isSquare == "True"):
            sizes.append(sum)

    if(len(sizes) > 0):
        print("Max magic sqaure of size", size_of_square, "has sum of", max(sizes))
        max_size.append(max(sizes))





if __name__ == "__main__":
    format = "%(asctime)s: %(message)s"
    logging.basicConfig(format=format, level=logging.INFO,
                        datefmt="%H:%M:%S")

    mat = [[2, 7, 6, 1],
           [9, 5, 1, 1],
           [4, 3, 8, 1],
           [4, 3, 8, 1]]

    mat2 = [[8,2,4,3,7,4,3,5,3,4],
            [10,7,5,5,9,3,10,1,4,5],
            [10,6,64,2,3,61,60,6,7,57],
            [3,8,9,55,54,12,13,51,50,16],
            [20,5,17,47,46,20,21,43,42,24],
            [9,4,40,26,27,37,36,30,31,33],
            [7,3,32,34,35,29,28,38,39,25],
            [9,7, 41,23,22,44,45,19,18,48],
            [6,4,49,15,14,52,53,11,10,56],
            [6,9,8,58,59,5,4,62,63,1]]

    mat3= [
        [14,5,5,8,8,14,11,3,8,15,2,11,6,8,5,8,2,9,14,5],
        [14,3,3,5,10,1,15,8,12,12,13,2,5,1,3,6,13,4,11,2],
        [4,9,1,6,9,9,12,2,7,7,10,14,5,14,5,12,8,12,3,10],
        [7,4,8,12,7,9,5,15,2,4,5,5,5,7,1,11,2,15,7,12],
        [11,10,8,8,4,9,8,12,5,3,13,5,1,1,8,15,13,5,4,6],
        [5,2,7,2,15,15,6,15,12,12,7,4,5,4,3,12,11,9,12,5],
        [4,4,4,4,4,14,4,2,7,9,12,2,5,5,6,8,4,2,11,7],
        [4,4,4,4,4,14,4,12,4,15,10,11,12,3,14,8,15,11,14,1],
        [4,4,4,4,13,8,11,11,1,11,15,15,4,8,8,4,11,13,7,2],
        [4,4,4,4,2,15,5,5,14,13,7,6,5,1,15,6,1,4,13,2],
        [6,2,10,6,13,1,1,12,9,6,12,12,14,3,15,7,3,1,2,4],
        [8,13,6,13,8,11,2,11,3,6,15,4,8,6,9,10,2,12,8,15],
        [1,3,4,6,6,14,6,3,9,14,6,8,2,10,2,12,13,2,3,4],
        [11,13,5,8,3,11,9,13,11,8,3,15,3,6,12,8,7,13,8,11],
        [7,11,9,12,9,15,9,10,8,14,4,7,2,14,6,10,12,5,12,7],
        [3,12,11,4,11,3,10,15,14,10,2,15,2,4,4,3,9,7,5,5],
        [8,1,5,6,15,15,13,9,12,14,10,12,2,4,4,4,4,9,12,3],
        [3,9,1,3,11,13,10,12,3,3,14,9,12,3,12,4,7,3,1,14],
        [15,8,9,13,5,13,3,10,14,13,1,15,1,5,13,14,12,1,10,13],
        [9,2,11,2,11,10,2,3,12,14,9,7,5,5,15,3,6,9,3,6]
    ]

    mat4 = np.random.randint(0, 5, size=(10, 10))
    #thread_function(3,mat)
    max_size = []

    threads = list()
    for index in range(2,len(mat4)):
        print("Main    : create and start thread %d.", index)
        x = threading.Thread(target=thread_function, args=(index,mat4))
        threads.append(x)
        x.start()

    for index, thread in enumerate(threads):
        logging.info("Main    : before joining thread %d.", index)
        thread.join()
        logging.info("Main    : thread %d done", index)

    print("Max sum for all magic sqaures is", max(max_size), max_size)
    print(mat4)

© 2021 Tecnance • by Juan Suarez