Partage
  • Partager sur Facebook
  • Partager sur Twitter

Exercices pour débutants en C

Au menu : zSommeChiffres (nombres, algo)

Anonyme
8 juillet 2009 à 4:00:35

Citation : shareman


unsigned int* incr(unsigned int* i)
{
    (*i)++; return i;
}

Je rêve ou tu as fait une fonction pour incrémenter une variable ?!!

Hallucinant, j'ai l'impression que tu cherches à pondre un code le plus compliqué possible pour essayé de nous embrouiller...

Genre une fonction qui prend 4 lignes de code pour remplacer un simple "i++", whaou ta fonction doit vraiment super bien incrémenter alors.

J'imagine si tu doit multiplier deux variables, ça dois te prendre 3 pages de code...

Puis en plus, à force de compliquer inutilement le code tu le ralentit considérablement, compare toi même, mais moi je vois bien que ta technique est 4 fois plus lente !

#include <stdio.h>
#include <time.h>

unsigned int* incr(unsigned int* i)
{
    (*i)++; return i;
}

int pth(const char* str, unsigned int* i)
{
    if(   (str[*i] == '\0')
       || (str[*i] == '(' && pth(str, incr(i)) && str[*i] == ')' && pth(str, incr(i)))
       || (str[*i] != ')' && pth(str, incr(i)))  )
        return 1;
    return 1;
}

int shareman(const char* str)
{
    unsigned int i = 0;
    pth(str, &i);
    if(i != strlen(str))
        return 0;
    return 1;
}


int parentheses(const char *chaine){
    int i = 0;

    int p = 0;

    while(chaine[i] != '\0'){
        if(chaine[i] == '(')        p++;
        else if(chaine[i] == ')')   p--;
        if(p < 0) return 0;
        i++;
    }

    if(p > 0)
        return 0;

    return 1;
}

int main(void){

    char chaine[] = "Je suis une (grande) chaine avec (plein(s)) de parenthese(s)";

    time_t t1, t2;

    time(&t1);

	int a = 0;
    while(a < 10000000){
        /*parentheses(chaine);*/ /* 2 ou 3 secondes */
        shareman(chaine); /* 11 ou 12 secondes */
        a++;
    }

    time(&t2);

    printf("%.0f secondes\n", difftime(t2, t1));

    /*printf("Chaine: %d\n", parentheses(chaine));*/
    printf("Chaine: %d\n", shareman(chaine));

	return 0;
}


Pour quelqu'un qui maitrise la langue de bois et qui veut impressionner tout le monde, ça la fout mal de sortir le code le plus pourris quand même, tu dois te sentir important à faire de la récursivité inutile et mettre des pointeurs qui servent à rien...
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2009 à 6:09:56

Tu n'étais pas obligé d'être arrogant, et pour ça tu mérites cette citation :

Citation

Quand le maître montre la lune, le disciple regarde le doigt



Peut être que la méthode qu'il a utilisé te parait trop compliquée pour faire un problème si simple, mais relis mes posts qui viennent après le sien et tu comprendras.
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2009 à 11:03:43

21 :

