Chaîne divisée avec des délimiteurs en C


155

Comment écrire une fonction pour fractionner et renvoyer un tableau pour une chaîne avec des délimiteurs dans le langage de programmation C?

char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
str_split(str,',');

25
Vous pouvez utiliser la strtokfonction de la bibliothèque standard pour réaliser la même chose.
Daniel Kamil Kozar le


Un commentaire ... le point clé pour une strtok()fonction familiale est la compréhension static variablesen C. c'est-à-dire comment ils se comportent entre les appels de fonction successifs dans lesquels ils sont utilisés. Voir mon code ci
fnisi

Réponses:


165

Vous pouvez utiliser la strtok()fonction pour fractionner une chaîne (et spécifier le délimiteur à utiliser). Notez que strtok()cela modifiera la chaîne qui y est passée. Si la chaîne d'origine est requise ailleurs, faites-en une copie et transmettez-la à strtok().

ÉDITER:

Exemple (notez qu'il ne gère pas les délimiteurs consécutifs, "JAN ,,, FEB, MAR" par exemple):

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

char** str_split(char* a_str, const char a_delim)
{
    char** result    = 0;
    size_t count     = 0;
    char* tmp        = a_str;
    char* last_comma = 0;
    char delim[2];
    delim[0] = a_delim;
    delim[1] = 0;

    /* Count how many elements will be extracted. */
    while (*tmp)
    {
        if (a_delim == *tmp)
        {
            count++;
            last_comma = tmp;
        }
        tmp++;
    }

    /* Add space for trailing token. */
    count += last_comma < (a_str + strlen(a_str) - 1);

    /* Add space for terminating null string so caller
       knows where the list of returned strings ends. */
    count++;

    result = malloc(sizeof(char*) * count);

    if (result)
    {
        size_t idx  = 0;
        char* token = strtok(a_str, delim);

        while (token)
        {
            assert(idx < count);
            *(result + idx++) = strdup(token);
            token = strtok(0, delim);
        }
        assert(idx == count - 1);
        *(result + idx) = 0;
    }

    return result;
}

int main()
{
    char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    char** tokens;

    printf("months=[%s]\n\n", months);

    tokens = str_split(months, ',');

    if (tokens)
    {
        int i;
        for (i = 0; *(tokens + i); i++)
        {
            printf("month=[%s]\n", *(tokens + i));
            free(*(tokens + i));
        }
        printf("\n");
        free(tokens);
    }

    return 0;
}

Production:

$ ./main.exe
months=[JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC]

month=[JAN]
month=[FEB]
month=[MAR]
month=[APR]
month=[MAY]
month=[JUN]
month=[JUL]
month=[AUG]
month=[SEP]
month=[OCT]
month=[NOV]
month=[DEC]

60
Salut! le strtokest marqué comme obsolète par strsep(3)dans la page de manuel.
osgx

4
Comme cela peut être la question / réponse canonique sur Stack Overflow pour cela, n'y a-t-il pas quelques mises en garde concernant le multi-threading utilisant strtok?
Peter Mortensen

3
@osgx Selon cette page, strsepest un remplacement pour strtok, mais strtokest préféré pour la portabilité. Donc, à moins que vous n'ayez besoin de support pour les champs vides ou de fractionner plusieurs chaînes à la fois, strtokc'est un meilleur choix.

4
@Dojo: Il s'en souvient; c'est l'une des raisons pour lesquelles c'est problématique. Il serait préférable d'utiliser strtok_s()(Microsoft, C11 Annexe K, facultatif) ou strtok_r()(POSIX) que plain strtok(). Plain strtok()est mal dans une fonction de bibliothèque. Aucune fonction appelant la fonction de bibliothèque ne peut être utilisée strtok()à ce moment, et aucune fonction appelée par la fonction de bibliothèque ne peut être appelée strtok().
Jonathan Leffler

3
Juste une note qui strtok()n'est pas thread-safe (pour les raisons mentionnées par @JonathanLeffler) et donc toute cette fonction n'est pas thread-safe. Si vous essayez de l'utiliser dans un environnement difficile, vous obtiendrez des résultats erratiques et imprévisibles. Le remplacement strtok()pour strtok_r()corrige ce problème.
Sean W

70

Je pense que strsepc'est toujours le meilleur outil pour cela:

while ((token = strsep(&str, ","))) my_fn(token);

C'est littéralement une ligne qui divise une chaîne.

Les parenthèses supplémentaires sont un élément stylistique pour indiquer que nous testons intentionnellement le résultat d'une affectation, pas un opérateur d'égalité ==.

Pour que ce modèle fonctionne, tokenet les strdeux ont du type char *. Si vous avez commencé avec une chaîne littérale, vous voudrez d'abord en faire une copie:

// More general pattern:
const char *my_str_literal = "JAN,FEB,MAR";
char *token, *str, *tofree;

tofree = str = strdup(my_str_literal);  // We own str's memory now.
while ((token = strsep(&str, ","))) my_fn(token);
free(tofree);

Si deux délimiteurs apparaissent ensemble dans str, vous obtiendrez une tokenvaleur correspondant à la chaîne vide. La valeur de strest modifiée en ce que chaque délimiteur rencontré est écrasé par un octet zéro - une autre bonne raison de copier la chaîne analysée en premier.

Dans un commentaire, quelqu'un a suggéré que strtokc'est mieux que strsepparce que strtokc'est plus portable. Ubuntu et Mac OS X ont strsep; il est sûr de deviner que d'autres systèmes unixy le font aussi. Windows manque strsep, mais il a strbrkce qui permet ce strsepremplacement court et doux :

char *strsep(char **stringp, const char *delim) {
  if (*stringp == NULL) { return NULL; }
  char *token_start = *stringp;
  *stringp = strpbrk(token_start, delim);
  if (*stringp) {
    **stringp = '\0';
    (*stringp)++;
  }
  return token_start;
}

Voici une bonne explication de strsepvs strtok. Les avantages et les inconvénients peuvent être jugés subjectivement; cependant, je pense que c'est un signe révélateur qui a strsepété conçu pour remplacer strtok.


3
Plus précisément sur la portabilité: il ne s'agit pas de POSIX 7 , mais de BSD, et implémenté sur la glibc .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

J'étais sur le point de demander ... Le C de Pelle a strdup (), mais pas strsep ().
rdtsc

1
pourquoi celui-là tofreeest-il libre et non str?
Sdlion

1
Vous ne pouvez pas libérer strcar sa valeur peut être modifiée par des appels à strsep(). La valeur de tofreesystématiquement pointe vers le début de la mémoire que vous souhaitez libérer.
Tyler

26

Tokenizer de chaîne ce code devrait vous mettre dans la bonne direction.

int main(void) {
  char st[] ="Where there is will, there is a way.";
  char *ch;
  ch = strtok(st, " ");
  while (ch != NULL) {
  printf("%s\n", ch);
  ch = strtok(NULL, " ,");
  }
  getch();
  return 0;
}

13

La méthode ci-dessous fera tout le travail (allocation de mémoire, comptage de la longueur) pour vous. Plus d'informations et une description peuvent être trouvées ici - Implémentation de la méthode Java String.split () pour diviser la chaîne C

int split (const char *str, char c, char ***arr)
{
    int count = 1;
    int token_len = 1;
    int i = 0;
    char *p;
    char *t;

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
            count++;
        p++;
    }

    *arr = (char**) malloc(sizeof(char*) * count);
    if (*arr == NULL)
        exit(1);

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
        {
            (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
            if ((*arr)[i] == NULL)
                exit(1);

            token_len = 0;
            i++;
        }
        p++;
        token_len++;
    }
    (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
    if ((*arr)[i] == NULL)
        exit(1);

    i = 0;
    p = str;
    t = ((*arr)[i]);
    while (*p != '\0')
    {
        if (*p != c && *p != '\0')
        {
            *t = *p;
            t++;
        }
        else
        {
            *t = '\0';
            i++;
            t = ((*arr)[i]);
        }
        p++;
    }

    return count;
}

Comment l'utiliser:

int main (int argc, char ** argv)
{
    int i;
    char *s = "Hello, this is a test module for the string splitting.";
    int c = 0;
    char **arr = NULL;

    c = split(s, ' ', &arr);

    printf("found %d tokens.\n", c);

    for (i = 0; i < c; i++)
        printf("string #%d: %s\n", i, arr[i]);

    return 0;
}

4
Huh Programmeur trois étoiles :)) Cela semble intéressant.
Michi

