Rapport sur Killer Sudoku Solver

Table of Contents

Lien vers ma page web ISIMA

killer.png

Présentation du problème

Killer Sudoku Solver est un problème du site Codingame. Il consiste en la création d’un programme permettant la résolution d’une grille de Killer Sudoku. Voici tout d’abord un point sur ce qu’est un Sudoku, et sur les spécificitées du Killer sudoku:

Le sudoku, est un jeu en forme de grille, créé en 1979 par l’Américain Howard Garns qui s’est inspiré de jeux plus anciens afin de le créer. Le but du jeu est de compléter la grille selon les règles suivantes (on décrira ici les règles pour une grille classique de 9*9):

  • Chaque ligne doit contenir de façon unique les nombres de 1 à 9.
  • Chaque colonne doit contenir de façon unique les nombres de 1 à 9.
  • Chaque carré de 3*3 délimité sur la grille doit contenir de façon unique les nombres de 1 à 9.

De plus, le Killer Sudoku, variante du Sudoku classique, a une spécificité. En plus de la grille classique de Sudoku, on délimite des zones à l’intérieur de celle-ci. On assignera à chaque zone une valeur, et la somme des nombres dans la zone devra être égale à la valeur de la zone. Avec cette règle en plus, cela a pour effet qu’il n’existe qu’une seule solution à chaque grille (ce qui n’était pas forcement le cas dans le cadre d’un Sudoku classique).

Alors, suivant ces règles, notre programme doit compléter et renvoyer la grille de Sudoku donnée en entrée, ligne par ligne. Chaque ligne est donnée sous la forme d’un tableau de caractère. Les cases restantes à compléter sont représentées par des .. De plus, il nous est fourni un second tableau de caractère servant à délimiter les zones de la grille. Chaque zone est définie par une lettre (majuscule ou minuscule). Les différents élements d’une zone sont forcement adjacent. Enfin, le site nous fourni une dernière entrée, une ligne avec les valeurs de chaque zone. Afin de mieux comprendre le format des entrées, voici un exemple d’entrée fournie par le site.

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

Métode de résolution

Description de la stratégie

Afin de résoudre ce problème, il me fallait reprendre le principe du premier puzzle sudoku, et l’améliorer pour qu’il prenne en compte les spécificitées de cette variante. Ainsi, j’ai pu découper mon code en plusieurs parties afin de pouvoir décrire au mieux ma démarche de résolution.

Partie 1: Fonction affiche

void affiche(){

    for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                fprintf(stderr,"%d", tab[i][j]); // Affiche la case de coordonnée i,j dans le tableau
            }
            fprintf(stderr,"\n");
        }
}

Cette fonction permet d’afficher la grille de sudoku. Elle m’a notamment été utile lors du débugage de mon code.

Partie 2: Fonction find_cage

int find_cage(int x,int y,int n){

    // Définition des variables

    int somme=n;
    int k=0;
    int stop=0;
    int i=0;
    int j=0;
    int count=0;

    // Boucle qui permet de récupérer des informations sur la cage dans laquelle se situe la case que l'on traite

    while((lettre[k]!='\0')&&(stop!=1)){
        if(lettre[k]==cage[y][x]){
            stop=1;
        }

        k++;
    }

    // Tant que l'on a pas parcouru le tableau ou que l'on a pas trouvé tout les élements d'une cage

    while((i<9)&&(compteur[k-1]!=count)){
        while((j<9)&&(compteur[k-1]!=count)){
            if((cage[i][j]==cage[y][x])&&(tab[i][j]!=0)){ // Si on a trouvé un élement de la cage et qu'il est rempli
                somme+=tab[i][j]; // On ajoute sa valeur à la somme
                count++;
                if(somme>valeur[k-1]){ // Si la somme des cases est plus grande que la somme attendue
                    return -1;
                }
            }else if((cage[i][j]==cage[y][x])&&(tab[i][j]==0)&&((i!=y)||(j!=x))){ // Si on trouve une case qui appartient à la cage est qui est vide
                return 1;
            }
            j++;
        }
        j=0;
        i++;
    }


    if(somme==valeur[k-1]){ // Si la somme est bien la somme attendue
        return 1;
    }

    return 0;
}