La fonction incr incrémente la valeur pointée par une variable et renvoie le pointeur. Faire ça directement dans le if, c'est moche et c'est encore plus compliqué. Non, je ne cherche pas à poster un code compliqué, je cherche à montrer une nouvelle technique, ce qui est fait. Si elle vous semble trop compliquée (parce que là, tu n'as juste pas fait l'effort de bien comprendre le code (au moins la fonction incr qui est banale quand même), ce qui est regrettable déjà que tu souhaites poster pour gueuler, ça foire), c'est peut-être juste que la technique n'est pas triviale à utiliser (enfin si, pour ceux qui sont habitués à l'analyse syntaxique descendante).

Si le code est trop complexe, ne vous focalisez pas sur lui.

Pour ce qui est de la lenteur, c'est un peu normal, on utilise la récursivité non-terminale, donc pile d'appel, etc. De plus, l'analyse syntaxique descendante a la réputation d'être l'une des techniques d'analyse syntaxique les plus lentes, peut-être aurais-je du utiliser l'analyse ascendante, moins évidente à implémenter (surtout en C).

"Gueule de bois" ? Bon, si tu es là pour venger d'une façon ou d'une autre zx-spectrum, premièrement c'est con (car il doit parfaitement savoir qu'il s'est un peu comporté de manière idiote, surtout sur le topic if/else) et deuxièmement, ça ne sert à rien, car ça ne m'intéresse pas trop. Et hum 21, sans pointeurs et sans récursivité, la technique ne serait plus présentable.
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2009 à 12:45:54

Citation : shareman

"Gueule de bois" ? Bon, si tu es là pour venger d'une façon ou d'une autre zx-spectrum, premièrement c'est con (car il doit parfaitement savoir qu'il s'est un peu comporté de manière idiote, surtout sur le topic if/else) et deuxièmement, ça ne sert à rien, car ça ne m'intéresse pas trop. Et hum 21, sans pointeurs et sans récursivité, la technique ne serait plus présentable.



--> c'est vraiment du n'importe quoi, et tu prends de mauvais pretexte sur une pente tres glissante................
primo : j'ai besoin de personne pour me "venger"
secundo : si pour toi le fait de demander des explications complémentaires (dont je n'ai toujours rien compris à ton débalage de savoir...) est un "comportement de manière idiote", je te réponds que tu fais un abus de pouvoir
tertio : re-descend les deux pieds sur terre, et te trompes pas de public: il est peut etre bien de presenter un programme sophistiqué ? a condition d'avoir fait plancher les zeros (et j'ai pas honte de ne pas savoir) sur la question :c'est la raison de mon coup de gueule sur l'exo du mois.Parce que le vrai resultat a cela se sera moins de participant parce qu'ils auront l'impression d'être larguer (déjà avec moi -1 :p ).......Voilà

Ensuite
1: l'idiot dans l'histoire d'apres toi c'est celui qui débale sa science, ou c'est celui qui demande des explications complémentaires ?
2 : Oublie moi et evite de me citer dans tes messages
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2009 à 14:07:40

Vous commencez à me gaver.

zx-spectrum tu prends une sanction pour avoir laver ton linge sale en public sur deux topics différents.

21, pourquoi s'adresser de cette manière à shareman ? A moins qu'il ne l'ai fait avant (et j'attends une preuve dans ce cas), rien ne justifie une telle réaction de ta part.

shareman, si tu pouvais les ignorer à partir de maintenant, ce serait mieux pour tout le monde ( il y a les Mp ;) ).
  • Partager sur Facebook
  • Partager sur Twitter
HR Community Manager for Viadeo@Work4Viadeo on Twitter, or join our group here.
Anonyme
8 juillet 2009 à 15:07:50

Je réagis comme ça, parce que ce membre semble avoir de bonne bases en C pour avoir pondu un code pareil, sauf que ce code est plus lent, moins lisible, utilise plus de variables, plus de pointeurs, il parcours la chaine deux fois (en comptant son strlen final).

Que des inconvénients ! je suis conscient qu'il le sait alors pourquoi faire ça ? Pourquoi mettre un code hyper pourri comme ça ???
Juste pour faire genre il peut faire des codes compliqués ?
Moi ça m'énerve c'est tout.

Si il veut faire des codes compliqués alors qu'il propose des exos un peu plus dur, mais faut arrêter de chercher la complication là ou elle n'est pas.
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2009 à 15:32:46

Ben c'est simplement une autre technique. Il suffit de voir : tous les autres codes postés suivent plus ou moins le même algorithme. C'est über-inintéressant pour les débutants. Il n'y aucun mal à vouloir présenter un peu une autre manière de faire, en utilisant les techniques de l'analyse syntaxique (cf compilation). C'est même assez intéressant.

Ici, ce n'est peut-être pas très adapté, comme l'a fait remarquer Arthurus avec "des canons pour tuer des mouches", mais ça change et contrairement à l'algorithme que les autres proposent, celui-ci est plus universel puisque la technique reste là-même, qu'on veuille analyser une expression parenthésée, du ptit langage ( ^^ ) ou même du C (en voyant large).

C'est de ton droit de réagir comme ça et j'aurais certainement eu la même impression que tu as eue. Le problème, c'est que ton argumentation ne tenait pas la route, là c'est mieux car je vois clairement ce que tu me reproches. On ne s'acharne pas sur une fonction (qui n'avait rien à se reprocher en fait) quand on veut critiquer toute l'action.

En réalité, ce ne sont pas les bonnes bases en C qui permettent d'écrire des codes comme celui que je présente (qui n'a pas vraiment quelque chose de particulier, juste qu'il faut se donner un minimum de temps pour le comprendre), mais une petite expérience en matière de compilation (d'ailleurs, c'est dans ce livre que j'ai appris).

