Rapport : Killer sudoku solver

Table des matières

1. Introduction

1.1. Principe de focntionnment

Url du challenge : https://www.codingame.com/ide/puzzle/killer-sudoku-solver

L’objectif de ce challenge est de résoudre une grille de killer sudoku.

Rappel des règles du sudoku :

  • Chaque ligne doit contenir les chiffres de 1 à 9
  • Chaque colonne doit contenir les chiffres de 1 à 9
  • Chaque carré de 3x3 doit contenir les chiffres de 1 à 9

A cela s’ajoute les contraintes du killer sudoku :

  • La somme des cases d’une cage doit être égale à la valeur indiquée dans la case en haut à gauche de la cage

1.2. Données fournies

On reçoit en entrée une grille de killer sudoku sous la forme suivante : 9 lignes contenat la grille, chaque ligne contient 9 caractères, un espace , 9 caractères. Les 9 premiers caractères sont les chiffres de la première ligne, les 9 suivants des lettres indiquant les cages.

Enfin un 10e ligne contient les valeurs des cages.

56..1..2. aabbccdde
..72..68. afghhiide
..2.87.15 jfggklmme
......3.9 jjgnklopp
.7....2.. qqgnooorr
9.634.8.. stuuvwwxx
2.9..8... stuuvvwyz
..41.2... sAuuByyyz
.8.4...3. CADDBEEFF
a=12 b=17 c=4 d=14 e=15 f=13 g=19 h=7 i=10 j=16 k=10 l=13 m=10 n=15 o=15 p=13 q=11 r=11 s=18 t=3 u=28 v=15 w=20 x=8 y=22 z=12 A=11 B=13 C=6 D=9 E=10 F=5

1.3. Données à fournir

On doit fournir en sortie une grille de killer sudoku complétée.

123874569
567932184
849651237
916247853
358196472
472385916
291768345
685423791
734519628

2. Méthode de résolution

On commence par définir les structures de données qui vont nous permettre de stocker les données du problème.

typedef struct {
    int taille; // Nombre de d'element dans la cage
    int somme; // Somme de la cage
    int *coords[MAX_COORDS_CAGE];   // tableau de pointeur vers les coordonnees des cases de la cage
} cage_t;

typedef struct {
    cage_t cages[MAX_CAGES];
} liste_cages_t;

On définit ensuite les fonctions qui vont nous permettre de manipuler ces structures. initialiser la liste des cages ajouter une coordonnée à une cage ajouter la somme d’une cage verifier ajouté une valeur à une cage est possible (pas de doublon dans la ligne, colonne ou cage)

int lettre_to_index(char lettre) {
    return lettre >= 'a' ? lettre - 'a' : lettre - 'A' + 26;
}

void init_liste_cages(liste_cages_t *ls_cages) {
    for (int i = 0; i < MAX_CAGES; i++) {
        ls_cages->cages[i].taille = 0;
    }
}

void ajouter_coord_cage(liste_cages_t *ls_cages, int num_cage, int *coord) {
    ls_cages->cages[num_cage].coords[ls_cages->cages[num_cage].taille] = coord;
    ls_cages->cages[num_cage].taille++;
}

void ajouter_somme_cage(liste_cages_t *ls_cages, int num_cage, int somme) {
    ls_cages->cages[num_cage].somme = somme;
}

bool somme_cage_possible(liste_cages_t *ls_cages, int num_cage, int num) {
    int somme = 0;
    for (int i = 0; i < ls_cages->cages[num_cage].taille; i++) {
        somme += *ls_cages->cages[num_cage].coords[i];
    }
    return somme + num <= ls_cages->cages[num_cage].somme;
}

On lit ensuite la grille: on stock dans un tableau 2D les valeurs de la grille on stock dans un tableau 2D les index des cages on stock dans une liste les cages avec leurs coordonnées et leur somme

