Tekenreeks splitsen met scheidingstekens in C

Hoe schrijf ik een functie om een array te splitsen en terug te geven voor een string met scheidingstekens in de programmeertaal C?

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

Antwoord 1, autoriteit 100%

Je kunt de functie strtok()gebruiken om te splitsen een tekenreeks (en specificeer het scheidingsteken dat u wilt gebruiken). Merk op dat strtok()de string die erin wordt doorgegeven zal wijzigen. Als de originele string ergens anders nodig is, maak er dan een kopie van en geef de kopie door aan strtok().

BEWERKEN:

Voorbeeld (merk op dat opeenvolgende scheidingstekens niet worden verwerkt, bijvoorbeeld “JAN,,,FEB,MAR”):

#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;
}

Uitgang:

$ ./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]

2, Autoriteit 44%

Ik denk strsepis nog steeds de beste tool voor deze:

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

Dat is letterlijk één regel die een string splitst.

De extra haakjes zijn een stilistische element om aan te geven dat we opzettelijk het resultaat van een opdracht testen, geen gelijkheidsexploitant ==.

Voor dat patroon om te werken, tokenen strBeide hebben Type char *. Als je begon met een string-letterlijk, dan zou je eerst een kopie van willen maken:

// 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);

Als twee scheidingstekens samen voorkomen in str, krijgt u een token-waarde die de lege tekenreeks is. De waarde van stris gewijzigd in die zin dat elk aangetroffen scheidingsteken wordt overschreven met een nulbyte – nog een goede reden om de tekenreeks die eerst wordt geparseerd te kopiëren.

In een opmerking suggereerde iemand dat strtokbeter is dan strsepomdat strtokdraagbaarder is. Ubuntu en Mac OS X hebben strsep; het is veilig om te raden dat andere unixy-systemen dat ook doen. Windows mist strsep, maar het heeft strbrkwat deze korte en mooie vervanging van strsepmogelijk maakt:

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;
}

Hieris een goede uitleg van strsepvs strtok. De voor- en nadelen kunnen subjectief worden beoordeeld; ik denk echter dat het een veelzeggend teken is dat strsepis ontworpen als vervanging voor strtok.


Antwoord 3, autoriteit 17%

String tokenizer deze code zou je in de goede richting moeten sturen.

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;
}

Antwoord 4, autoriteit 7%

Hieronder volgt alle taak (geheugentoewijzing, tellen van de lengte) voor u. Meer informatie en beschrijving is hier te vinden – implementatie van Java String.Split () -methode om C String

te splitsen

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;
}

Hoe het te gebruiken:

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;
}

Antwoord 5, autoriteit 4%

Hier is mijn twee cent:

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;
}

Gebruik:

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);

Antwoord 6, autoriteit 2%

In het bovenstaande voorbeeld zou er een manier zijn om een array met null-beëindigde tekenreeksen (zoals u wilt) op hun plaats in de tekenreeks te retourneren. Het zou het echter niet mogelijk maken om een letterlijke tekenreeks door te geven, omdat deze zou moeten worden gewijzigd door de functie:

#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;
}

Er is waarschijnlijk een nettere manier om het te doen, maar je snapt het idee.


Antwoord 7, autoriteit 2%

#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;
}

Antwoord 8, autoriteit 2%

Hieronder staat mijn strtok()-implementatie van zString-bibliotheek.
zstring_strtok()verschilt van strtok()van de standaardbibliotheek in de manier waarop het opeenvolgende scheidingstekens behandelt.

Kijk maar eens naar de onderstaande code, zodat u zeker weet hoe het werkt (ik heb geprobeerd zoveel mogelijk opmerkingen te gebruiken)

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;
}

Hieronder is een voorbeeldgebruik …

 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)

De bibliotheek kan worden gedownload van GitHub
https://github.com/fnoyanisi/zstring


9

Deze functie neemt een char *-string en splitst het door de delimator. Er kunnen meerdere delimators op een rij zijn. Merk op dat de functie de Orignal String wijzigt. U moet eerst een kopie van de originele string maken als u het origineel nodig hebt om ongewijzigd te blijven. Deze functie gebruikt geen CSTRING-functie-oproepen, zodat het misschien een beetje sneller is dan andere. Als u niet geeft om geheugentoewijzing, kunt u sub_strings aan de bovenkant van de functie toewijzen met maat Strlen (SRC_STR) / 2 en (zoals de genoemde C++ “-versie” van de onderste helft van de functie overslaan. Als u dit doet, wordt de functie gereduceerd tot O (n), maar de onderstaande geheugenoptimaliseerde manier is O (2N).

de functie:

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);
}

Hoe het te gebruiken:

 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);

Antwoord 10

Probeer dit.

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;
}

Antwoord 11

Deze geoptimaliseerde methode maakt (of update een bestaande) array van pointers in *result en retourneert het aantal elementen in *count.

Gebruik “max” om het maximale aantal strings aan te geven dat u verwacht (wanneer u een bestaande array of een andere reden opgeeft), anders stelt u deze in op 0

Om te vergelijken met een lijst met scheidingstekens, definieert u scheidingsteken als een teken* en vervangt u de regel:

if (str[i]==delim) {

Met de twee volgende regels:

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

Geniet

#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;
}  