Ensuite, oui, le code est relativement lent par rapport à l'autre pour les raisons que j'ai évoquées dans mon précédent post. Deux raisons qui prouvent que ceci n'est pas un argument valable ici : le but n'était pas de présenter un code plus rapide et dans la pratique, on ne sera jamais confronté à 10000000 chaînes à traiter, donc à moins de retourner à l'âge de pierre, la différence ne sera fera pas sentir.

Globalement, je n'utilise pas vraiment plus de variables que les autres code (si l'on oublie la pile d'appels), de toute manière, quand la différence est aussi subtile et quand la quantité de variables déclarées ne varie pas en fonction de l'entrée, ça importe peu.

Et oui, je parcours deux fois la chaîne. Il y a un parcours principal et un parcours dans le strlen. On aurait très bien pu forcer l'utilisateur de la fonction à passer la taille en paramètre, et ce problème aurait été réglé. Ce n'est pas un véritable argument non-plus.

Il est de mon droit de présenter la technique que je souhaite présenter. Il est de votre droit d'essayer de la comprendre ou non.
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
8 juillet 2009 à 16:06:29

Finalement tu aurais très bien pu te passer de la fonction qui incrémente une variable, et ça fait gagner 3 ou 4 secondes ! Toujours plus long que la technique normal.

Mais sinon ça peut te paraître bête de tester la chaine 10 millions de fois mais ça doit probablement revenir au même si tu voulais tester une très grande chaine, sauf que la pile d'appel sauterai peut être j'en sais rien.

EDIT:

Citation : shareman

Faire ça directement dans le if, c'est moche et c'est encore plus compliqué.

Arf. j'avais oublié que tu l'avais mentionné...
Alors pourquoi ne pas le faire, ce n'est pas spécialement plus compliqué, et si ça permet d'aller beaucoup plus vite alors faut le faire.

#include <stdio.h>
#include <time.h>

/* <shareman> */
unsigned int* incr(unsigned int* i)
{
    (*i)++; return i;
}

int pth(const char* str, unsigned int* i)
{
    if(   (str[*i] == '\0')
       || (str[*i] == '(' && pth(str, incr(i)) && str[*i] == ')' && pth(str, incr(i)))
       || (str[*i] != ')' && pth(str, incr(i)))  )
        return 1;
    return 1;
}

int shareman(const char* str)
{
    unsigned int i = 0;
    pth(str, &i);
    if(i != strlen(str))
        return 0;
    return 1;
}
/* </shareman> */




/* <shareman 2> */
int pth2(const char* str, unsigned int* i)
{
    if(   (str[*i] == '\0')
       || (str[*i] == '(' && ++(*i) && pth2(str, i) && str[*i] == ')' && ++(*i) && pth2(str, i))
       || (str[*i] != ')' && ++(*i) && pth2(str, i))  )
        return 1;
    return 1;
}

int shareman2(const char* str)
{
    unsigned int i = 0;
    pth2(str, &i);
    if(i != strlen(str))
        return 0;
    return 1;
}
/* </shareman 2> */




int parentheses(const char *chaine){
    int i = 0;

    int p = 0;

    while(chaine[i] != '\0'){
        if(chaine[i] == '(')
            p++;
        else if(chaine[i] == ')'){
            p--;
            if(p < 0) return 0;
        }
        i++;
    }

    if(p > 0)
        return 0;

    return 1;
}

int main(void){

    char chaine[] = "Je suis une (grande) chaine avec (plein(s)) de parenthese(s)";

    time_t t1, t2;

    time(&t1);

	int a = 0;
    while(a < 10000000){
        /*parentheses(chaine);*/ /* 2 ou 3 secondes */
        /*shareman(chaine);*/ /* 11 ou 12 secondes */
        shareman2(chaine); /* 8 ou 9 secondes */
        a++;
    }

    time(&t2);

    printf("%.0f secondes\n", difftime(t2, t1));

    /*printf("Chaine: %d\n", parentheses(chaine));*/
    /*printf("Chaine: %d\n", shareman(chaine));*/
    printf("Chaine: %d\n", shareman2(chaine));

	return 0;
}
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2009 à 23:37:14

Citation : shareman


Je crois qu'un code vaut mieux qu'un long discours :

// 1 : bien
// 0 : pas bien

/** pth -> ( pth ) pth | l pth | RIEN **/

unsigned int* incr(unsigned int* i)
{
    (*i)++; return i;
}

int pth(const char* str, unsigned int* i)
{
    if(   (str[*i] == '\0')
       || (str[*i] == '(' && pth(str, incr(i)) && str[*i] == ')' && pth(str, incr(i)))
       || (str[*i] != ')' && pth(str, incr(i)))  )
        return 1;
    return 1;
}