Lorsque je fais cela, cela ajoute trop au dernier jeton ou lui alloue trop de mémoire. Voici la sortie: found 10 tokens. string #0: Hello, string #1: this string #2: is string #3: a string #4: test string #5: module string #6: for string #7: the string #8: string string #9: splitting.¢
KeizerHarm

2
Cet exemple présente plusieurs fuites de mémoire. Pour toute personne lisant ceci, n'utilisez pas cette approche. Préférez plutôt les approches de tokenisation strtok ou strsep.
Jorma Rebane

7

Voici mes deux cents:

int split (const char *txt, char delim, char ***tokens)
{
    int *tklen, *t, count = 1;
    char **arr, *p = (char *) txt;

    while (*p != '\0') if (*p++ == delim) count += 1;
    t = tklen = calloc (count, sizeof (int));
    for (p = (char *) txt; *p != '\0'; p++) *p == delim ? *t++ : (*t)++;
    *tokens = arr = malloc (count * sizeof (char *));
    t = tklen;
    p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
    while (*txt != '\0')
    {
        if (*txt == delim)
        {
            p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
            txt++;
        }
        else *p++ = *txt++;
    }
    free (tklen);
    return count;
}

Usage:

char **tokens;
int count, i;
const char *str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

count = split (str, ',', &tokens);
for (i = 0; i < count; i++) printf ("%s\n", tokens[i]);

/* freeing tokens */
for (i = 0; i < count; i++) free (tokens[i]);
free (tokens);

3
oh boi, trois pointeurs! J'ai déjà peur de l'utiliser lol c'est juste moi, je ne suis pas très bon avec les pointeurs en c.
Hafiz Temuri

Merci mec, toutes les réponses strtok ci-dessus n'ont pas fonctionné dans mon cas, même après beaucoup d'efforts, et votre code fonctionne comme un charme!
hmmftg

4

Dans l'exemple ci-dessus, il y aurait un moyen de retourner un tableau de chaînes terminées par null (comme vous le souhaitez) en place dans la chaîne. Cela ne permettrait cependant pas de passer une chaîne littérale, car elle devrait être modifiée par la fonction:

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

char** str_split( char* str, char delim, int* numSplits )
{
    char** ret;
    int retLen;
    char* c;

    if ( ( str == NULL ) ||
        ( delim == '\0' ) )
    {
        /* Either of those will cause problems */
        ret = NULL;
        retLen = -1;
    }
    else
    {
        retLen = 0;
        c = str;

        /* Pre-calculate number of elements */
        do
        {
            if ( *c == delim )
            {
                retLen++;
            }

            c++;
        } while ( *c != '\0' );

        ret = malloc( ( retLen + 1 ) * sizeof( *ret ) );
        ret[retLen] = NULL;

        c = str;
        retLen = 1;
        ret[0] = str;

        do
        {
            if ( *c == delim )
            {
                ret[retLen++] = &c[1];
                *c = '\0';
            }

            c++;
        } while ( *c != '\0' );
    }

    if ( numSplits != NULL )
    {
        *numSplits = retLen;
    }

    return ret;
}