Gebruiksvoorbeeld:

#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;
}

12

Mijn versie:

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;
}

13

Dit is een functie voor het splitsen van tekenreeksen die kan omgaan met scheidingstekens van meerdere tekens. Merk op dat als het scheidingsteken langer is dan de tekenreeks die wordt gesplitst, bufferen stringLengthsworden ingesteld op (void *) 0, en numStringsworden ingesteld op 0.

Dit algoritme is getest en werkt. (Disclaimer: het is niet getest op niet-ASCII-strings en het gaat ervan uit dat de beller geldige parameters heeft gegeven)

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;
}

Voorbeeldcode:

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]);
    }
}

Bibliotheken:

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

Antwoord 14

Mijn code (getest):

#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);
}

Resultaat:

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

Antwoord 15

Explodeer & implode – initiële string blijft intact, dynamische geheugentoewijzing

#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;
}

Gebruik:

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;
}

Antwoord 16

Als je bereid bent een externe bibliotheek te gebruiken, kan ik bstrlibgenoeg. Het vergt wat extra installatie, maar is op de lange termijn gemakkelijker te gebruiken.

Bijvoorbeeld, splits de onderstaande string, men maakt eerst een bstringmet de bfromcstr()aanroep. (Een bstringis een wrapper rond een char buffer).
Splits vervolgens de tekenreeks op komma’s en sla het resultaat op in een struct bstrList, die velden qtyheeft en een array entry, wat een array is van bstrings.

bstrlibheeft vele andere functies om op bstrings

te werken

Eenvoudig als taart…

#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], '_'));
  }
}

Antwoord 17

Mijn aanpak is om de tekenreeks te scannen en de aanwijzers naar elk teken na de scheidingstekens (en het eerste teken) te laten wijzen, terwijl ik tegelijkertijd het uiterlijk van de deliminator in tekenreeks toewijs aan ‘\0’.
Maak eerst een kopie van de originele string (aangezien deze constant is), en verkrijg vervolgens het aantal splitsingen door deze te scannen en door te geven aan de pointerparameter len. Wijs daarna de eerste resultaataanwijzer naar de kopieertekenreeksaanwijzer en scan vervolgens de kopieertekenreeks: zodra u een scheidingsteken tegenkomt, wijst u deze toe aan ‘\0’, zodat de vorige resultaatreeks wordt beëindigd en wijst u de volgende tekenreeksaanwijzer naar de volgende karakter aanwijzer.

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;
}

Antwoord 18

Twee problemen met betrekking tot deze vraag zijn geheugenbeheer en threadveiligheid. Zoals je kunt zien aan de vele berichten,
dit is geen gemakkelijke taak om naadloos uit te voeren in C. Ik wilde een oplossing die:

  • Draad veilig. (strtok is niet thread-safe)
  • Gebruikt geen malloc of een van zijn afgeleiden (om problemen met geheugenbeheer te voorkomen)
  • Controleert arraygrenzen op de afzonderlijke velden (om segmentfouten op onbekende gegevens te voorkomen)
  • Werkt met multi-byte veldscheidingstekens (utf-8)
  • negert extra velden in de invoer
  • biedt een zachte foutroutine voor ongeldige veldlengtes

De oplossing die ik heb bedacht, voldoet aan al deze criteria. Het is waarschijnlijk wat meer werk om in te stellen
dan sommige andere oplossingen die hier zijn gepost, maar ik denk dat het extra werk in de praktijk de moeite waard is
het om de veelvoorkomende valkuilen van andere oplossingen te vermijden.

#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);
}

Hieronder ziet u een voorbeeld van compileren en uitvoeren. Merk op dat ik in mijn voorbeeld doelbewust “APRIL” heb gespeld, zodat je kunt zien hoe de zachte fout werkt.

$ 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

Veel plezier!


Antwoord 19

Hier is een andere implementatie die veilig zal werken om een string-literalte tokeniseren die overeenkomt met het prototype dat in de vraag is gevraagd en een toegewezen pointer-naar-pointer naar char retourneert (bijv. char **). De scheidingstekenreeks kan meerdere tekens bevatten en de invoerreeks kan een willekeurig aantal tokens bevatten. Alle toewijzingen en hertoewijzingen worden afgehandeld door mallocof realloczonder POSIX strdup.

Het aanvankelijke aantal toegewezen pointers wordt bepaald door de constante NPTRSen de enige beperking is dat deze groter is dan nul. De char **die wordt geretourneerd, bevat een sentinelNULLna het laatste token, vergelijkbaar met *argv[]en in de formulier bruikbaar door execv, execvpen execve.

Net als bij strtok()worden meerdere opeenvolgende scheidingstekens behandeld als een enkel scheidingsteken, dus "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"wordt geparseerd alsof er slechts een enkele ','scheidt van "MAY,JUN".

De onderstaande functie is inline becommentarieerd en een korte main()is toegevoegd om de maanden te splitsen. Het aanvankelijke aantal toegewezen pointers was ingesteld op 2om drie hertoewijzing te forceren tijdens het tokeniseren van de invoerreeks:

#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 */
    }
}

Gebruikt / uitvoer

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

Laat het me weten als je nog vragen hebt.

Other episodes