int verif(const char* str)
{
    unsigned int i = 0;
    pth(str, &i);
    if(i != strlen(str))
        return 0;
    return 1;
}


Il me semble que ce code est incorrect. Si je le modifie comme ceci :

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

// 1 : bien
// 0 : pas bien

/** pth -> ( pth ) pth | l pth | RIEN **/

unsigned int* incr(unsigned int* i)
{
    (*i)++; return i;
}

int pth(const char* str, unsigned int* i)
{
printf("i=%u\n",*i);
    if(   (str[*i] == '\0')
       || (str[*i] == '(' && pth(str, incr(i)) && str[*i] == ')' && pth(str, incr(i)))
       || (str[*i] != ')' && pth(str, incr(i)))  )
        return 1;
    return 1;
}

int verif(const char* str)
{
    unsigned int i = 0;
    pth(str, &i);
    if(i != strlen(str))
        return 0;
    return 1;
}

int main(void)
{
char *str="((";
printf("\n%d\n",verif(str));

  return 0;
}


afin d'afficher les valeurs des positions de la chaîne qui sont accédées, il n'est pas du tout normal que cela me renvoie ceci :

i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8

0



car cela veut dire que le code va lire ce que contient la chaîne

((


aux positions de 0 à 8 incluses alors que bien sûr les positions de 3 à 8 incluses n'existent pas. C'est même un comportement indéterminé au sens de la Norme du langage C.
  • Partager sur Facebook
  • Partager sur Twitter
8 juillet 2009 à 23:56:00

Se passer de la fonction qui incrémente la valeur pointée par i, je ne l'ai pas fait pour trois raisons qui sont liées : le code serait encore moins "simple" à comprendre, il serait beaucoup moins logique de l'intégrer dans le if et ça rendrait plutôt mal, il suffit de voir ton exemple. Après effectivement, sur 10000000 applications à la suite, on remarque qu'il y a du changement au niveau des performances (et c'est normal quand on y pense).

Il n'y a pas de "technique normale". Ou chaque technique est normale. Celle que tout le monde a présentée (et celle que j'avais également élaborée au début), c'est juste la méthode naïve, qui une fois n'est pas coutume est plus efficace en pratique (et dans ce cas) que l'analyse syntaxique descendante.

Ça ne me parait pas bête du tout de tester la chaîne 10 millions de fois. C'est d'ailleurs une technique que j'utilise également pour évaluer deux algorithmes. Je dis simplement que dans une application concrète, on aura jamais 10 millions de chaînes à la suite à traiter.

Pour la pile d'appel, cf ici, c'est quand même plus robuste que ça.

Candide : effectivement. :)
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 0:03:24

Bon, normalement là c'est bon :
int pth(const char* str, unsigned int* i, unsigned int size)
{
    if((*i) >= size) {
        return 1;
    }
    if(   (str[*i] == '\0')
       || (str[*i] == '(' && pth(str, incr(i), size) && str[*i] == ')' && pth(str, incr(i), size))
       || (str[*i] != ')' && pth(str, incr(i), size))  )
        return 1;
    return 1;
}
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 0:32:17

Citation : shareman

Bon, normalement là c'est bon :



Non toujours pas :

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


// 1 : bien
// 0 : pas bien

/** pth -> ( pth ) pth | l pth | RIEN **/

unsigned int* incr(unsigned int* i)
{
    (*i)++; return i;
}

int pth(const char* str, unsigned int* i, unsigned int size)
{
printf("i=%u\n",*i);
    if((*i) >= size) {
        return 1;
    }
    if(   (str[*i] == '\0')
       || (str[*i] == '(' && pth(str, incr(i), size) && str[*i] == ')' && pth(str, incr(i), size))
       || (str[*i] != ')' && pth(str, incr(i), size))  )
        return 1;
    return 1;
}



int verif(const char* str)
{
    unsigned int i = 0;
    pth(str, &i, strlen(str));
    if(i != strlen(str))
        return 0;
    return 1;
}

int main(void)
{
char *str="((";
printf("\n%d\n",verif(str));

  return 0;
}


ce qui donne :

i=0
i=1
i=2
i=3
i=4

0



  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 0:39:29

Hum, à moins de faire un truc trop moche, je pense qu'il s'agit juste d'abandonner l'idée de "tout dans le if" et ce problème se gère facilement, candide. L'idée et l'algorithme restent les-mêmes. Je vais poster une nouvelle version du code demain. Merci à toi d'avoir remarqué cette erreur de ma part. :)
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 2:09:04