int main( int argc, char* argv[] )
{
    const char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

    char* strCpy;
    char** split;
    int num;
    int i;

    strCpy = malloc( strlen( str ) * sizeof( *strCpy ) );
    strcpy( strCpy, str );

    split = str_split( strCpy, ',', &num );

    if ( split == NULL )
    {
        puts( "str_split returned NULL" );
    }
    else
    {
        printf( "%i Results: \n", num );

        for ( i = 0; i < num; i++ )
        {
            puts( split[i] );
        }
    }

    free( split );
    free( strCpy );

    return 0;
}

Il y a probablement une façon plus soignée de le faire, mais vous voyez l'idée.


3

Cette fonction prend une chaîne char * et la divise par le déliminateur. Il peut y avoir plusieurs déliminateurs d'affilée. Notez que la fonction modifie la chaîne orignal. Vous devez d'abord faire une copie de la chaîne d'origine si vous souhaitez que l'original reste inchangé. Cette fonction n'utilise aucun appel de fonction cstring, elle peut donc être un peu plus rapide que d'autres. Si vous ne vous souciez pas de l'allocation de mémoire, vous pouvez allouer des sub_strings en haut de la fonction avec la taille strlen (src_str) / 2 et (comme la "version" c ++ mentionnée) sauter la moitié inférieure de la fonction. Si vous faites cela, la fonction est réduite à O (N), mais la méthode d'optimisation de la mémoire illustrée ci-dessous est O (2N).

La fonction:

char** str_split(char *src_str, const char deliminator, size_t &num_sub_str){
  //replace deliminator's with zeros and count how many
  //sub strings with length >= 1 exist
  num_sub_str = 0;
  char *src_str_tmp = src_str;
  bool found_delim = true;
  while(*src_str_tmp){
    if(*src_str_tmp == deliminator){
      *src_str_tmp = 0;
      found_delim = true;
    }
    else if(found_delim){ //found first character of a new string
      num_sub_str++;
      found_delim = false;
      //sub_str_vec.push_back(src_str_tmp); //for c++
    }
    src_str_tmp++;
  }
  printf("Start - found %d sub strings\n", num_sub_str);
  if(num_sub_str <= 0){
    printf("str_split() - no substrings were found\n");
    return(0);
  }

  //if you want to use a c++ vector and push onto it, the rest of this function
  //can be omitted (obviously modifying input parameters to take a vector, etc)

  char **sub_strings = (char **)malloc( (sizeof(char*) * num_sub_str) + 1);
  const char *src_str_terminator = src_str_tmp;
  src_str_tmp = src_str;
  bool found_null = true;
  size_t idx = 0;
  while(src_str_tmp < src_str_terminator){
    if(!*src_str_tmp) //found a NULL
      found_null = true;
    else if(found_null){
      sub_strings[idx++] = src_str_tmp;
      //printf("sub_string_%d: [%s]\n", idx-1, sub_strings[idx-1]);
      found_null = false;
    }
    src_str_tmp++;
  }
  sub_strings[num_sub_str] = NULL;

  return(sub_strings);
}

Comment l'utiliser:

  char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char *str = strdup(months);
  size_t num_sub_str;
  char **sub_strings = str_split(str, ',', num_sub_str);
  char *endptr;
  if(sub_strings){
    for(int i = 0; sub_strings[i]; i++)
      printf("[%s]\n", sub_strings[i]);
  }
  free(sub_strings);
  free(str);

3
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

/**
 *  splits str on delim and dynamically allocates an array of pointers.
 *
 *  On error -1 is returned, check errno
 *  On success size of array is returned, which may be 0 on an empty string
 *  or 1 if no delim was found.  
 *
 *  You could rewrite this to return the char ** array instead and upon NULL
 *  know it's an allocation problem but I did the triple array here.  Note that
 *  upon the hitting two delim's in a row "foo,,bar" the array would be:
 *  { "foo", NULL, "bar" } 
 * 
 *  You need to define the semantics of a trailing delim Like "foo," is that a
 *  2 count array or an array of one?  I choose the two count with the second entry
 *  set to NULL since it's valueless.
 *  Modifies str so make a copy if this is a problem
 */
int split( char * str, char delim, char ***array, int *length ) {
  char *p;
  char **res;
  int count=0;
  int k=0;

  p = str;
  // Count occurance of delim in string
  while( (p=strchr(p,delim)) != NULL ) {
    *p = 0; // Null terminate the deliminator.
    p++; // Skip past our new null
    count++;
  }

  // allocate dynamic array
  res = calloc( 1, count * sizeof(char *));
  if( !res ) return -1;

  p = str;
  for( k=0; k<count; k++ ){
    if( *p ) res[k] = p;  // Copy start of string
    p = strchr(p, 0 );    // Look for next null
    p++; // Start of next string
  }

  *array = res;
  *length = count;

  return 0;
}

