Quand c'est carré et c'est magique... c'est un carré magique - Solution

Quand c'est carré et c'est magique... c'est un carré magique - Solution

On se retrouve aujourd'hui pour la solution du précédent #KataOfTheWeek proposé par Yann en début de semaine !

La logique de résolution se base sur le backtracking : on analyse l'ensemble des combinaisons possibles.

Le diagramme d'action associé :

diagramme d'action

Une implémentation python pour toute taille de matrices :

SIZE = 3
matrix = [[0 for i in range(SIZE)] for k in range(SIZE)] #Fill matrix with 0
current_i = 0
current_j = 0

remaining = [i for i in range(1, SIZE * SIZE + 1)]
tested = []
put = []
MAGIC_SUM = sum(remaining) / SIZE #Expected sum for lines/columns and diagonals

def run():
    global matrix

    if not remaining:
        previous_square()
        reset_left()
    else:
        put_remaining_value()
        if magic_broken():
            remove_value()
            if not remaining:
                previous_square()
                reset_left()
        else:
            next_square()
            reset_right()


def reset_left():
    """Reset tested/remaining values"""

    global tested, remaining
    current_val = matrix[current_i][current_j]
    remove_value()
    tested = [i for i in range(1, current_val + 1)]
    remaining = [i for i in range(1, SIZE * SIZE + 1) if i not in tested and i not in put]


def reset_right():
    """Reset tested/remaining values"""

    global tested, remaining
    tested = []
    remaining = [i for i in range(1, SIZE * SIZE + 1) if i not in tested and i not in put]


def remove_value():
    """Remove current placed value"""

    global tested, remaining
    val = matrix[current_i][current_j]
    matrix[current_i][current_j] = 0
    tested.append(val)
    put.remove(val)


def put_remaining_value():
    """Put in current position next available value"""

    global matrix
    val = remaining.pop(0)
    put.append(val)
    matrix[current_i][current_j] = val


def magic_broken():
    """Check if the magic square is broken (ie: any addition > MAGIC_SUM)"""

    # Return true if any line, column or diagonal is broken
    return len([line for line in matrix if direction_broken(line)]) != 0 \
           or direction_broken([matrix[i][i] for i in range(SIZE)]) \
           or direction_broken([matrix[i][SIZE - 1 - i] for i in range(SIZE)]) \
           or True in [direction_broken([matrix[i][j] for i in range(SIZE)]) for j in range(SIZE)]


def direction_broken(elements: list):
    """Check if the magic is broken for direction (line/col/diag)"""
    total = sum(elements)
    if total > MAGIC_SUM:
        return True
    return (total == MAGIC_SUM) == (0 in elements)


def previous_square():
    """Move to the previous square"""

    global current_i, current_j
    current_j -= 1
    if current_j < 0:
        current_j = SIZE - 1
        current_i -= 1


def next_square():
    """Move to the next square"""

    global current_i, current_j
    current_j += 1
    if current_j >= SIZE:
        current_j = 0
        current_i += 1


def display():
    for i in range(SIZE):
        print(matrix[i])


if __name__ == '__main__':
    while matrix[-1][-1] == 0:  # While last square is empty
        run()
    display()

A bientôt pour un nouveau #KataOfTheWeek !