Citation : shareman

L'idée et l'algorithme restent les-mêmes.



Je n'ai pas vu quel algorithme tu voulais utiliser. Pour moi, fondamentalement il n'y en a qu'un : il s'agit de parcourir la chaîne de gauche à droite et de voir si l'écart entre les nombres de parenthèses ouvrantes et fermantes reste toujours positif ou nul et s'il est nul à la fin de la chaîne. À partir de là, on obtient le code itératif que presque tout le monde à trouvé, à des détails prêts près. La version récursive est de peu d'intérêt selon moi et elle est triviale à écrire.

Voici ce que j'aurais proposé :

#include <stdio.h>

int estBienPar(char *s)
{
  int po = 0;                   /* parenthèses ouvertes */
  char c;                       /* pour éviter les indirections multiples */

  while ((c = *s++) && po >= 0)
    if (c == '(')
      po++;
    else if (c == ')')
      po--;
  /* 
     Les 4 lignes précédentes peuvent être remplacées par : 
       po += (c == '(') - (c == ')');
     C'est plus court mais peu lisible et moins
     efficace. */
  return po == 0;
}

int main(void)
{
  char *str = "((((nnnn)n))zero(o)ooo))";

  printf(estBienPar(str) == 1 ? "OK\n" : "KO\n");
  /* Un peu compliqué pour les débutants, désolé ! */

  return 0;
}

KO



S'il fallait montrer quelque chose de savant (ce qui n'aurait a priori pas sa place dans ce topic destiné aux débutants), je pense que ce serait une implémentation via Flex et Bison. Je pense en effet que c'est sur des exemples simples comme celui-ci que l'on peut apprendre à utiliser ces puissants outils pour être capable de les adapter à des programmes beaucoup complexes.

  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 6:06:02

Salut,

Citation : candide


Salut !

;)
Juste un truc...
Le code de Shareman, était destiné à nous faire découvrir, une autre voie(il fait du fonctionnel, et ça se voit!)...
Il a expliqué, donné des liens, participé à un tuto sur la compilation...
C'est pourquoi, je n'ai pas compris l'échange avec ZX-SPECTRUM...
@ZX : Shareman, a pris de son temps, il y a quelques semaines, pour nous proposer des exercices et commenter nos codes.
Il a passé la main, est devenu validateur, et ? Estimons nous heureux, d'avoir des personnes comme lui sur le forum C, qui est devenu plat...

A noter, que personne, à l'exception de Arthurus et de candide(heureux de ton retour ^^ ), n'a essayé de comprendre le code de Shareman(moi itou!...).
Alors, on est un peu humble, et on profite de ce que ces personnes ont à nous apportés, avec une merci facultatif de surcroit.

a++




  • Partager sur Facebook
  • Partager sur Twitter
Zeste de Savoir, le site qui en a dans le citron !
9 juillet 2009 à 8:33:33

Pour répondre à la question de 21, l'intérêt de cet algorithme est qu'il est plus modulaire. L'algorithme qui incrémente et décrémente un compteur utilise une spécificité du problème qui le rend en quelque sorte "facile à résoudre", mais manque de généralité.
Si te poses une question un peu plus délicate (par exemple si on te demande de renvoyer l'arbre correspondant à l'expression parenthesée, ou si on prend une grammaire plus riche), la solution par compteur ne va plus marcher, et tu devras faire complètement autre chose. La solution par descente récursive de Shareman est plus flexible et s'adaptera plus facilement.
En contrepartie, elle a un "coût" de départ plus élevé (elle utilise des concepts plus avancés) donc si on se restreint au problème posé elle est moins adaptée. Cependant, dans une optique d'évolution future du code, ça peut être un meilleur choix.


Pour ce qui est des performances, on peut écrire cet algorithme de manière efficace. J'ai fait une version pour voir, qui (bien que je ne l'aie pas spécialement optimisée) est presque aussi performante que l'algorithme naïf :
int check(const char *str, int *i)
{
    switch(str[(*i)++]) {
    case '\0': return 0;
    case ')': return 1;
    case '(':
        if (check(str, i) != 1)
            return (-1);
        return check(str, i);
    default: return check(str, i);
    }
}

int valide(const char *str)
{
    int i = 0;
    return check(str, &i);
}


J'utilise un entier comme variable modifiable à travers les différents appels de la fonction (sinon il faut le passer en paramètre de retour, ce qui n'est pas idiomatique en C). On peut se contenter d'un pointeur **str, comme le faisait ma version de départ mais j'ai changé pour que ce soit plus accessible aux débutants (qui ne sont souvent pas habitués aux doubles pointeurs). Je ne fais pas de C donc je ne prétends pas que ce code est un exemple.