Cette fonction permet de vérifier si le nombre que l’on souhaite ajouter respecte bien les conditions liées à sa cage. On cherche donc à savoir si la somme des nombres de la cage en question (ajoutés au nouveau nombre) est bien la bonne. Pour cela, on va parcourir la grille dans une double boucle while ce qui pourra nous permettre de stopper le parcour lorsque l’on aura trouvé tout les élements dont on a besoin. Ainsi, on va définir une variable count qui sera incrémentée de 1 dès lors que l’on aura trouvé un élement de la cage. Elle permettra à la boucle de s’arréter lorsque l’on aura trouvé tout les élements de la cage. Mais ce n’est pas la seule condition qui fera s’arréter la boucle. En effet, si un des élements de la cage n’est pas encore complété, il ne servira à rien de chercher les autres élements, puisqu’il nous sera impossible de compléter la somme de la cage. Alors, on renvoi 1, ce qui signifit que le nombre que l’on souhaite ajouter peut être bon, même si l’on en est pas assuré. Enfin, à chaque tour de boucle, on va tester si la somme actuelle que l’on connait (celle qui n’est pas encore complète puisque l’on a pas trouvé toutes les cases), ne dépasse pas la somme attendue. Si tel est le cas, on renvoi le code -1, qui signifit que le nombre que l’on souhaite tester fait que la somme dépasse de la valeur attendu. Cela pourra nous aider à gagner en nombre d’opération plus tard dans le programme. Enfin, si l’appel de la fonction ne s’est pas arrété avant la fin du parcour, on compare la somme obtenue à celle attendue pour la cage. Si elles sont égales, on renvoi le code 1 signifiant que le nombre est éligible pour compléter cette case.

Partie 3: Fonction test

int test(int y, int x, int n) {
    int x0=0, y0=0;
    int count=1;

    // Vérifie si le nombre testé est déja sur la ligne

    while (x0 < 9 && count==1) {
        if (tab[y][x0] == n) {
            count=0;
        }
        x0++;
    }

    // Vérifie si le nombre testé est déja sur la colonne

    while (y0 < 9 && count==1) {
        if (tab[y0][x] == n) {
            count=0;
        }
        y0++;
    }


    // Vérifie si le nombre testé est déja dans le carré de 3 par 3

    x0 = (x / 3) * 3;
    y0 = (y / 3) * 3;
    int i=0;
    while (i < 3 && count==1) {
        int j = 0;
        while (j < 3 && count==1) {
            if (tab[y0+i][x0+j] == n) {
                count=0;
            }
            j++;
        }
        i++;
    }
    return count;
}

Dans cette troisième partie, on définie une fonction test, qui va permettre de tester la validité des nombres que l’on souhaite ajouter à la grille. Cette fonction prend en argument les coordonnées de la position à laquelle on souhaite ajouter le nombre, ainsi que le nombre que l’on souhaite ajouter. Premièrement, la fonction va tester si le nombre testé est présent sur la ligne où l’on souhaite l’ajouter. Si c’est le cas, on renverra le code 0 qui, dans le cadre de ce code représentera un échec. Ensuite, la fonction va tester de la même manière, si le nombre que l’on souhaite ajouter est déjà présent sur la colonne. Enfin, dans une troisième boucle, qui est une double boucle while, on va parcourir le tableau afin de vérifier si le nombre est déjà présent dans le cube de 3*3 délimité par les grilles de sudoku. A la fin de tout ces test, on renverra 1 si tout les tests sont passés, sinon la fonction renverra 0.

Partie 4: Fonction solve