void lire_grille(int grille[TAILLE][TAILLE], int cage_index[TAILLE][TAILLE], liste_cages_t *ls_cages) {
    int i, j;
    int index;
    for (i = 0; i < TAILLE; i++) {
        char grid_line[10];
        char grid_cages[10];
        scanf("%s%s", grid_line, grid_cages);
        fgetc(stdin);
        for (j = 0; j < TAILLE; j++) {
            if (grid_line[j] == '.') {
                grille[i][j] = 0;
            } else {
                grille[i][j] = grid_line[j] - '0';
            }
        }

        for (j = 0; j < TAILLE; j++) {
            index = lettre_to_index(grid_cages[j]);
            cage_index[i][j] = index;
            ajouter_coord_cage(ls_cages, index, &grille[i][j]);
        }
    }

    char cages[251];
    scanf("%[^\n]", cages);

    char somme[3];
    char lettre;
    i = 0;
    while (cages[i] != '\0') {
        lettre = cages[i];
        i += 2;
        j = 0;
        somme[0] = '\0';
        somme[1] = '\0';
        somme[2] = '\0';
        while (cages[i] != ' ' && cages[i] != '\0') {
            somme[j] = cages[i];
            j++;
            i++;
        }
        ajouter_somme_cage(ls_cages, lettre_to_index(lettre), atoi(somme));
        i++;
    }
}

On définit ensuite la fonction qui va nous permettre de vérifier si une valeur est valide pour une case donnée. On vérifie que la valeur n’est pas présente dans la ligne, la colonne et la sous-grille. On vérifie que la valeur n’est pas présente dans la cage. On vérifie que la somme de la cage n’est pas dépassée.

bool est_valide(int grille[TAILLE][TAILLE], int cage_index[TAILLE][TAILLE], liste_cages_t *ls_cages, int x, int y, int num) {
    int i;

    // Vérifier la ligne et la colonne
    for (i = 0; i < TAILLE; i++) {
        if (grille[i][y] == num || grille[x][i] == num) {
            return false;
        }
    }

    // Vérifier la sous-grille
    int x0 = (x / 3) * 3;
    int y0 = (y / 3) * 3;
    for (i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (grille[x0 + i][y0 + j] == num) {
                return false;
            }
        }
    }

    // Vérifier les cages
    return somme_cage_possible(ls_cages, cage_index[x][y], num);
}

On définit ensuite la fonction qui va nous permettre de résoudre la grille. On parcourt la grille de gauche à droite et de haut en bas. Si on tombe sur une case vide, on essaie de mettre une valeur de 1 à 9. Si la valeur est valide, on essaie de résoudre la grille avec cette valeur. Si la grille est résolue, on retourne true. Sinon, on essaie avec une autre valeur. Si on a essayé toutes les valeurs, on retourne false.

bool resolution(int grille[TAILLE][TAILLE], int cage_index[TAILLE][TAILLE], liste_cages_t *ls_cages) {
    int i, j, num;

    for (i = 0; i < TAILLE; i++) {
        for (j = 0; j < TAILLE; j++) {
            if (grille[i][j] == 0) {
                for (num = 1; num <= 9; num++) {
                    if (est_valide(grille, cage_index, ls_cages, i, j, num)) {
                        grille[i][j] = num;
                        if (resolution(grille, cage_index, ls_cages)) {
                            return true;
                        } else {
                            grille[i][j] = 0;
                        }
                    }
                }
                return false;
            }
        }
    }
    return true;
}

On définit enfin la fonction qui va nous permettre d’afficher la grille.

void afficher_grille(int grille[TAILLE][TAILLE]) {
    int i, j;
    for (i = 0; i < TAILLE; i++) {
        for (j = 0; j < TAILLE; j++) {
            printf("%d", grille[i][j]);
        }
        printf("\n");
    }
}

On définit enfin la fonction principale qui va nous permettre de résoudre la grille.

int main() {
    int grille[TAILLE][TAILLE];
    int cage_index[TAILLE][TAILLE];
    liste_cages_t ls_cages;

    init_liste_cages(&ls_cages);

    lire_grille(grille, cage_index, &ls_cages);

    resolution(grille, cage_index, &ls_cages);
    afficher_grille(grille);

    return 0;
}

2.1. Implémentation complète

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>


#define TAILLE 9

#define MAX_CAGES 52
#define MAX_COORDS_CAGE 9

typedef struct {
    int taille; // Nombre de d'element dans la cage
    int somme; // Somme de la cage
    int *coords[MAX_COORDS_CAGE];   // tableau de pointeur vers les coordonnees des cases de la cage
} cage_t;

typedef struct {
    cage_t cages[MAX_CAGES];
} liste_cages_t;

int lettre_to_index(char lettre) {
    return lettre >= 'a' ? lettre - 'a' : lettre - 'A' + 26;
}

void init_liste_cages(liste_cages_t *ls_cages) {
    for (int i = 0; i < MAX_CAGES; i++) {
        ls_cages->cages[i].taille = 0;
    }
}