En -O2, sur mon ordinateur et avec ton test, le code de shareman met 12 secondes, le mien 3.4s et le tien (celui qui était dans le bench que tu as publié) 3.1s. Cet algorithme (descente récursive) reste donc un peu plus lent, mais c'est tout à fait raisonnable.


Pour voir son intérêt, on peut par exemple se poser la question suivante : comment vérifier si une expression pouvant utiliser plusieurs délimiteurs différents est bien parenthésée ? Par exemple si on autorise les parenthèses et les crochets, "([])" est bien parenthésée mais pas "([)]".

Avec mon code (ou tout code utilisant l'algorithme de descente récursive), il suffit d'une petite modification pour aboutir au résultat suivant :
int check(const char *str, int *i)
{
    char c;
    switch(c = str[(*i)++]) {
    case '\0': return '\0';
    case ']': return '[';
    case ')': return '(';
    case '(':
    case '[':
        if (check(str, i) != c)
            return (-1);
        return check(str, i);
    default: return check(str, i);
    }
}

Avec la méthode de compteur, c'est à ma connaissance infaisable, ou du moins très difficile.

On peut par ailleurs généraliser cet algorithme pour permettre de spécifier (dans un tableau ou une liste associative, en paramètre de la fonction) n'importe quel couple de délimiteurs.
Je ne l'ai pas fait parce que ça n'est pas plus clair, et que ça demande un peu trop de C pour moi (je n'ose pas utiliser de int tab[256] pour indexer par des char parce que j'ai peur que ce ne soit pas dans la norme, le problème des signed char, etc.), mais un codeur en C qui comprend l'algorithme pourra le faire très facilement.
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 12:05:03

Je pense que la méthode la plus simple pour faire les multi-delimiteurs avec le compteur cést de maintenir une pile de chars (Dansl'ordre où ces delimiteurs sont apparut). En gros plus besoin de compteur en plus, suffit de savoir si, quand on a un delimiteur fermant, le dernier ouvrant (haut de la pile) correspond. Si la pil est vide, c'est mal parenthèsé, enfin quand on a fini de chercker la string, si la pile est vide c'est bon.
Grace à la STL, ça serait plutot facile à faire en C++, après en C il faut recoder une pile :)
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 14:33:48

C'est équivalent à la solution récursive, sauf qu'elle utilise la call stack à la place de la pile.
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 16:02:16


Citation : bluestorm


Pour ce qui est des performances, on peut écrire cet algorithme de manière efficace. J'ai fait une version pour voir, qui (bien que je ne l'aie pas spécialement optimisée) est presque aussi performante que l'algorithme naïf :

int check(const char *str, int *i)
{
    switch(str[(*i)++]) {
    case '\0': return 0;
    case ')': return 1;
    case '(':
        if (check(str, i) != 1)
            return (-1);
        return check(str, i);
    default: return check(str, i);
    }
}

int valide(const char *str)
{
    int i = 0;
    return check(str, &i);
}




Au-delà de la question secondaire des performances, voilà qui est BEAUCOUP plus clair et même assez simple à comprendre même si pas forcément évident à produire.

Ci-dessous un code complet avec juste un commentaire ou deux.

#include <stdio.h>

int check(const char *str, int *i)
{
  switch (str[(*i)++])
    {
    case '\0':
      return 0;
    case ')':
      return 1;
    case '(':
      /* si un parenthèse ouverte n'est pas refermée, on renvoie -1 */
      if (check(str, i) != 1)
        return (-1);
      /* sinon on continue après la parenthèse fermante */
      return check(str, i);
    default:
      return check(str, i);
    }
}

int valide(const char *str)
{
  int i = 0;

  return check(str, &i);
}

int main(void)
{
  printf((valide("x(xx(xx)xx)xxxx") == 0) ? "OK\n" : "KO\n");
  printf((valide("x(x)x(xx)xx)xxxx") == 0) ? "OK\n" : "KO\n");
  printf((valide("cccccccccc") == 0) ? "OK\n" : "KO\n");

  return 0;
}

OK
KO
OK