void solve() {

    int y, x,n;
    for (y= 0; y < 9; y++) {       // On parcour le tableau
        for (x = 0; x < 9; x++) {
            if (tab[y][x] == 0) {       // Si on tombe sur une case non remplie
                for(n = 1; n < 10; n++){
                    int find=find_cage(x,y,n);
                    if ((test(y, x, n)==1)&&(find==1)){  // Test si le nombre n peut aller dans la case
                        tab[y][x] = n;   //  Mise à jour du tableau
                        solve();        // On relance la fonction
                        tab[y][x] = 0;      // Remise à 0 de la case si mauvais choix
                    }else if((test(y, x, n)==1)&&(find==-1)){
                        break;
                    }
                }
                return;
            }
        }
    }

    for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                printf("%d", tab[i][j]);
            }
            printf("\n");
        }
    exit(0);
}

Dans cette partie, on retrouve la fonction solve, utilisant le mécanisme de récursion, qui va nous permettre de trouver la solution à la grille de Sudoku. Si le nombre que l’on souhaite ajouter respecte les conditions, on va à nouveau appeler la fonction solve pour continuer à compléter la grille. Si la fonction find renvoie le code -1, cela signifit que la somme de la cage est plus grande que la somme attendu. Il faut donc revenir en arrière sur le positionnement des autres chiffres, puisque comme on test les chiffres par ordre croissant, le chiffre suivant ne sera forcement pas valide (la somme sera forcement trop grande).

Partie 5: Fonction voisin

int voisin(int i, int j, char name, int count, int teste[10][10]){

    // Si l'élement testé ne fait pas parti de la cage

    if (cage[i][j]!=name){
        return count;
    }

    count++; // On ajoute 1 au compteur si on détecte un élement de la cage
    teste[i][j]=1; // Indique que la case a déja été testé


    if((i!=0)&&(teste[i-1][j]!=1)){     // Si on est pas en haut du tableau, alors on regarde la case du dessus
        count=voisin(i-1 , j, name, count, teste) ;
    }
    if((j!=0)&&(teste[i][j-1]!=1)){     // Si on est pas sur le bord droit du tableau, alors on regarde la case de gauche
        count=voisin(i , j-1, name, count, teste ) ;
    }
    if((j!=8)&&(teste[i][j+1]!=1)){     // Si on est pas en haut du tableau, alors on regarde la case de droite
        count=voisin(i , j+1, name, count, teste ) ;
    }
    if((i!=8)&&(teste[i+1][j]!=1)){     // Si on est pas en bas du tableau, alors on regarde la case du dessous
        count=voisin(i+1 , j, name, count, teste ) ;
    }

    return count;
}

Dans cette partie, on définit la fonction voisin, qui a pour but de calculer le nombre d’élement de chaque cage. Ainsi, on pourra se servir de cette donnée afin de créer les nouvelles conditions vu au dessus. Cela diminuera donc la complexité de notre code, et donc son temps d’execution. On reviendra plus en détail sur cette fonction lors du détail algorithmique.

Partie 6: Boucle principale

for (int i = 0; i < 9; i++) {
        char grid_line[10];
        char grid_cages[10];
        scanf("%s%s", grid_line, grid_cages);
        fgetc(stdin);
        for(int j=0;j <9 ;j++){ // Récupération des valeurs des cases du sudoku
            if(grid_line[j]!='.'){ // Si la valeur est indiqué
                tab[i][j]=grid_line[j]-'0'; // On l'inscrit
            }else{
                tab[i][j]=0; // Sinon on lui assigne automatiquement la valeur 0
            }

            cage[i][j]=grid_cages[j]; // On crée un tableau avec les cages
        }
    }
    char cages[251];
    scanf("%[^\n]", cages);

    int j;
    int i=0;
    int k=0;
    int y=0;
    int x=0;
    char stock_nb[10];
    stock_nb[0]='\0';
    stock_nb[1]='\0';
    stock_nb[2]='\0';
    int count_tot=0;
    int trouve=0;
    int xsave,ysave;

    while(cages[i]!='\0'){ // Tant que l'on a pas lu toute la ligne
        if(((cages[i]<='Z')&&(cages[i]>='A'))||((cages[i]<='z')&&(cages[i]>='a'))){ // Si on détecte une lettre
            j=2;

            while((cages[i+j]<='9')&&(cages[i+j]>='0')){ // On récupère la valeur correspondante
                stock_nb[j-2]=cages[i+j];
                j++;
            }

            valeur[k]=atoi(stock_nb);
            count_tot++;
            lettre[k]=cages[i];

            while(trouve!=1){
                while((trouve!=1)||(x<9)){
                    if(lettre[k]==cage[y][x]){
                        trouve=1;
                        xsave=x;
                        ysave=y;
                    }
                    x++;
                }
                x=0;
                y++;
            }
            y=0;
            trouve=0;

            int teste[10][10]={0};  // Remplissage d'un tableau de 0
            compteur[k]=voisin(ysave,xsave,cage[ysave][xsave],0, teste); // Création d'un tableau avec le nombre d'élement de chaque cage.

            stock_nb[0]='\0';
            stock_nb[1]='\0';
            stock_nb[2]='\0';
            k++;
        }
        i++;
    }


    i=0;
    j=0;

    solve();

    return 0;