void ajouter_coord_cage(liste_cages_t *ls_cages, int num_cage, int *coord) {
    ls_cages->cages[num_cage].coords[ls_cages->cages[num_cage].taille] = coord;
    ls_cages->cages[num_cage].taille++;
}

void ajouter_somme_cage(liste_cages_t *ls_cages, int num_cage, int somme) {
    ls_cages->cages[num_cage].somme = somme;
}

bool somme_cage_possible(liste_cages_t *ls_cages, int num_cage, int num) {
    int somme = 0;
    for (int i = 0; i < ls_cages->cages[num_cage].taille; i++) {
        somme += *ls_cages->cages[num_cage].coords[i];
    }
    return somme + num <= ls_cages->cages[num_cage].somme;
}

void lire_grille(int grille[TAILLE][TAILLE], int cage_index[TAILLE][TAILLE], liste_cages_t *ls_cages) {
    int i, j;
    int index;
    for (i = 0; i < TAILLE; i++) {
        char grid_line[10];
        char grid_cages[10];
        scanf("%s%s", grid_line, grid_cages);
        fgetc(stdin);
        for (j = 0; j < TAILLE; j++) {
            if (grid_line[j] == '.') {
                grille[i][j] = 0;
            } else {
                grille[i][j] = grid_line[j] - '0';
            }
        }

        for (j = 0; j < TAILLE; j++) {
            index = lettre_to_index(grid_cages[j]);
            cage_index[i][j] = index;
            ajouter_coord_cage(ls_cages, index, &grille[i][j]);
        }
    }

    char cages[251];
    scanf("%[^\n]", cages);

    char somme[3];
    char lettre;
    i = 0;
    while (cages[i] != '\0') {
        lettre = cages[i];
        i += 2;
        j = 0;
        somme[0] = '\0';
        somme[1] = '\0';
        somme[2] = '\0';
        while (cages[i] != ' ' && cages[i] != '\0') {
            somme[j] = cages[i];
            j++;
            i++;
        }
        ajouter_somme_cage(ls_cages, lettre_to_index(lettre), atoi(somme));
        i++;
    }
}

bool est_valide(int grille[TAILLE][TAILLE], int cage_index[TAILLE][TAILLE], liste_cages_t *ls_cages, int x, int y, int num) {
    int i;

    // Vérifier la ligne et la colonne
    for (i = 0; i < TAILLE; i++) {
        if (grille[i][y] == num || grille[x][i] == num) {
            return false;
        }
    }

    // Vérifier la sous-grille
    int x0 = (x / 3) * 3;
    int y0 = (y / 3) * 3;
    for (i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            if (grille[x0 + i][y0 + j] == num) {
                return false;
            }
        }
    }

    // Vérifier les cages
    return somme_cage_possible(ls_cages, cage_index[x][y], num);
}

bool resolution(int grille[TAILLE][TAILLE], int cage_index[TAILLE][TAILLE], liste_cages_t *ls_cages) {
    int i, j, num;

    for (i = 0; i < TAILLE; i++) {
        for (j = 0; j < TAILLE; j++) {
            if (grille[i][j] == 0) {
                for (num = 1; num <= 9; num++) {
                    if (est_valide(grille, cage_index, ls_cages, i, j, num)) {
                        grille[i][j] = num;
                        if (resolution(grille, cage_index, ls_cages)) {
                            return true;
                        } else {
                            grille[i][j] = 0;
                        }
                    }
                }
                return false;
            }
        }
    }
    return true;
}