char str[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,";

int main() {
  char **res;
  int k=0;
  int count =0;
  int rc;

  rc = split( str, ',', &res, &count );
  if( rc ) {
    printf("Error: %s errno: %d \n", strerror(errno), errno);
  }

  printf("count: %d\n", count );
  for( k=0; k<count; k++ ) {
    printf("str: %s\n", res[k]);
  }

  free(res );
  return 0;
}

3

Voici mon strtok()implémentation de la bibliothèque zString . zstring_strtok()diffère des bibliothèques standardstrtok() par la manière dont elle traite les délimiteurs consécutifs.

Jetez un œil au code ci-dessous, assurez-vous que vous aurez une idée de son fonctionnement (j'ai essayé d'utiliser autant de commentaires que possible)

char *zstring_strtok(char *str, const char *delim) {
    static char *static_str=0;      /* var to store last address */
    int index=0, strlength=0;       /* integers for indexes */
    int found = 0;                  /* check if delim is found */

    /* delimiter cannot be NULL
    * if no more char left, return NULL as well
    */
    if (delim==0 || (str == 0 && static_str == 0))
        return 0;

    if (str == 0)
        str = static_str;

    /* get length of string */
    while(str[strlength])
        strlength++;

    /* find the first occurance of delim */
    for (index=0;index<strlength;index++)
        if (str[index]==delim[0]) {
            found=1;
            break;
        }

    /* if delim is not contained in str, return str */
    if (!found) {
        static_str = 0;
        return str;
    }

    /* check for consecutive delimiters
    *if first char is delim, return delim
    */
    if (str[0]==delim[0]) {
        static_str = (str + 1);
        return (char *)delim;
    }

    /* terminate the string
    * this assignmetn requires char[], so str has to
    * be char[] rather than *char
    */
    str[index] = '\0';

    /* save the rest of the string */
    if ((str + index + 1)!=0)
        static_str = (str + index + 1);
    else
        static_str = 0;

        return str;
}

Voici un exemple d'utilisation ...

  Example Usage
      char str[] = "A,B,,,C";
      printf("1 %s\n",zstring_strtok(s,","));
      printf("2 %s\n",zstring_strtok(NULL,","));
      printf("3 %s\n",zstring_strtok(NULL,","));
      printf("4 %s\n",zstring_strtok(NULL,","));
      printf("5 %s\n",zstring_strtok(NULL,","));
      printf("6 %s\n",zstring_strtok(NULL,","));

  Example Output
      1 A
      2 B
      3 ,
      4 ,
      5 C
      6 (null)

La bibliothèque peut être téléchargée depuis Github https://github.com/fnoyanisi/zString


joli! c'est ce que je cherchais.
Kostia Kim

3

Je pense que la solution suivante est idéale:

  • Ne détruit pas la chaîne source
  • Re-entrant - c'est-à-dire que vous pouvez l'appeler en toute sécurité de n'importe où dans un ou plusieurs threads
  • Portable
  • Gère correctement plusieurs séparateurs
  • Rapide et efficace

Explication du code:

  1. Définir une structure tokenpour stocker l'adresse et la longueur des jetons
  2. Allouez suffisamment de mémoire pour ceux-ci dans le pire des cas, c'est-à-dire lorsque strest entièrement composé de séparateurs, donc il y a des strlen(str) + 1 jetons, tous des chaînes vides
  3. Balayage strenregistrant l'adresse et la longueur de chaque jeton
  4. Utilisez ceci pour allouer le tableau de sortie de la taille correcte, y compris un espace supplémentaire pour une NULLvaleur sentinelle
  5. Allouez, copiez et ajoutez les jetons en utilisant les informations de début et de longueur - utilisez memcpycar c'est plus rapide questrcpy et nous connaissons les longueurs
  6. Libérez l'adresse du jeton et le tableau de longueur
  7. Renvoyer le tableau de jetons
typedef struct {
    const char *start;
    size_t len;
} token;

char **split(const char *str, char sep)
{
    char **array;
    unsigned int start = 0, stop, toks = 0, t;
    token *tokens = malloc((strlen(str) + 1) * sizeof(token));
    for (stop = 0; str[stop]; stop++) {
        if (str[stop] == sep) {
            tokens[toks].start = str + start;
            tokens[toks].len = stop - start;
            toks++;
            start = stop + 1;
        }
    }
    /* Mop up the last token */
    tokens[toks].start = str + start;
    tokens[toks].len = stop - start;
    toks++;
    array = malloc((toks + 1) * sizeof(char*));
    for (t = 0; t < toks; t++) {
        /* Calloc makes it nul-terminated */
        char *token = calloc(tokens[t].len + 1, 1);
        memcpy(token, tokens[t].start, tokens[t].len);
        array[t] = token;
    }
    /* Add a sentinel */
    array[t] = NULL; 
    free(tokens);
    return array;
}

Remarque la malloc vérification omise par souci de concision.

En général, je ne retournerais pas un tableau de char *pointeurs à partir d'une fonction fractionnée comme celle-ci car cela place beaucoup de responsabilité sur l'appelant pour les libérer correctement. Une interface que je préfère est de permettre à l'appelant de passer une fonction de rappel et appeler cela pour chaque jeton, comme je l' ai décrit ici: découper une chaîne en C .


Il est probablement préférable de rechercher deux fois les séparateurs plutôt que d'allouer un large éventail de fichiers token.
chqrlie le

2

Essayez d'utiliser ceci.

char** strsplit(char* str, const char* delim){
    char** res = NULL;
    char*  part;
    int i = 0;

    char* aux = strdup(str);

    part = strdup(strtok(aux, delim));

    while(part){
        res = (char**)realloc(res, (i + 1) * sizeof(char*));
        *(res + i) = strdup(part);

        part = strdup(strtok(NULL, delim));
        i++;
    }

    res = (char**)realloc(res, i * sizeof(char*));
    *(res + i) = NULL;

    return res;
}

2

Cette méthode optimisée crée (ou met à jour un tableau existant) de pointeurs dans * result et renvoie le nombre d'éléments dans * count.

Utilisez "max" pour indiquer le nombre maximum de chaînes que vous attendez (lorsque vous spécifiez un tableau existant ou tout autre reaseon), sinon définissez-le sur 0

Pour comparer avec une liste de délimiteurs, définissez delim comme un caractère * et remplacez la ligne:

if (str[i]==delim) {

avec les deux lignes suivantes:

 char *c=delim; while(*c && *c!=str[i]) c++;
 if (*c) {

Prendre plaisir

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

char **split(char *str, size_t len, char delim, char ***result, unsigned long *count, unsigned long max) {
  size_t i;
  char **_result;

  // there is at least one string returned
  *count=1;

  _result= *result;

  // when the result array is specified, fill it during the first pass
  if (_result) {
    _result[0]=str;
  }

  // scan the string for delimiter, up to specified length
  for (i=0; i<len; ++i) {

    // to compare against a list of delimiters,
    // define delim as a string and replace 
    // the next line:
    //     if (str[i]==delim) {
    //
    // with the two following lines:
    //     char *c=delim; while(*c && *c!=str[i]) c++;
    //     if (*c) {
    //       
    if (str[i]==delim) {

      // replace delimiter with zero
      str[i]=0;

      // when result array is specified, fill it during the first pass
      if (_result) {
        _result[*count]=str+i+1;
      }

      // increment count for each separator found
      ++(*count);

      // if max is specified, dont go further
      if (max && *count==max)  {
        break;
      }

    }
  }

  // when result array is specified, we are done here
  if (_result) {
    return _result;
  }

  // else allocate memory for result
  // and fill the result array                                                                                    

  *result=malloc((*count)*sizeof(char*));
  if (!*result) {
    return NULL;
  }
  _result=*result;

  // add first string to result
  _result[0]=str;

  // if theres more strings
  for (i=1; i<*count; ++i) {

    // find next string
    while(*str) ++str;
    ++str;

    // add next string to result
    _result[i]=str;

  }

  return _result;
}  

Exemple d'utilisation:

#include <stdio.h>

int main(int argc, char **argv) {
  char *str="JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char **result=malloc(6*sizeof(char*));
  char **result2=0;
  unsigned long count;
  unsigned long count2;
  unsigned long i;

  split(strdup(str),strlen(str),',',&result,&count,6);
  split(strdup(str),strlen(str),',',&result2,&count2,0);

  if (result)
  for (i=0; i<count; ++i) {
    printf("%s\n",result[i]);
  }

  printf("\n");

  if (result2)
  for (i=0; i<count2; ++i) {
    printf("%s\n", result2[i]);
  }

  return 0;

}

2

Ma version:

int split(char* str, const char delimeter, char*** args) {
    int cnt = 1;
    char* t = str;

    while (*t == delimeter) t++;

    char* t2 = t;
    while (*(t2++))
        if (*t2 == delimeter && *(t2 + 1) != delimeter && *(t2 + 1) != 0) cnt++;

    (*args) = malloc(sizeof(char*) * cnt);

    for(int i = 0; i < cnt; i++) {
        char* ts = t;
        while (*t != delimeter && *t != 0) t++;

        int len = (t - ts + 1);
        (*args)[i] = malloc(sizeof(char) * len);
        memcpy((*args)[i], ts, sizeof(char) * (len - 1));
        (*args)[i][len - 1] = 0;

        while (*t == delimeter) t++;
    }

    return cnt;
}

2

Il s'agit d'une fonction de fractionnement de chaîne qui peut gérer des délimiteurs à plusieurs caractères. Notez que si le délimiteur est plus long que la chaîne en cours de fractionnement, alors bufferet stringLengthssera défini sur (void *) 0, et numStringssera défini sur 0.

Cet algorithme a été testé et fonctionne. (Clause de non-responsabilité: il n'a pas été testé pour les chaînes non-ASCII, et il suppose que l'appelant a donné des paramètres valides)

void splitString(const char *original, const char *delimiter, char ** * buffer, int * numStrings, int * * stringLengths){
    const int lo = strlen(original);
    const int ld = strlen(delimiter);
    if(ld > lo){
        *buffer = (void *)0;
        *numStrings = 0;
        *stringLengths = (void *)0;
        return;
    }

    *numStrings = 1;

    for(int i = 0;i < (lo - ld);i++){
        if(strncmp(&original[i], delimiter, ld) == 0) {
            i += (ld - 1);
            (*numStrings)++;
        }
    }

    *stringLengths = (int *) malloc(sizeof(int) * *numStrings);

    int currentStringLength = 0;
    int currentStringNumber = 0;
    int delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(i < (lo - ld)){
            if(strncmp(&original[i], delimiter, ld) == 0){
                (*stringLengths)[currentStringNumber] = currentStringLength;
                currentStringNumber++;
                currentStringLength = 0;
                delimiterTokenDecrementCounter = ld - 1;
            } else {
                currentStringLength++;
            }
        } else {
            currentStringLength++;
        }

        if(i == (lo - 1)){
            (*stringLengths)[currentStringNumber] = currentStringLength;
        }
    }

    *buffer = (char **) malloc(sizeof(char *) * (*numStrings));
    for(int i = 0;i < *numStrings;i++){
        (*buffer)[i] = (char *) malloc(sizeof(char) * ((*stringLengths)[i] + 1));
    }

    currentStringNumber = 0;
    currentStringLength = 0;
    delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(currentStringLength >= (*stringLengths)[currentStringNumber]){
            (*buffer)[currentStringNumber][currentStringLength] = 0;
            delimiterTokenDecrementCounter = ld - 1;
            currentStringLength = 0;
            currentStringNumber++;
        } else {
            (*buffer)[currentStringNumber][currentStringLength] = (char)original[i];
            currentStringLength++;
        }
    }
    buffer[currentStringNumber][currentStringLength] = 0;
}

Exemple de code:

int main(){
    const char *string = "STRING-1 DELIM string-2 DELIM sTrInG-3";
    char **buffer;
    int numStrings;
    int * stringLengths;

    splitString(string, " DELIM ", &buffer, &numStrings, &stringLengths);

    for(int i = 0;i < numStrings;i++){
        printf("String: %s\n", buffer[i]);
    }
}

Bibliothèques:

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

Comment appeler cela depuis le main? Je ne sais pas quoi passer au tampon.
Aymon Fournier

La logique d'attribution est fausse. realloc () renvoie un nouveau pointeur et vous ignorez la valeur renvoyée. Aucune manière appropriée de renvoyer un nouveau pointeur de mémoire - le prototype de fonction doit être modifié pour accepter la taille de l'allocation bufferet laisser l'allocation à l'appelant, traiter les éléments de taille maximale.
Alex

@Alex Fixe, entièrement réécrit et testé. Remarque: je ne sais pas si cela fonctionnera pour les non-ASCII ou non.
Élektra

Pour commencer, ce n'est pas du code C. Et pourquoi passeriez-vous des pointeurs par référence réelle en C ++?
Kamiccolo

@Kamiccolo Je suis désolé, comment ce code n'est-il pas exactement C? Aussi, pourquoi le passage de pointeurs par référence pose-t-il un problème ici?
Élektra

1

Mon approche est de scanner la chaîne et de laisser les pointeurs pointer vers chaque caractère après les déliminateurs (et le premier caractère), en même temps attribuer les apparences du déliminateur dans la chaîne à '\ 0'.
Faites d'abord une copie de la chaîne d'origine (car elle est constante), puis obtenez le nombre de divisions en la parcourant, passez-la au paramètre de pointeur len . Après cela, pointez le premier pointeur de résultat vers le pointeur de chaîne de copie, puis scannez la chaîne de copie: une fois que vous rencontrez un déliminateur, affectez-le à '\ 0' ainsi la chaîne de résultat précédente est terminée, et pointez le pointeur de chaîne de résultat suivant vers le suivant pointeur de caractère.

char** split(char* a_str, const char a_delim, int* len){
    char* s = (char*)malloc(sizeof(char) * strlen(a_str));
    strcpy(s, a_str);
    char* tmp = a_str;
    int count = 0;
    while (*tmp != '\0'){
        if (*tmp == a_delim) count += 1;
        tmp += 1;
    }
    *len = count;
    char** results = (char**)malloc(count * sizeof(char*));
    results[0] = s;
    int i = 1;
    while (*s!='\0'){
        if (*s == a_delim){
            *s = '\0';
            s += 1;
            results[i++] = s;
        }
        else s += 1;
    }
    return results;
}

Cette méthode est fausse. Je viens juste de supprimer ce post, mais j'ai réalisé que c'était peut-être intéressant pour certains d'entre vous.
metalcrash

1

Mon code (testé):

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

int dtmsplit(char *str, const char *delim, char ***array, int *length ) {
  int i=0;
  char *token;
  char **res = (char **) malloc(0 * sizeof(char *));

  /* get the first token */
   token = strtok(str, delim);
   while( token != NULL ) 
   {
        res = (char **) realloc(res, (i + 1) * sizeof(char *));
        res[i] = token;
        i++;
      token = strtok(NULL, delim);
   }
   *array = res;
   *length = i;
  return 1;
}

int main()
{
    int i;
    int c = 0;
    char **arr = NULL;

    int count =0;

    char str[80] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    c = dtmsplit(str, ",", &arr, &count);
    printf("Found %d tokens.\n", count);

    for (i = 0; i < count; i++)
        printf("string #%d: %s\n", i, arr[i]);

   return(0);
}

Résultat:

Found 12 tokens.
string #0: JAN
string #1: FEB
string #2: MAR
string #3: APR
string #4: MAY
string #5: JUN
string #6: JUL
string #7: AUG
string #8: SEP
string #9: OCT
string #10: NOV
string #11: DEC

1
Sachez que la fonction strtok change la chaîne à laquelle 'str' a été appliquée!
SchLx

1

Exploser et imploser - la chaîne initiale reste intacte, allocation de mémoire dynamique

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

typedef struct
{
    uintptr_t   ptr;
    int         size;
} token_t;

int explode(char *str, int slen, const char *delimiter, token_t **tokens)
{
    int i = 0, c1 = 0, c2 = 0;

    for(i = 0; i <= slen; i++)
    {
            if(str[i] == *delimiter)
            {
                c1++;
            }
    }

    if(c1 == 0)
    {
            return -1;
    }

    *tokens = (token_t*)calloc((c1 + 1), sizeof(token_t));
    ((*tokens)[c2]).ptr = (uintptr_t)str;

    i = 0; 
    while(i <= slen)
    {
        if((str[i] == *delimiter) || (i == slen))
        {
                ((*tokens)[c2]).size = (int)((uintptr_t)&(str[i]) - (uintptr_t)(((*tokens)[c2]).ptr));
                if(i < slen)
                {
                    c2++;
                    ((*tokens)[c2]).ptr = (uintptr_t)&(str[i + 1]);
                }
        }
        i++;
    }
    return (c1 + 1);
}

char* implode(token_t *tokens, int size, const char *delimiter)
{
    int     i, len = 0;
    char    *str;

    for(i = 0; i < len; i++)
    {
        len += tokens[i].size + 1;
    }

    str = (char*)calloc(len, sizeof(char));

    len = 0;
    for(i = 0; i < size; i++)
    {
        memcpy((void*)&str[len], (void*)tokens[i].ptr, tokens[i].size);
        len += tokens[i].size;
        str[(len++)] = *delimiter;
    }

    str[len - 1] = '\0';

    return str;
}

Usage:

int main(int argc, char **argv)
{
    int         i, c;
    char        *exp = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    token_t     *tokens;
    char        *imp;

    printf("%s\n", exp);

    if((c = explode(exp, strlen(exp), ",", &tokens)) > 0)
    {
        imp = implode(tokens, c, ",");
        printf("%s\n", imp);

        for(i = 0; i < c; i++)
        {
            printf("%.*s, %d\n", tokens[i].size, (char*)tokens[i].ptr, tokens[i].size);
        }
    }

    free((void*)tokens);
    free((void*)imp);
    return 0;
}

0

Si vous êtes prêt à utiliser une bibliothèque externe, je ne peux pas vous recommander bstrlib trop vous . Cela prend un peu de configuration supplémentaire, mais est plus facile à utiliser à long terme.

Par exemple, divisez la chaîne ci-dessous, on crée d'abord un bstringavec l' bfromcstr()appel. (A bstringest un wrapper autour d'un tampon de caractères). Ensuite, divisez la chaîne par des virgules, en enregistrant le résultat dans a struct bstrList, qui contient des champs qtyet un tableau entry, qui est un tableau de bstrings.

bstrliba de nombreuses autres fonctions pour opérer sur bstrings

C'est de la tarte...

#include "bstrlib.h"
#include <stdio.h>
int main() {
  int i;
  char *tmp = "Hello,World,sak";
  bstring bstr = bfromcstr(tmp);
  struct bstrList *blist = bsplit(bstr, ',');
  printf("num %d\n", blist->qty);
  for(i=0;i<blist->qty;i++) {
    printf("%d: %s\n", i, bstr2cstr(blist->entry[i], '_'));
  }

}

0

Encore une autre réponse (cela a été déplacé d'ici à partir d' ici ):

Essayez d'utiliser la fonction strtok:

voir les détails sur ce sujet ici ou ici

Le problème ici est que vous devez traiter wordsimmédiatement. Si vous voulez le stocker dans un tableau, vous devez allouer le correct sizecar il est inconnu.

Donc par exemple:

char **Split(char *in_text, char *in_sep)
{
    char **ret = NULL;
    int count = 0;
    char *tmp = strdup(in_text);
    char *pos = tmp;

    // This is the pass ONE: we count 
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        count++;
        pos = NULL;
    }

    // NOTE: the function strtok changes the content of the string! So we free and duplicate it again! 
    free(tmp);
    pos = tmp = strdup(in_text);

    // We create a NULL terminated array hence the +1
    ret = calloc(count+1, sizeof(char*));
    // TODO: You have to test the `ret` for NULL here

    // This is the pass TWO: we store
    count = 0;
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        ret[count] = strdup(pos);
        count++;
        pos = NULL;
    }
    free(tmp);

    return count;
}