Dans le programme principal, on va récupérer les entrées fournies par Codingame, puis on va appeler la fonction solve, qui va résoudre le Sudoku. Pour récupérer les entrées, on va stocker dans un premier tableau la grille de sudoku, vide, avec uniquement les chiffres déjà préremplis dans les test. On va remplacer les . représentant les cases vides par des 0. Simultanemment, on stockera les cages dans un autre tableau. Ensuite, on va récupérer les valeurs des sommes pour chaque cage. Pour se faire, on va parcourir la ligne qui nous est fournie. Lorsque l’on trouvera une lettre, on la stockera, puis on cherchera la valeur correspondante. Les entrées sont données en une seule ligne sous la forme suivante: lettre=nombre et sont séparées par un espace. Pour récupérer le nombre il suffira donc de récupérer tout les caractères qui sont des chiffres jusqu’à rencontrer un espace. Ensuite, à l’aide de la fonction atoi(), on convertira la chaine de caractère obtenue en un entier. Enfin, on fera appel à la fonction voisin dans une double boucle afin d’obtenir le nombre d’élement de chaque cage.

Détail algorithmique de la méthode

Ce programme se base sur le principe de récursion, et appelle un certain nombre d’autres fonctions auxiliaires. Je vais tout d’abord présenter le principe générale de la récursion, puis je reviendrai plus en détail sur l’une des fonctions de mon code.

Principe général et fonction solve

killerschema1.png

Cliquez ici pour agrandir

Dans le schéma ci dessus, les flèches rouges représentent un test négatif, et les vertes un test positif.

Tout d’abord revenons sur ce qu’est un mécanisme de récursion. Ce mécanisme consiste en l’appel d’une fonction à l’intérieur d’elle même. Une fonction récursive mal définie/codée peut donc engendrer un nombre infini d’appel. Mais ce mécanisme bien utilisé peut être très puissant et extrémement utile.

C’est le cas de cette fonction qui l’utilise afin de trouver la solution au Sudoku de manière brute. C’est-à-dire qu’elle teste toutes les solutions possibles jusqu’à trouver la bonne. Dans cette fonction (solve()), on parcour le tableau, et dès que l’on trouve un 0, on essaye de remplir la case. Alors, on test tout les nombres de 1 à 9. Pour que le test soit validé il faut que la fonction test, et la fonction find_cage renvoient 1. Dès que l’on trouve un nombre qui fonctionne, on le place dans la grille, puis on appelle à nouveau la fonction, jusqu’à ce que la grille soit remplie. Alors, si les nombres placés bloquent la grille de Sudoku par la suite, les cases précedement remplies seront remises à 0, et les test seront fait avec d’autres nombres. Si la fonction find_cage renvoie -1 et que la fonction test renvoie 1, alors on sait qu’il n’est pas nécessaire de tester d’autres nombres pour cette case, et qu’il faut directement revenir en arrière.

Ainsi, suivant ce processus, la fonction ne s’arrétera que lorsque toute la grille sera remplie.

Fonction voisin

Nous allons maintenant nous intéresser à la fonction voisin et à son fonctionnement. Afin d’illustrer mes propos, voici ci dessous un schéma explicatif du fonctionnement de la fonction.

killerschema2.png