Comme ce post s'adresse aux débutants, je crois qu'il faut un peu détailler. Il faut préciser que ce n'est pas un nouvel algorithme : on mesure toujours l'écart de parenthèses sauf qu'au lieu de mesurer cet écart avec un compteur, on utilise la pile d'implémentation : on empile chaque fois qu'on rencontre une parenthèse ouvrante et on dépile jusqu'à cette dernière lorsqu'on trouve sa parenthèse fermante. C'est donc une implémentation différente de l'algorithme habituel. L'astuce qui consiste à utiliser le pointeur i permet, alors qu'on a dépilé, de garder trace de la position d'examen courante dans la chaîne. Il faut reconnaître que la méthode est assez naturelle car si on ne veut pas compter l'écart entres les ouvrantes et les fermantes (on n'a pas besoin, ce qui compte c'est que l'équilibre soit toujours en faveur des ouvrantes et nul en fin de chaîne), c'est comme ça qu'on procède "à la main", par exemple, soit la formule :

zzz(rr(gg((hhhh)jjjjj)pppp)ooo)hhh


Ce qu'on fait souvent en pratique, c'est parcourir la chaine de gauche à droite jusqu'à ce qu'on trouve une paire de parenthèses séparés par aucune parenthèse, ce qui nous conduit ici à la lettre h et ensuite, mentalement on efface tout le bloc, comme si on avait

zzz(rr(gg(hhhhhhjjjjj)pppp)ooo)hhh


voire (en effaçant vraiment)

zzz(rr(gg(jjjjj)pppp)ooo)hhh


Ensuite, que fait-on ? on décale vers la gauche depuis le début de l'endroit où on a effacé (dans le programme : on dépile) pour chercher la première parenthèse ouvrante et on regarde à droite du bloc effacé (dans le programme : grâce au pointeur vers i) la première parenthèse fermante ce qui nous donne le nouveau bloc (jjjjj), etc.


Citation : bluestorm


Pour voir son intérêt, on peut par exemple se poser la question suivante : comment vérifier si une expression pouvant utiliser plusieurs délimiteurs différents est bien parenthésée ? Par exemple si on autorise les parenthèses et les crochets, "([])" est bien parenthésée mais pas "([)]".



Oui mais le débutant il veut d'abord savoir résoudre le problème dans son contexte simple, tel qu'il a été formulé au départ. Bien sûr, si tu modifies les spécifications en cours de route, le code peut différer.


Citation : bluestorm


Avec la méthode de compteur, c'est à ma connaissance infaisable, ou du moins très difficile.



Je n'ai pas essayé mais ça ne me semble pas très difficile, il faut juste gérer "les croisements". Bien sûr, le code donné pour le premier problème ne s'adaptera peut-être pas aussi facilement que ton code mais une fois de plus, il ne faut pas demander à un débutant d'avoir un code qui résolve un problème qu'il ne s'est même pas posé.


  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 16:22:51

Citation : candide

Je n'ai pas vu quel algorithme tu voulais utiliser. Pour moi, fondamentalement il n'y en a qu'un : il s'agit de parcourir la chaîne de gauche à droite et de voir si l'écart entre les nombres de parenthèses ouvrantes et fermantes reste toujours positif ou nul et s'il est nul à la fin de la chaîne. À partir de là, on obtient le code itératif que presque tout le monde à trouvé, à des détails prêts près. La version récursive est de peu d'intérêt selon moi et elle est triviale à écrire.



Non, ce n'est pas le même algorithme, fondamentalement. Dans l'algo par descente récursive, on cherche à vérifier une syntaxe bien décrite. La grammaire est LL(1), l'idée peut donc se résumer à cela : on identifie à partir du premier symbole rencontré la bonne production et on vérifie si ce qui suit respecte la forme décrite par la production (par exemple, quand on rencontre '(', on doit s'assurer qu'il y a bien un ')' qui lui correspond). La méthode naïve utilise une autre technique, et c'est clairement visible.

Citation : candide

S'il fallait montrer quelque chose de savant (ce qui n'aurait a priori pas sa place dans ce topic destiné aux débutants), je pense que ce serait une implémentation via Flex et Bison. Je pense en effet que c'est sur des exemples simples comme celui-ci que l'on peut apprendre à utiliser ces puissants outils pour être capable de les adapter à des programmes beaucoup complexes.



Pour la n-ième fois, l'idée n'était pas de poster quelque chose de savant, mais quelque chose de nouveau, pour que les débutants puissent s'y intéresser, quel intérêt y a-t-il à ne voir que des implémentations naïves partout ? La question à se poser est "qu'est ce qui apporte véritablement quelque chose dans l'histoire ?" : n versions d'un même algorithme (naïf) ou un autre algorithme, plus logique (à mon goût) et plus modulaire ?