// Use this to free
void Free_Array(char** in_array)
{
    char *pos = in_array;

    while (pos[0] != NULL)
    {
        free(pos[0]);
        pos++;

    }

    free(in_array);

}

Remarque : Nous utilisons la même boucle et la même fonction pour calculer les comptages (passer un) et pour faire les copies (passer deux), afin d'éviter les problèmes d'allocation.

Remarque 2 : Vous pouvez utiliser une autre implémentation du strtok dont les raisons sont mentionnées dans des articles séparés.

Vous pouvez utiliser ceci comme:

int main(void)
{
  char **array = Split("Hello World!", " ");
  // Now you have the array
  // ...

  // Then free the memory
  Free_Array(array);
  array = NULL;
  return 0;
}

(Je ne l'ai pas testé, alors faites-moi savoir si cela ne fonctionne pas!)


0

Deux problèmes entourant cette question sont la gestion de la mémoire et la sécurité des threads. Comme vous pouvez le voir dans les nombreux articles, ce n'est pas une tâche facile à accomplir de manière transparente en C.Je souhaitais une solution qui soit:

  • Fil sûr. (strtok n'est pas thread-safe)
  • N'emploie pas malloc ou l'un de ses dérivés (pour éviter les problèmes de gestion de la mémoire)
  • Vérifie les limites du tableau sur les champs individuels (pour éviter les erreurs de segment sur des données inconnues)
  • Fonctionne avec des séparateurs de champ multi-octets (utf-8)
  • ignore les champs supplémentaires dans l'entrée
  • fournit une routine d'erreur douce pour les longueurs de champ non valides

La solution que j'ai trouvée répond à tous ces critères. C'est probablement un peu plus de travail à mettre en place que certaines autres solutions affichées ici, mais je pense qu'en pratique, le travail supplémentaire en vaut la peine afin d'éviter les écueils courants des autres solutions.

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

struct splitFieldType {
    char *field;
    int   maxLength;
};

typedef struct splitFieldType splitField;

int strsplit(splitField *fields, int expected, const char *input, const char *fieldSeparator, void (*softError)(int fieldNumber,int expected,int actual))  {
    int i;
    int fieldSeparatorLen=strlen(fieldSeparator);
    const char *tNext, *tLast=input;

    for (i=0; i<expected && (tNext=strstr(tLast, fieldSeparator))!=NULL; ++i) {
        int len=tNext-tLast;
        if (len>=fields[i].maxLength) {
            softError(i,fields[i].maxLength-1,len);
            len=fields[i].maxLength-1;
        }
        fields[i].field[len]=0;
        strncpy(fields[i].field,tLast,len);
        tLast=tNext+fieldSeparatorLen;
    }
    if (i<expected) {
        if (strlen(tLast)>fields[i].maxLength) {
            softError(i,fields[i].maxLength,strlen(tLast));
        } else {
            strcpy(fields[i].field,tLast);
        }
        return i+1;
    } else {
        return i;
    }
}


void monthSplitSoftError(int fieldNumber, int expected, int actual) {
    fprintf(stderr,"monthSplit: input field #%d is %d bytes, expected %d bytes\n",fieldNumber+1,actual,expected);
}


int main() {
  const char *fieldSeparator=",";
  const char *input="JAN,FEB,MAR,APRI,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR";

  struct monthFieldsType {
    char field1[4];
    char field2[4];
    char field3[4];
    char field4[4];
    char field5[4];
    char field6[4];
    char field7[4];
    char field8[4];
    char field9[4];
    char field10[4];
    char field11[4];
    char field12[4];
  } monthFields;

  splitField inputFields[12] = {
    {monthFields.field1,  sizeof(monthFields.field1)},
    {monthFields.field2,  sizeof(monthFields.field2)},
    {monthFields.field3,  sizeof(monthFields.field3)},
    {monthFields.field4,  sizeof(monthFields.field4)},
    {monthFields.field5,  sizeof(monthFields.field5)},
    {monthFields.field6,  sizeof(monthFields.field6)},
    {monthFields.field7,  sizeof(monthFields.field7)},
    {monthFields.field8,  sizeof(monthFields.field8)},
    {monthFields.field9,  sizeof(monthFields.field9)},
    {monthFields.field10, sizeof(monthFields.field10)},
    {monthFields.field11, sizeof(monthFields.field11)},
    {monthFields.field12, sizeof(monthFields.field12)}
  };

  int expected=sizeof(inputFields)/sizeof(splitField);

  printf("input data: %s\n", input);
  printf("expecting %d fields\n",expected);

  int ct=strsplit(inputFields, expected, input, fieldSeparator, monthSplitSoftError);

  if (ct!=expected) {
    printf("string split %d fields, expected %d\n", ct,expected);
  }

  for (int i=0;i<expected;++i) {
    printf("field %d: %s\n",i+1,inputFields[i].field);
  }

  printf("\n");
  printf("Direct structure access, field 10: %s", monthFields.field10);
}

Voici un exemple de compilation et de sortie. Notez que dans mon exemple, j'ai volontairement épelé «AVRIL» afin que vous puissiez voir comment fonctionne l'erreur logicielle.

$ gcc strsplitExample.c && ./a.out
input data: JAN,FEB,MAR,APRIL,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR
expecting 12 fields
monthSplit: input field #4 is 5 bytes, expected 3 bytes
field 1: JAN
field 2: FEB
field 3: MAR
field 4: APR
field 5: MAY
field 6: JUN
field 7: JUL
field 8: AUG
field 9: SEP
field 10: OCT
field 11: NOV
field 12: DEC

Direct structure access, field 10: OCT

Prendre plaisir!


0

Voici une autre implémentation qui fonctionnera en toute sécurité pour tokeniser un littéral de chaîne correspondant au prototype demandé dans la question renvoyant un pointeur à pointeur alloué à char (par exemple char **). La chaîne de délimitation peut contenir plusieurs caractères et la chaîne d'entrée peut contenir n'importe quel nombre de jetons. Toutes les allocations et réallocations sont gérées par mallocou reallocsans POSIXstrdup .

Le nombre initial de pointeurs alloués est contrôlé par la NPTRSconstante et la seule limitation est qu'il soit supérieur à zéro. Le char **retourné contient une sentinelle NULL après le dernier jeton similaire à *argv[]et sous la forme utilisable par execv, execvpetexecve .

Comme avec strtok()plusieurs délimiteurs séquentiels sont traités comme un seul délimiteur, ils "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"seront analysés comme si un seul ','séparait "MAY,JUN".

La fonction ci-dessous est commentée en ligne et un court a main()été ajouté pour répartir les mois. Le nombre initial de pointeurs alloués a été défini sur 2pour forcer trois réallocations lors de la création de jetons de la chaîne d'entrée:

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

#define NPTRS 2     /* initial number of pointers to allocate (must be > 0) */

/* split src into tokens with sentinel NULL after last token.
 * return allocated pointer-to-pointer with sentinel NULL on success,
 * or NULL on failure to allocate initial block of pointers. The number
 * of allocated pointers are doubled each time reallocation required.
 */
char **strsplit (const char *src, const char *delim)
{
    int i = 0, in = 0, nptrs = NPTRS;       /* index, in/out flag, ptr count */
    char **dest = NULL;                     /* ptr-to-ptr to allocate/fill */
    const char *p = src, *ep = p;           /* pointer and end-pointer */

    /* allocate/validate nptrs pointers for dest */
    if (!(dest = malloc (nptrs * sizeof *dest))) {
        perror ("malloc-dest");
        return NULL;
    }
    *dest = NULL;   /* set first pointer as sentinel NULL */

    for (;;) {  /* loop continually until end of src reached */
        if (!*ep || strchr (delim, *ep)) {  /* if at nul-char or delimiter char */
            size_t len = ep - p;            /* get length of token */
            if (in && len) {                /* in-word and chars in token */
                if (i == nptrs - 1) {       /* used pointer == allocated - 1? */
                    /* realloc dest to temporary pointer/validate */
                    void *tmp = realloc (dest, 2 * nptrs * sizeof *dest);
                    if (!tmp) {
                        perror ("realloc-dest");
                        break;  /* don't exit, original dest still valid */
                    }
                    dest = tmp;             /* assign reallocated block to dest */
                    nptrs *= 2;             /* increment allocated pointer count */
                }
                /* allocate/validate storage for token */
                if (!(dest[i] = malloc (len + 1))) {
                    perror ("malloc-dest[i]");
                    break;
                }
                memcpy (dest[i], p, len);   /* copy len chars to storage */
                dest[i++][len] = 0;         /* nul-terminate, advance index */
                dest[i] = NULL;             /* set next pointer NULL */
            }
            if (!*ep)                       /* if at end, break */
                break;
            in = 0;                         /* set in-word flag 0 (false) */
        }
        else {  /* normal word char */
            if (!in)                        /* if not in-word */
                p = ep;                     /* update start to end-pointer */
            in = 1;                         /* set in-word flag 1 (true) */
        }
        ep++;   /* advance to next character */
    }

    return dest;
}

int main (void) {

    char *str = "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC",
        **tokens;                           /* pointer to pointer to char */

    if ((tokens = strsplit (str, ","))) {   /* split string into tokens */
        for (char **p = tokens; *p; p++) {  /* loop over filled pointers */
            puts (*p);
            free (*p);      /* don't forget to free allocated strings */
        }
        free (tokens);      /* and pointers */
    }
}

Exemple d'utilisation / sortie

$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC

Dis moi si tu as d'autres questions.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.