CC-251003 Science des données

Notebook originel: cc251003_magic_sol.ipynb

CC-251003 Science des données🔗

Date: 03 octobre 2025
Durée: 1 h

Consignes🔗

Créez un répertoire CC251003_Nom_Prenom/ dans lequel vous travaillerez, et où sera stocké le package python en développement, selon les instructions ci-dessous.

Vous devez alors mettre ce répertoire sous contrôle git sur un projet dédié sur le serveur https://gitlab.in2p3.fr, et en transmettre l’adresse à y.copin@ipnl.in2p3.fr.

Si, par manque de temps ou de connaissance, vous ne pouvez pas créer un dépôt git, envoyez par mail la distribution source du package voir ci-dessous) à y.copin@ipnl.in2p3.fr (cette solution sera pénalisée dans la notation).

Dans tous les cas, vous devez envoyer quelque chose!

Carré magique🔗

Un carré magique d’ordre n est un tableau carré \(n × n\) dans lequel on écrit une et une seule fois les nombres entiers de 1 à \(n^2\), de sorte que la somme des n nombres de chaque ligne, colonne ou diagonale principale soit constante.

Pour les carrés magiques d’ordre impair, on dispose de l’algorithme suivant — (i,j) désignant la case de la ligne i, colonne j du carré, avec une indexation « naturelle » commençant à 1:

  • initialisation: la case \((n, (n + 1)/2)\) contient 1 ;

  • itération: si la case \((i, j)\) contient la valeur \(k\), alors on place la valeur \(k+1\) dans la case \((i + 1, j + 1)\) si cette case est vide, ou dans la case \((i - 1, j)\) sinon. On respecte la règle du carré « torique » (modulo n).

  1. Écrire une fonction empty_magic_square(n) qui retourne un tableau 2D (liste de listes) \(n×n\) de 0, en utilisant une double liste en compréhension.

[1]:
def empty_magic_square(n):
    """
    Retourne un tableau 2D (liste de listes) n×n de 0.

    Utiliser une double liste en compréhension.
    """

    return [ [ 0 for i in range(n) ] for j in range(n) ]
[2]:
n = 5
sq = empty_magic_square(n)
sq
[2]:
[[0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0]]
  1. Écrire une fonction init_magic_square(square) qui initialise l’algorithme en remplissant (sur place) la case 1 du tableau square (supposé être \(n×n\)), et en retournant les coordonnées de cette case (indexation à 1).

[3]:
def init_magic_square(square):
    """
    Initialise l'algorithme en remplissant (sur place) la case '1',
    et en retournant les coordonnées de la case (indexation à 1).

    square est supposé être un tableau n×n.
    """

    n = len(square)

    i, j = n, (n + 1) // 2
    square[i - 1][j - 1] = 1

    return i, j
[4]:
i, j = init_magic_square(sq)
sq, i, j
[4]:
([[0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 1, 0, 0]],
 5,
 3)
  1. Écrire une fonction iterate_magic_square(square, i, j, k) qui itère l’algorithme en remplissant (sur place) la case \(k+1\) du tableau square (supposé être \(n×n\)), et en retournant les coordonnées de cette case (indexation à 1), en supposant que la case \(k\), de coordonnées \((i, j)\), est déjà remplie.

[5]:
def iterate_magic_square(square, i, j, k):
    """
    Itère l'algorithme en remplissant (sur place) la case 'k+1',
    et en retournant les coordonnées de la case (indexation à 1),
    en supposant que la case 'k', de coordonnées (i, j), est déjà remplie.

    square est supposé être un tableau n×n.
    """

    n = len(square)

    i2, j2 = (i + 1) % n, (j + 1) % n  # Default step (i, j) → (i + 1, j+ 1) [mod n]
    if square[i2 - 1][j2 - 1]:         # Cell is not empty, use alternative step
        i2 = (i - 1) % n
        j2 = j
    square[i2 - 1][j2 - 1] = k + 1

    return i2, j2
[6]:
for k in range(1, n**2):
    i, j = iterate_magic_square(sq, i, j, k)
    print(k, i, j, sq)