Cliquez ici pour agrandir

Dans le schéma ci dessus, les cases rouges représentent la cage dont on compte les élements, les cases plus clairs représentent les cases à tester, et les cases plus foncées représentent les cases testé au moment de l’étape. La seconde grille remplie de cases noirs et blanches représente le tableau test, servant à sauvegarder les cases rouges déja testées.

Pour rappel, la fonction voisin a pour but de compter le nombre d’élement d’une cage. On voudra ici compter les élements de la cage rouge.

  • Etape 0:

    Cette étape représente l’état initial de nos tableaux, lorque aucune opération n’a encore été effectuée.

  • Etape 1:

    Lors de l’étape 1, on appelle la fonction pour une case donnée. On marque alors cette case dans le tableau test pour montrer que l’on a déja appelé la fonction voisin pour cette case. Puis on ajoute 1 au compteur.

  • Etape 2:

    On va alors appeller récursivement la fonction voisin, pour chaque case adjacente à la première case testée. Lors de cette étape on test la case de gauche, comme on ne détecte pas qu’elle appartient à la cage dont on souhaite compter les élements, alors on ne modifie rien.

  • Etape 3:

    Sous le même principe que l’étape 2, on va tester cette fois ci la case du dessus. Comme elle n’appartient pas à la cage, on ne va rien modifier.

  • Etape 4:

    On va alors procéder de la même manière jusqu’à tester la case en dessous de la case de départ. Cette fois ci, comme on détecte l’appartenance à la cage, on va ajouter 1 au compteur, puis marquer la case dans le tableau test, et tester les cases adjacentes.

  • Etape 5:

    L’opération précedente nous mène à l’étape 5. En effet, on a testé les cases adjacentes à la case détectée précedement, sans tester la plus haute case rouge puisqu’elle a été marqué dans le tableau test. Alors, on a fini par détecter que la case que l’on test actuellement (la plus basse) est une case appartenant à la cage rouge. On a alors ajouté de nouveau 1 au compteur, et marqué la case dans le tableau test. Ainsi après avoir testé toutes ses cases adjacentes, la fonction ne détectera pas d’autres cases appartenant à la cage testée. Donc cette fonction finira par renvoyer 3, la valeur du compteur.

    Remarque: On observe encore une fois, dans le cadre de cette fonction, l’utilitée de la récursion.

Code commenté ayant résolu le problème

Voici ci dessous le code complet m’ayant permis de résoudre le problème.

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

int tab[10][10];
char cage[10][10];
int valeur[100];
char lettre[100];
int compteur[100];

void affiche(){

    for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                fprintf(stderr,"%d", tab[i][j]); // Affiche la case de coordonnée i,j dans le tableau
            }
            fprintf(stderr,"\n");
        }
}

int find_cage(int x,int y,int n){

    // Définition des variables

    int somme=n;
    int k=0;
    int stop=0;
    int i=0;
    int j=0;
    int count=0;

    // Boucle qui permet de récupérer des informations sur la cage dans laquelle se situe la case que l'on traite

    while((lettre[k]!='\0')&&(stop!=1)){
        if(lettre[k]==cage[y][x]){
            stop=1;
        }

        k++;
    }

    // Tant que l'on a pas parcouru le tableau ou que l'on a pas trouvé tout les élements d'une cage

    while((i<9)&&(compteur[k-1]!=count)){
        while((j<9)&&(compteur[k-1]!=count)){
            if((cage[i][j]==cage[y][x])&&(tab[i][j]!=0)){ // Si on a trouvé un élement de la cage et qu'il est rempli
                somme+=tab[i][j]; // On ajoute sa valeur à la somme
                count++;
                if(somme>valeur[k-1]){ // Si la somme des cases est plus grande que la somme attendue
                    return -1;
                }
            }else if((cage[i][j]==cage[y][x])&&(tab[i][j]==0)&&((i!=y)||(j!=x))){ // Si on trouve une case qui appartient à la cage est qui est vide
                return 1;
            }
            j++;
        }
        j=0;
        i++;
    }


    if(somme==valeur[k-1]){ // Si la somme est bien la somme attendue
        return 1;
    }

    return 0;
}