void afficher_grille(int grille[TAILLE][TAILLE]) {
    int i, j;
    for (i = 0; i < TAILLE; i++) {
        for (j = 0; j < TAILLE; j++) {
            printf("%d", grille[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int grille[TAILLE][TAILLE];
    int cage_index[TAILLE][TAILLE];
    liste_cages_t ls_cages;

    init_liste_cages(&ls_cages);

    lire_grille(grille, cage_index, &ls_cages);

    resolution(grille, cage_index, &ls_cages);
    afficher_grille(grille);

    return 0;
}

3. Tests

3.1. Compilation

Compilation avec gcc :

✅ Compilation sans erreur/warning OK

3.2. Test de Codingame

3.2.1. Test 1 : Easy

Input:

56..1..2. aabbccdde
..72..68. afghhiide
..2.87.15 jfggklmme
......3.9 jjgnklopp
.7....2.. qqgnooorr
9.634.8.. stuuvwwxx
2.9..8... stuuvvwyz
..41.2... sAuuByyyz
.8.4...3. CADDBEEFF
a=12 b=17 c=4 d=14 e=15 f=13 g=19 h=7 i=10 j=16 k=10 l=13 m=10 n=15 o=15 p=13 q=11 r=11 s=18 t=3 u=28 v=15 w=20 x=8 y=22 z=12 A=11 B=13 C=6 D=9 E=10 F=5

Expected output:

568913427
197254683
342687915
851726349
473891256
926345871
219538764
734162598
685479132

Actual output:

568913427
197254683
342687915
851726349
473891256
926345871
219538764
734162598
685479132

Run test:

Test 1: Easy
✅ Test passed

3.2.2. Test 2 : Medium

Input:

.1..65..7 abcccdeee
6.7....15 fbbgddhie
.54..1..3 fjggdkhil
......... fjmgkknil
16..3.5.. ooppqqnrr
..8..2... sotuvqnnw
3......7. xxtuyyzzA
...3..... BCDDEFzAA
.2.1..349 BBDGGGzzz
a=9 b=16 c=17 d=21 e=18 f=12 g=18 h=15 i=12 j=12 k=15 l=5 m=9 n=19 o=10 p=6 q=12 r=17 s=5 t=13 u=15 v=1 w=4 x=7 y=11 z=33 A=12 B=17 C=9 D=10 E=7 F=4 G=14

Expected output:

913865427
687243915
254791683
479586132
162437598
538912764
345629871
891374256
726158349

Actual output:

913865427
687243915
254791683
479586132
162437598
538912764
345629871
891374256
726158349

Run test:

Test 2: Medium
✅ Test passed

3.2.3. Test 3 : Hard

Input:

........1 abbcdddef
....5.... agccchfff
...3..... ijjjhhkkl
.......9. ijmmnnokp
..6...... qrrsttuvv
....9.7.. qwxsyuuzA
.....3... qwxxyBBCA
......... qDxEEBBCC
..8..5... DDFFFBGHH
a=14 b=9 c=17 d=22 e=3 f=20 g=3 h=9 i=6 j=21 k=19 l=7 m=9 n=11 o=4 p=6 q=16 r=15 s=11 t=11 u=10 v=9 w=9 x=20 y=16 z=5 A=5 B=22 C=19 D=17 E=11 F=15 G=3 H=11

Expected output:

672489531
831752649
549316827
157238496
396547218
284691753
415873962
763924185
928165374

Actual output:

672489531
831752649
549316827
157238496
396547218
284691753
415873962
763924185
928165374

Run test:

Test 3: Hard
✅ Test passed

3.2.4. Test 4 : Expert

Input:

......... abbcccddd
......... aabefghii
......... jkkefghil
......... jmnnnooil
......... mmmppqqrl
......... sttpuuqrr
......... svwwwxxxy
......... zvvABBCCy
......... zDDAAEEFF
a=7 b=23 c=13 d=13 e=5 f=10 g=17 h=11 i=28 j=12 k=8 l=16 m=20 n=9 o=11 p=22 q=14 r=8 s=15 t=13 u=4 v=17 w=11 x=16 y=8 z=4 A=19 B=10 C=7 D=13 E=15 F=6

Expected output:

496175238
218369547
753248691
531627489
827594316
649813752
962451873
374982165
185736924

Actual output:

496175238
218369547
753248691
531627489
827594316
649813752
962451873
374982165
185736924

Run test:

Test 4: Expert
❌ Test failed (timeout)

3.2.5. Résumé global

✅ Test 1 : Easy passed
✅ Test 2 : Medium passed
✅ Test 3 : Hard passed
❌ Test failed (timeout)
Passed: 3 (75.0%)
Failed: 1 (25.0%)

Le dernier test echoue par manque de temps.

4. Conclusion

Lors de cet exercice, j’ai pu mettre en place un algorithme récursif pour résoudre un killer sudoku. Pour résoudre le dernier test, qui échoue, j’ai testé différentes solution qui n’ont pas fonctionné.

  • Limiter le nombre de solutions possibles à tester (1-9) pour chaque case
  • Trouver des solutions évidentes (ex: 1 seul chiffre possible pour une case) ou utiliser la règle du 45.

Retour vers ma page Web personnelle

Crédits :

Date: 2023-03-03

Auteur: Jules Girard

Created: 2023-04-26 mer. 19:30