1 1 4 [[0, 0, 0, 2, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0]]
2 2 0 [[0, 0, 0, 2, 0], [0, 0, 0, 0, 3], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0]]
3 3 1 [[0, 0, 0, 2, 0], [0, 0, 0, 0, 3], [4, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0]]
4 4 2 [[0, 0, 0, 2, 0], [0, 0, 0, 0, 3], [4, 0, 0, 0, 0], [0, 5, 0, 0, 0], [0, 0, 1, 0, 0]]
5 3 2 [[0, 0, 0, 2, 0], [0, 0, 0, 0, 3], [4, 6, 0, 0, 0], [0, 5, 0, 0, 0], [0, 0, 1, 0, 0]]
6 4 3 [[0, 0, 0, 2, 0], [0, 0, 0, 0, 3], [4, 6, 0, 0, 0], [0, 5, 7, 0, 0], [0, 0, 1, 0, 0]]
7 0 4 [[0, 0, 0, 2, 0], [0, 0, 0, 0, 3], [4, 6, 0, 0, 0], [0, 5, 7, 0, 0], [0, 0, 1, 8, 0]]
8 1 0 [[0, 0, 0, 2, 9], [0, 0, 0, 0, 3], [4, 6, 0, 0, 0], [0, 5, 7, 0, 0], [0, 0, 1, 8, 0]]
9 2 1 [[0, 0, 0, 2, 9], [10, 0, 0, 0, 3], [4, 6, 0, 0, 0], [0, 5, 7, 0, 0], [0, 0, 1, 8, 0]]
10 1 1 [[11, 0, 0, 2, 9], [10, 0, 0, 0, 3], [4, 6, 0, 0, 0], [0, 5, 7, 0, 0], [0, 0, 1, 8, 0]]
11 2 2 [[11, 0, 0, 2, 9], [10, 12, 0, 0, 3], [4, 6, 0, 0, 0], [0, 5, 7, 0, 0], [0, 0, 1, 8, 0]]
12 3 3 [[11, 0, 0, 2, 9], [10, 12, 0, 0, 3], [4, 6, 13, 0, 0], [0, 5, 7, 0, 0], [0, 0, 1, 8, 0]]
13 4 4 [[11, 0, 0, 2, 9], [10, 12, 0, 0, 3], [4, 6, 13, 0, 0], [0, 5, 7, 14, 0], [0, 0, 1, 8, 0]]
14 0 0 [[11, 0, 0, 2, 9], [10, 12, 0, 0, 3], [4, 6, 13, 0, 0], [0, 5, 7, 14, 0], [0, 0, 1, 8, 15]]
15 4 0 [[11, 0, 0, 2, 9], [10, 12, 0, 0, 3], [4, 6, 13, 0, 0], [0, 5, 7, 14, 16], [0, 0, 1, 8, 15]]
16 0 1 [[11, 0, 0, 2, 9], [10, 12, 0, 0, 3], [4, 6, 13, 0, 0], [0, 5, 7, 14, 16], [17, 0, 1, 8, 15]]
17 1 2 [[11, 18, 0, 2, 9], [10, 12, 0, 0, 3], [4, 6, 13, 0, 0], [0, 5, 7, 14, 16], [17, 0, 1, 8, 15]]
18 2 3 [[11, 18, 0, 2, 9], [10, 12, 19, 0, 3], [4, 6, 13, 0, 0], [0, 5, 7, 14, 16], [17, 0, 1, 8, 15]]
19 3 4 [[11, 18, 0, 2, 9], [10, 12, 19, 0, 3], [4, 6, 13, 20, 0], [0, 5, 7, 14, 16], [17, 0, 1, 8, 15]]
20 2 4 [[11, 18, 0, 2, 9], [10, 12, 19, 21, 3], [4, 6, 13, 20, 0], [0, 5, 7, 14, 16], [17, 0, 1, 8, 15]]
21 3 0 [[11, 18, 0, 2, 9], [10, 12, 19, 21, 3], [4, 6, 13, 20, 22], [0, 5, 7, 14, 16], [17, 0, 1, 8, 15]]
22 4 1 [[11, 18, 0, 2, 9], [10, 12, 19, 21, 3], [4, 6, 13, 20, 22], [23, 5, 7, 14, 16], [17, 0, 1, 8, 15]]
23 0 2 [[11, 18, 0, 2, 9], [10, 12, 19, 21, 3], [4, 6, 13, 20, 22], [23, 5, 7, 14, 16], [17, 24, 1, 8, 15]]
24 1 3 [[11, 18, 25, 2, 9], [10, 12, 19, 21, 3], [4, 6, 13, 20, 22], [23, 5, 7, 14, 16], [17, 24, 1, 8, 15]]
[7]:
sq
[7]:
[[11, 18, 25, 2, 9],
 [10, 12, 19, 21, 3],
 [4, 6, 13, 20, 22],
 [23, 5, 7, 14, 16],
 [17, 24, 1, 8, 15]]
  1. Écrire une fonction magic_square(n) qui implémente l’algorithme en retournant le carré magique d’ordre \(n\) (tableau \(n×n\)). Elle doit générer une exception AssertionError si l’ordre \(n\) n’est pas impair.

[8]:
def magic_square(n):
    """
    Implémentation de l'algo.

    * vérifier que l'ordre est impair (assert)
    * retourner le carré magique totalement rempli (tableau n×n)
    """

    assert n % 2 == 1, f"{n=} is not odd."

    sq = empty_magic_square(n)
    i, j = init_magic_square(sq)
    for k in range(1, n**2):
        i, j = iterate_magic_square(sq, i, j, k)
    return sq
