Partage
  • Partager sur Facebook
  • Partager sur Twitter

strtok zestedesavoir

Sujet résolu
    9 septembre 2021 à 20:21:50

    Bonjour,

    J'essaie de faire la fonction strtok sur le cours de zds mais je sais pas, ma fonction me renvoie rien je comprend pas. Si vous pouvez m'aider svp

    Aussi, pour l'instant elle fonction que pour 1char mais je rectifirai ça plus tard..

    Voici mon code:

    char    *f_bzero(char *src, size_t n)
    {
        while (src[n])
        {
            src[n] = 0;
            n++;
        }
        src[n] = 0;
        return (src);
    }
    
    char    *f_move(char *src, size_t n)
    {
        size_t i;
    
        i = 0;
        while (src[n])
        {
            src[i] = src[n];
            n++;
            i++;
        }
        src[i] = 0;
        return (src);
    }
    
    char    *f_strtok(char *src, char *sep)
    {
        if (!sep)
            return (NULL);
    
        static char *line;
        if (src)
            line = src;
    
        size_t  i;
        size_t  j;
        int     index;
    
        index = -1;
        i = 0;
        while (line[i])
        {
            j = 0;
            while (sep[j])
            {
                if (sep[j] == line[i])
                    index = i;
                j++;
            }
            if (index > -1)
            {
                char *tmp = f_bzero(src, i);
                f_move(line, i + 1);
                return (tmp);
            }
            index = -1;
            i++;
        }
        return (line);
    }
    
    int main(void)
    {
        char chaine[] = "un,deux,trois";
        char *p = f_strtok(chaine, ",");
    
        for (size_t i = 0; p != NULL; ++i)
        {
            printf("%zu : %s\n", i, p);
            p = f_strtok(NULL , ",");
        }
        return (0);
    }

    Merci..

    • Partager sur Facebook
    • Partager sur Twitter
      9 septembre 2021 à 20:40:50

      Hello,

      strtok() se contente de mettre un \0 lorsqu'un des délimiteurs est rencontré. Pourquoi faire des déplacements de caractères (via ta fonction f_move() ) ?

      • Partager sur Facebook
      • Partager sur Twitter

      Il y a ceux qui font des sauvegardes, et ceux qui n'ont pas encore eu d'incident....

        9 septembre 2021 à 22:15:04

        Merci pour votre retour,

        C'est vrai que c'est plus simple comme ça lol

        char    *f_strtok(char *src, char *sep)
        {
            if (!sep)
                return (NULL);
        
            static char *line;
            if (src)
                line = src;
        
            size_t  i;
            size_t  j;
        
            i = 0;
            while (line[i])
            {
                j = 0;
                while (sep[j])
                {
                    if (sep[j] == line[i])
                    {
                        line[i] = 0;
                        if (!sep[j + 1])
                        {
                            char *tmp = line;
                            line = tmp + i + 1;
                            return (tmp);
                        }
                    }
                    j++;
                }
                i++;
            }
            return (line);
        }

        Du coup, ça fonctionne mais quand dois-je renvoyer NULL ?

        Merci pour votre aide !

        • Partager sur Facebook
        • Partager sur Twitter
          9 septembre 2021 à 22:40:52

          Bonjour,

          Quand ton code arrive à la ligne 33, c'est qu'il n'y a plus de séparateur. Il faut bien retourner line. Mais lors de l'appel suivant, la fonction devra retourner immédiatement NULL, il reste à modifier le code pour se souvenir d'être arrivé ligne 33 (en modifiant une dernière fois line.)

          Et ta ligne 22 devrait être supprimée.

          • Partager sur Facebook
          • Partager sur Twitter

          En recherche d'emploi.

            9 septembre 2021 à 23:26:43

            Dalfab a écrit:

            Bonjour,

            Quand ton code arrive à la ligne 33, c'est qu'il n'y a plus de séparateur. Il faut bien retourner line. Mais lors de l'appel suivant, la fonction devra retourner immédiatement NULL, il reste à modifier le code pour se souvenir d'être arrivé ligne 33 (en modifiant une dernière fois line.)

            Et ta ligne 22 devrait être supprimée.


            Merci !!

            c'est impec ;)

            char    *f_strtok(char *src, char *sep)
            {
                if (!sep)
                    return (NULL);
            
                static char *line;
                if (src)
                    line = src;
                if (!line)
                    return (NULL);
            
                size_t  i;
                size_t  j;
            
                i = 0;
                while (line[i])
                {
                    j = 0;
                    while (sep[j])
                    {
                        if (sep[j] == line[i])
                        {
                            line[i] = 0;
                            if (!sep[j + 1])
                            {
                                char *tmp = line;
                                line = tmp + i + 1;
                                return (tmp);
                            }
                        }
                        j++;
                    }
                    i++;
                }
                char *tmp = line;
                line = NULL;
                return (tmp);
            }

            Mais du coup, j'ai une question: je viens de regarder le code correction et je me demande en quoi le code du zds est-il mieux que le mien ? Pouvez-vous détailler vos explications svp car pour moi, je préfère la mienne car je la trouve plus lisible.. Après est-il mieux de travailler avec le pointeur directement ou via un index ?

            char *xstrtok(char *chaine, char *liste)
            {
                static char *dernier;
                char *base = (chaine != NULL) ? chaine : dernier;
            
                if (base == NULL)
                    return NULL;
            
            separateur_au_debut:
                for (char *sep = liste; *sep != '\0'; ++sep)
                    if (*base == *sep)
                    {
                        ++base;
                        goto separateur_au_debut;
                    }
            
                if (*base == '\0')
                {
                    dernier = NULL;
                    return NULL;
                }
            
                for (char *s = base; *s != '\0'; ++s)
                    for (char *sep = liste; *sep != '\0'; ++sep)
                        if (*s == *sep)
                        {
                            *s = '\0';
                            dernier = s + 1;
                            return base;
                        }
            
                dernier = NULL;
                return base;
            }

            -
            Edité par lucass91 10 septembre 2021 à 12:50:11

            • Partager sur Facebook
            • Partager sur Twitter
              10 septembre 2021 à 12:55:20

              EDIT

              -
              Edité par nicollabaoo 10 septembre 2021 à 12:55:33

              • Partager sur Facebook
              • Partager sur Twitter
                10 septembre 2021 à 17:34:02

                Bonjour,

                En comparant les deux codes, je dirais que ton code est globalement meilleur et plus clair. Sauf que le tien ne fonctionne pas au cause de ta ligne 24 qu'il faut supprimer (je ne vois pas ce que tu attends de ton test.)

                Le code solution veut gérer le cas particulier d'un séparateur en début de chaine que tu n'as pas, mais je ne sais pas si c'est ce que fait la fonction strtok() (et pourquoi rien pour des séparateurs en fin ou des séparateurs accolés au milieu?), il faudrait lire la doc ou vérifier qui a juste.

                Si la chaine est vide ou ne contient que des séparateurs, la solution retourne immédiatement NULL, alors que ton code retourne une chaine vide, puis NULL. Là aussi je pense que ton code est plus proche de strtok(), à confirmer.

                Ton code se protège du cas où la liste des séparateurs est un pointeur NULL. Là encore voir ce que fait strtok(). Je penses que tu n'es pas conforme bien qu'un code sécuritaire soit plutôt positif. Mais ne jamais oublier qu'un programme doit toujours respecter ce qui est spécifié.

                Ce qu'il faut prendre dans le code solution que tu n'as pas:
                - distinguer les 2 variables base et dernier permet de mieux comprendre;
                - utilise des boucles for plutôt que des while qui est plus lisible;
                - définit les variables au plus près de leur utilisation (ta variable j est définie hors de la boucle sur i);
                - le caractère nul s'écrit plutôt '\0', la valeur 0 marche aussi mais c'est bien de rappeler que c'est un caractère;
                - inutile de mettre entre parenthèses l'expression passée à return.

                Ce qui est meilleur dans ton code :
                - trop de return dans la solution, on s'y perd;
                - inutile de travailler sur des pointeurs, utiliser des indices est tout aussi optimum et plus lisible selon moi;
                - ne jamais jamais utiliser de goto à moins de ne vraiment pas avoir le choix (dans les cas très très rares où ça améliore la lisibilité ou la maintenabilité.)
                - éviter d'avoir des variables mal nommées ou courtes sans raison (s ou liste sont mal nommées dans la solution, tmp et ligne chez toi mais moins gênant) le cas de i et j est une habitude acceptable.

                Et *sep ou *ligne devraient avoir le type const char, pas le type char car en aucun cas on ne va modifier les délimiteurs. Mais je pense que le cours ne parle pas du tout de cette notion. En réalité la fonction strtok() est déclarée:

                char *strtok( char *restrict str, const char *restrict delim );

                (le mot restrict est à apprendre en mode expert, mais le const est à connaître)

                Edit: Après vérification, le code de la solution fait bien exactement la même chose que strtok(), il est donc finalement le gagnant ;-)

                -
                Edité par Dalfab 10 septembre 2021 à 17:44:30

                • Partager sur Facebook
                • Partager sur Twitter

                En recherche d'emploi.

                  10 septembre 2021 à 23:42:02

                  Bonjour,

                  je rejoins Dalfab sur le point qu'il est nécessaire de désapprendre la norme 42 (while vs for, return parenthésé, …).

                  La solution de zds est tout sauf «classique».

                  Lorsqu'on écrit du code pour une bibliothèque alors on applique le principe DRY = Don't Repeat Yourself : on écrit des fonctions plutôt que de pisser du code. Par exemple, la libc contient une autre version de strtok qu'on dit réentrante et qui se nomme strtok_r. La fonction strtok doit donc s'écrire dans un tel contexte :

                  char *strtok(char *restrict str, const char *restrict delim)
                  {
                      static char *saveptr;
                      return strtok_r(str, delim, &saveptr);
                  }

                  La lib fournit aussi deux autres fonctions utiles dans notre cas : strspn et strcspn :

                  NAME
                         strspn, strcspn - get length of a prefix substring
                  
                  SYNOPSIS
                         #include <string.h>
                  
                         size_t strspn(const char *s, const char *accept);
                         size_t strcspn(const char *s, const char *reject);
                  
                  DESCRIPTION
                         The  strspn()  function calculates the length (in bytes) of the initial segment of s which consists entirely of bytes in
                         accept.
                  
                         The strcspn() function calculates the length of the initial segment of s which consists entirely of bytes not in reject.
                  
                  RETURN VALUE
                         The strspn() function returns the number of bytes in the initial segment of s which consist only of bytes from accept.
                  
                         The strcspn() function returns the number of bytes in the initial segment of s which are not in the string reject.
                  
                  

                  du coup strtok_r peut s'écrire ainsi :

                  char *strtok_r(char *restrict str, const char *restrict delim, char **restrict saveptr)
                  {
                      if (str==NULL) str=*saveptr;
                      if (*str=='\0') {
                          *saveptr=str;
                          return NULL;
                      }
                  
                      // on saute les delimiteurs au début
                      str += strspn(str, delim);
                      if (*str=='\0') {
                          *saveptr=str;
                          return NULL;
                      }
                  
                      // on trouve la fin du token
                      char *end=str + strcspn(str,delim);
                      if (*end == '\0')
                      {
                        *saveptr = end;
                        return str;
                      }
                  
                      *end='\0';
                      *saveptr=end+1;
                      return str;
                  }

                  Il nous reste deux fonctions de la libc à écrire : strspn et strcpsn. Elles sont très imilaires : la première compte le nombre de caractères au début d'une chaîne qui sont dans un ensemble, la seconde qui n'en sont pas … cela sent la spécialisation d'une fonction plus générale que l'on pourrait nommer strskip :

                  size_t strskip(const char *string, const char *set, bool accept)
                  {
                      char *strchar;
                      for(strchar=string; *strchar; strchar++) {
                          char *setchar;
                          for(setchar=set; *setchar && *setchar!=*strchar; setchar++)
                              ;
                  
                          if ( (accept && *setchar=='\0') || (!accept && *setchar!='\0') ) break;
                      }
                      return strchar-string;
                  }

                  et du coup on peut avoir comme implémentation :

                  size_t strspn(const char *s, const char *accept)
                  {
                      return strskip(s, accept, true);
                  }
                  
                  size_t strcspn(const char *s, const char *reject)
                  {
                      return strskip(s, reject, false);
                  }
                  



                  Bon j'ai écrit tout ça à main levée … tout est à prendre avec des pincettes en gardant à l'esprit que ce n'est que mon opinion.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    12 septembre 2021 à 18:30:34

                    Profitons de l'occasion pour expliquer ces histoires de réentrance.

                    En gros, dans les débuts, C a été conçu pour UNIX, dans lequel les programmes sont lancés sous forme d'un processus qui peut lui même lancer d'autres processus (par fork).

                    Quand un processus (père) en crée un autre, le processus fils démarre avec un espace mémoire qui est une copie de celui du père, et à partir de là il vit sa vie.

                    Ce qui fait que les variables statiques qui s'y trouvent sont séparées. Le

                    static char *saveptr;

                    de l'un est indépendant du saveptr de l'autre. Chacun ses données.

                    --

                    Après ça on s'est mis à utiliser aussi des "processus légers" (threads) qui eux, travaillent dans le même espace mémoire que le processus père. Et donc partagent les variables statiques.

                    Et ça peut foutre le bordel.

                    Exemple

                    #include <pthread.h>
                    #include <stdio.h>
                    #include <unistd.h>
                    #include <string.h>
                    
                    struct data {
                    	char string[100];
                    	char numero;
                    };
                    
                    void * task(void * ptr) {
                    	struct data * d = ptr;
                    	for (char *p = strtok(d->string, ","); p != NULL; p = strtok(NULL, ",")) {
                    		printf("%d-> %s\n", d->numero, p);
                    		sleep(1);
                    	}
                    }
                    
                    void main() {
                    	struct data d1 = {"un,deux,trois", 1};
                    	struct data d2 = {"pif,paf", 2};	
                    	
                    	pthread_t t1, t2;
                    	pthread_create(&t1, NULL,  task, &d1);
                    	pthread_create(&t2, NULL,  task, &d2);	
                    	pthread_join(t1, NULL) ;
                    	pthread_join(t2, NULL) ;
                    }

                    programme lance deux threads en parallèle. Le premier devrait afficher

                    1-> un
                    2-> deux
                    3-> trois

                    avec un petit délai pour chaque et le second quelque chose du même genre.

                    Sauf que quand on les lance, ça fait ça :

                    1-> un
                    2-> pif
                    1-> paf
                    


                    Ce qui se passe : la variable statique qui sert à strtok est commune aux deux processus. Le second change sa valeur, ce qui fait que le premier processus travaille avec la chaîne du second. Halleluia.

                    Alors que si on avait appelé

                    	task(& d1);
                    	task(& d2);

                    ça marchait très bien.

                    Conclusion : la fonction strtok n'est pas capable de fonctionner correctement quand elle est appelée simultanément dans plusieurs processus d'un programme (thread safe). On dit qu'elle n'est pas réentrante.

                    Il y a d'autres trucs comme ça, il faut y faire attention. Pendant longtemps, c'était le cas de la variable globale errno, vous imaginez ?

                    PS: l'astuce géniale d'avoir une variable statique et deux façons d'appeler la même fonction, c'était pourri au départ.

                    Une bonne lecture : https://www.ibm.com/docs/en/aix/7.2?topic=programming-writing-reentrant-threadsafe-code




                    -
                    Edité par michelbillaud 12 septembre 2021 à 18:52:39

                    • Partager sur Facebook
                    • Partager sur Twitter
                      12 septembre 2021 à 21:48:00

                      Attention, la notion de multi-thread safety et de réentrance sont distinctes. Une fonction non réentrante comme strtok peut causer des soucis même avec un seul thread si elle est appelée lors d'une interruption logicielle par exemple. On peut construire des fonctions réentrantes mais non multi-thread safe, non réentrantes mais multi-thread safe, ou les deux à la fois ou ni une ni l'autre …

                      • Partager sur Facebook
                      • Partager sur Twitter
                        13 septembre 2021 à 1:07:22

                        White Crow a écrit:

                        Attention, la notion de multi-thread safety et de réentrance sont distinctes. Une fonction non réentrante comme strtok peut causer des soucis même avec un seul thread si elle est appelée lors d'une interruption logicielle par exemple. On peut construire des fonctions réentrantes mais non multi-thread safe, non réentrantes mais multi-thread safe, ou les deux à la fois ou ni une ni l'autre …


                        C'est encore pire que ça. Si ma fonction f() appelle strtok(), alors elle casse le contexte de strtok() de toute fonction appelant f().
                        Les fonctions non réentrantes, pour moi c'est parmi les pires cochonneries qu'on puisse trouver dans un code.

                        A part ça, un exemple de strtok perso:

                        char *my_strtok(char *restrict str, const char *restrict delim)
                        {
                                static char *last_str = NULL;
                                if (!str)
                                        str = last_str;
                        
                                char *start = str + strspn(str, delim);
                                char *end = start + strcspn(start, delim);
                        
                                if (start == end)
                                        return NULL;
                                if (*end != '\0') {
                                        *end++ = '\0';
                                }
                                last_str = end;
                        
                                return start;
                        }
                        



                        -
                        Edité par Marc Mongenet 13 septembre 2021 à 1:53:37

                        • Partager sur Facebook
                        • Partager sur Twitter

                        strtok zestedesavoir

                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                        • Editeur
                        • Markdown