Ceux qui trouvent de l'intérêt à utiliser Flex et Bison sont ceux qui veulent rapidement monter quelque chose sans s'embêter à apprendre les bases de l'analyse lexicale et de l'analyse syntaxique. De plus, l'algorithme serait différent, Bison produisant un analyseur syntaxique ascendant, technique beaucoup plus difficile à comprendre que la descente récursive.

Pourquoi prétendre à tort que ça n'a pas sa place sur ce topic destiné aux débutants ? Tout le site est destiné aux débutants. Faire découvrir de nouvelles choses aux lecteurs, des choses "savantes" ou non, c'est toujours avantageux.

À partir de maintenant, cette discussion continue par MP.

bluestorm : j'avais déjà expliqué l'intérêt de l'algorithme.
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
9 juillet 2009 à 17:20:44

Euh bluestorm t'es sur qu'il marche ton code, avec les crochets en plus là ?

Nan parce que moi j'ai beaucoup de mal à coder ce truc (en itératif donc) et désespéré j'ai essayé le tient, et ta fonction me renvoie soit 0 ou -1 (quand la chaine est trop petite).
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 21:28:53

La fonction renvoie 0 quand l'entrée est correcte, et un code d'erreur sinon. Je ne suis pas sûr qu'il marche, je n'ai fait que deux tests, si tu as une entrée précise où il se trompe ça m'intéresse.


candide > je pense que ce n'est pas le même algorithme dans le sens où la version par descente récursive permet l'accès "gratuit" à plus d'informations, à savoir faire le lien entre une parenthèse ouvrante et sa parenthèse fermante correspondante, ce que l'algorithme avec un seul compteur ne peut pas exprimer.
Effectivement, si on prend une structure de donnée de pile, plus riche qu'un compteur, on peut s'y retrouver (il faut stocker la position des parenthèses et on a notre algorithme en itératif), mais alors amha ce n'est plus "le même" algorithme. Cependant, tout cela c'est de la terminologie, ça n'est pas très important de savoir ce qu'on appelle "le même algorithme".

Par ailleurs oui, un débutant répondant seulement au problème ne verra pas forcément d'intérêt à l'extensivité de cette méthode. Je ne dis pas que cet algorithme est un meilleur choix pour les débutants, seulement qu'il a des qualités propres et n'est pas juste "une solution plus lente et plus lourde pour faire la même chose" comme le pensait 21 à la base.

Après on peut débattre de sa pertinence ici, pour ma part je trouve que Shareman n'a pas tort de présenter des choses différentes est peut-être un peu plus poussées pour les gens que ça intéresse, que ça peut profiter à tout le monde et qu'il est un peu regrettable que le débat ressemble maintenant à un champ de bataille, alors qu'en discutant calmement et en posant les bonnes questions il aurait sans doute réussi à expliquer clairement son affaire aux gens que ça intéresse. Après, je veux bien comprendre que ce n'est pas le but de ce topic, mais ça ce n'est pas mon problème.
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
9 juillet 2009 à 21:52:54

Ta fonction renvoie 0 quand la chaine a de bonnes parenthèses et -1 ou 1 quand c'est pas bon.
Mais les crochets ou pas ça donne toujours 0 même avec que des crochets ouvrant.

Présenter des nouvelles techniques ça peut être cool mais alors faudrait bien choisir son exemple, là moi j'ai rien compris le code donné n'avait que des inconvénients donc bon ça donnait l'impression de juste vouloir nous épater, apparemment me suis trompé.
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 22:18:27

Hors-Sujet : A quand l'exercice du mois de juillet ??
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
9 juillet 2009 à 22:25:07

Citation : frager50

Hors-Sujet : A quand l'exercice du mois de juillet ??


+1 , même si le débat ci-dessus est intéressant, il faudrait se refocaliser sur l'unique but du topic : proposer des exercices au débutants.
  • Partager sur Facebook
  • Partager sur Twitter
9 juillet 2009 à 22:34:42

J'envisage de poster une correction demain sinon samedi. On (avec Eusebus) enchaînera directement après avec l'exercice de juillet. :)
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
16 juillet 2009 à 13:56:45

Un petit up pour avoir quelques nouvelles ^^
  • Partager sur Facebook
  • Partager sur Twitter
18 juillet 2009 à 1:08:10

Hum, j'ai mal spéculé pour le week-end dernier, je n'ai pas trop trouvé le temps d'écrire la correction. Je vais _essayer_ de le faire demain. :-°
  • Partager sur Facebook
  • Partager sur Twitter