int test(int y, int x, int n) {
    int x0=0, y0=0;
    int count=1;

    // Vérifie si le nombre testé est déja sur la ligne

    while (x0 < 9 && count==1) {
        if (tab[y][x0] == n) {
            count=0;
        }
        x0++;
    }

    // Vérifie si le nombre testé est déja sur la colonne

    while (y0 < 9 && count==1) {
        if (tab[y0][x] == n) {
            count=0;
        }
        y0++;
    }


    // Vérifie si le nombre testé est déja dans le carré de 3 par 3

    x0 = (x / 3) * 3;
    y0 = (y / 3) * 3;
    int i=0;
    while (i < 3 && count==1) {
        int j = 0;
        while (j < 3 && count==1) {
            if (tab[y0+i][x0+j] == n) {
                count=0;
            }
            j++;
        }
        i++;
    }
    return count;
}

void solve() {

    int y, x,n;
    for (y= 0; y < 9; y++) {       // On parcour le tableau
        for (x = 0; x < 9; x++) {
            if (tab[y][x] == 0) {       // Si on tombe sur une case non remplie
                for(n = 1; n < 10; n++){
                    int find=find_cage(x,y,n);
                    if ((test(y, x, n)==1)&&(find==1)){  // Test si le nombre n peut aller dans la case
                        tab[y][x] = n;   //  Mise à jour du tableau
                        solve();        // On relance la fonction
                        tab[y][x] = 0;      // Remise à 0 de la case si mauvais choix
                    }else if((test(y, x, n)==1)&&(find==-1)){
                        break;
                    }
                }
                return;
            }
        }
    }

    for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                printf("%d", tab[i][j]);
            }
            printf("\n");
        }
    exit(0);
}

int voisin(int i, int j, char name, int count, int teste[10][10]){

    // Si l'élement testé ne fait pas parti de la cage

    if (cage[i][j]!=name){
        return count;
    }

    count++; // On ajoute 1 au compteur si on détecte un élement de la cage
    teste[i][j]=1; // Indique que la case a déja été testé


    if((i!=0)&&(teste[i-1][j]!=1)){     // Si on est pas en haut du tableau, alors on regarde la case du dessus
        count=voisin(i-1 , j, name, count, teste) ;
    }
    if((j!=0)&&(teste[i][j-1]!=1)){     // Si on est pas sur le bord droit du tableau, alors on regarde la case de gauche
        count=voisin(i , j-1, name, count, teste ) ;
    }
    if((j!=8)&&(teste[i][j+1]!=1)){     // Si on est pas en haut du tableau, alors on regarde la case de droite
        count=voisin(i , j+1, name, count, teste ) ;
    }
    if((i!=8)&&(teste[i+1][j]!=1)){     // Si on est pas en bas du tableau, alors on regarde la case du dessous
        count=voisin(i+1 , j, name, count, teste ) ;
    }

    return count;
}