[9]:
magic_square(n)
[9]:
[[11, 18, 25, 2, 9],
 [10, 12, 19, 21, 3],
 [4, 6, 13, 20, 22],
 [23, 5, 7, 14, 16],
 [17, 24, 1, 8, 15]]
  1. Écrire une fonction str_magic_square(n) convertissant un carré magique en une chaîne de caractères correctement formattée, p.ex.

>>> print(str_magic_square(5))
11 18 25  2  9
10 12 19 21  3
 4  6 13 20 22
23  5  7 14 16
17 24  1  8 15
[10]:
def str_magic_square(square):
    """
    Retourne une représentation str du carré magique.

    Utiliser la méthode str.join pour construire la chaîne
    à partir des lignes et des cellules, et
    f"{val:>{m}d}" pour afficher l'entier val aligné
    à droite sur m caractères.
    """

    n = len(square)
    m = len(str(n**2)) # Max length of a cell

    return "\n".join([ " ".join( f"{val:>{m}d}" for val in row ) for row in square ])
[11]:
print(str_magic_square(magic_square(5)))
11 18 25  2  9
10 12 19 21  3
 4  6 13 20 22
23  5  7 14 16
17 24  1  8 15

Packaging🔗

  1. Créez un package magic_nom_prenom constitué du seul module magic.py incluant l’ensemble des fonctions précédemment définies (ne pas ajouter la section de tests), avec l’arborescence suivante:

    CC251003_Nom_Prenom/
    ├── magic_nom_prenom
    │   ├── magic.py
    │   └── __init__.py
    ├── pyproject.toml
    └── ...
    
  2. En utilisant les fonctions définies à la partie précédente, ajoutez un fichier __main__.py à votre package pour pouvoir exécuter:

    $ python -m magic 5
    11 18 25  2  9
    10 12 19 21  3
     4  6 13 20 22
    23  5  7 14 16
    17 24  1  8 15
    
  3. Ajoutez un point d’entrée magicsquare à votre package pour pouvoir exécuter:

    $ magicsquare 5
    11 18 25  2  9
    10 12 19 21  3
     4  6 13 20 22
    23  5  7 14 16
    17 24  1  8 15
    

    (Vous pouvez créer de nouvelles fonctions intermédiaires si nécessaire.)

  4. Finalement, commit/push toutes vos modifications sur votre répo central git, et envoyer l’adresse de ce répo à y.copin@ipnl.in2p3.fr.

    Si vous n’avez pas réussi à stocker votre package sous git, générer une distribution des sources de votre package avec la commande

    $ python -m build
    

    et envoyer l’archive correspondante (générée sous dist/) à y.copin@ipnl.in2p3.fr.

    Si vous n’avez pas non plus réussi à configurer votre package, générer manuellement une archive de votre package:

    $ tar cvzf CC251003_NOM_Prenom.tgz CC251003_NOM_Prenom/
    

    et envoyer le tarball.

[12]:
import unittest

class TestNotebook(unittest.TestCase):

    def test_01_empty(self):
        self.assertEqual(empty_magic_square(3), [[0] * 3] * 3)

    def test_02_init(self):
        sq = empty_magic_square(3)
        i, j = init_magic_square(sq)
        self.assertEqual((i, j), (3, 2))
        self.assertEqual(sq, [[0, 0, 0], [0, 0, 0], [0, 1, 0]])

    def test_03_iterate(self):
        sq = [[0, 0, 0], [0, 0, 0], [0, 1, 0]]
        i, j = iterate_magic_square(sq, 3, 2, 1)
        self.assertEqual((i, j), (1, 0))
        self.assertEqual(sq, [[0, 0, 2], [0, 0, 0], [0, 1, 0]])
        i, j = iterate_magic_square(sq, 1, 0, 2)
        self.assertEqual((i, j), (2, 1))
        self.assertEqual(sq, [[0, 0, 2], [3, 0, 0], [0, 1, 0]])

    def test_04_all(self):
        self.assertEqual(magic_square(3), [[4, 9, 2], [3, 5, 7], [8, 1, 6]])
        with self.assertRaises(AssertionError):
            magic_square(4)

    def test_05_str(self):
        self.assertEqual(
            str_magic_square(magic_square(3)),
            '4 9 2\n3 5 7\n8 1 6')
        self.assertEqual(
            str_magic_square(magic_square(5)),
            '11 18 25  2  9\n10 12 19 21  3\n 4  6 13 20 22\n23  5  7 14 16\n17 24  1  8 15')

unittest.main(argv=[''], verbosity=2, exit=False)
test_01_empty (__main__.TestNotebook) ... ok
test_02_init (__main__.TestNotebook) ... ok
test_03_iterate (__main__.TestNotebook) ... ok
test_04_all (__main__.TestNotebook) ... ok
test_05_str (__main__.TestNotebook) ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.003s

OK
[12]:
<unittest.main.TestProgram at 0x7fb1b9efe6b0>

Cette page a été générée à partir de cc251003_magic_sol.ipynb.