int main(){

  for (int i = 0; i < 9; i++) {
          char grid_line[10];
          char grid_cages[10];
          scanf("%s%s", grid_line, grid_cages);
          fgetc(stdin);
          for(int j=0;j <9 ;j++){ // Récupération des valeurs des cases du sudoku
              if(grid_line[j]!='.'){ // Si la valeur est indiqué
                  tab[i][j]=grid_line[j]-'0'; // On l'inscrit
              }else{
                  tab[i][j]=0; // Sinon on lui assigne automatiquement la valeur 0
              }
  
              cage[i][j]=grid_cages[j]; // On crée un tableau avec les cages
          }
      }
      char cages[251];
      scanf("%[^\n]", cages);
  
      int j;
      int i=0;
      int k=0;
      int y=0;
      int x=0;
      char stock_nb[10];
      stock_nb[0]='\0';
      stock_nb[1]='\0';
      stock_nb[2]='\0';
      int count_tot=0;
      int trouve=0;
      int xsave,ysave;
  
      while(cages[i]!='\0'){ // Tant que l'on a pas lu toute la ligne
          if(((cages[i]<='Z')&&(cages[i]>='A'))||((cages[i]<='z')&&(cages[i]>='a'))){ // Si on détecte une lettre
              j=2;
  
              while((cages[i+j]<='9')&&(cages[i+j]>='0')){ // On récupère la valeur correspondante
                  stock_nb[j-2]=cages[i+j];
                  j++;
              }
  
              valeur[k]=atoi(stock_nb);
              count_tot++;
              lettre[k]=cages[i];
  
              while(trouve!=1){
                  while((trouve!=1)||(x<9)){
                      if(lettre[k]==cage[y][x]){
                          trouve=1;
                          xsave=x;
                          ysave=y;
                      }
                      x++;
                  }
                  x=0;
                  y++;
              }
              y=0;
              trouve=0;
  
              int teste[10][10]={0};  // Remplissage d'un tableau de 0
              compteur[k]=voisin(ysave,xsave,cage[ysave][xsave],0, teste); // Création d'un tableau avec le nombre d'élement de chaque cage.
  
              stock_nb[0]='\0';
              stock_nb[1]='\0';
              stock_nb[2]='\0';
              k++;
          }
          i++;
      }
  
  
      i=0;
      j=0;
  
      solve();
  
      return 0;

}

Présentation des tests

Afin de coder ce programme et de le valider, il a fallut passer plusieurs tests. Alors voici ci dessous les tests qui m’ont permi de résoudre ce problème.

Test 1

  • Entrée
    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
    
  • Sortie
    568913427
    197254683
    342687915
    851726349
    473891256
    926345871
    219538764
    734162598
    685479132
    

    Ce premier test permet de comprendre le problème et de tester notre code sur un cas simple.

Test 2

  • Entrée
    .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
    
  • Résultat
    ============Test passe============
    
    ==============Sortie==============
    
    913865427
    687243915
    254791683
    479586132
    162437598
    538912764
    345629871
    891374256
    726158349
    
    
    ==================================
    

    Ce second test nécessite un minimum d’optimisation sur le code afin de s’assurer qu’il traite tout les cas possibles.

Test 3

  • Entrée
    ........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
    
  • Résultat
    ============Test passe============
    
    ==============Sortie==============
    
    672489531
    831752649
    549316827
    157238496
    396547218
    284691753
    415873962
    763924185
    928165374
    
    
    ==================================
    

    Dans ce troisième test, on traite de nombreux cas, ce qui permet de corriger les erreurs encore présentes dans le code, mais qui n’affectaient pas des tests moins importants. Cela permet également de se pencher sur l’optimisation du programme.

Test 4

  • Entrée
    ......... 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
    

    Mon code ne passe pas le dernier test. En effet, lorsqu’il n’y a aucun nombre au départ, mon code devient inefficace. En effet, lorsque j’execute mon code sur machine, au bout d’un très long moment, on arrive à obtenir une solution. Malgré de nombreux essais, je n’ai malheuresement pas réussi à suffisemment l’améliorer afin qu’il fonctionne dans le temps imparti.

    Remarque: N’ayant pas réussi le dernier test, je n’ai pas pu présenter la solution d’un autre utilisateur.

Valgrind

Afin d’être certain de ne pas avoir de problèmes de mémoire et autres, que le compilateur n’aurait pas détecté, on utilise valgrind.

valgrind ./killer < killertest/test3

valgrindkiller.png

Cliquez ici pour agrandir

Le programme présenté passe bien valgrind pour les 3 premiers tests.

Bilan de ce travail

Malgré que je n’ai pas réussi à aboutir à un code passant tout les tests, ce travail a tout de même eu un certain nombre d’aspect positifs. En effet, la conception de ce programme m’a forcé à réfléchir à la complexité temporelle de mon algorithme. J’ai donc dû l’optimiser un minimum afin qu’il puisse passer les premiers tests. De plus, les fonctions solve et voisin m’ont permi d’approfondir mes connaissances au niveau des mécanismes de récursion.

Author: Mathieu CHEDAS

Created: 2023-03-23 jeu. 10:23