Сплит строка с разделителями в C - PullRequest
142 голосов
/ 09 февраля 2012

Как написать функцию для разделения и возврата массива для строки с разделителями на языке программирования C?

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

Ответы [ 25 ]

3 голосов
/ 11 сентября 2014
#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;
}
2 голосов
/ 05 апреля 2017

Моя версия:

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 голосов
/ 02 сентября 2016

Я думаю, что следующее решение идеально:

  • Не уничтожает исходную строку
  • Повторно входящий - то есть вы можете безопасно вызвать его из любого места одного или несколькихнити
  • Переносной
  • Правильно обрабатывает несколько разделителей
  • Быстро и эффективно

Объяснение кода:

  1. Определите структуру token для хранения адреса и длины токенов
  2. Выделите достаточно памяти для них в худшем случае, когда str полностью состоит из разделителей, поэтому имеется strlen(str) + 1 токеноввсе они - пустые строки
  3. Scan str с записью адреса и длины каждого токена
  4. Используйте это для выделения выходного массива правильного размера, включая дополнительное пространство для NULL значение Sentinel
  5. Распределите, скопируйте и добавьте токены, используя информацию о начале и длине - используйте memcpy, поскольку это быстрее, чем strcpy, и мы знаем длины
  6. Свободный адрес токенаи длина массива
  7. Возвращать массив токенов
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;
}

Примечание malloc проверка опущена для краткости.

В общем, я бы не сталверните массив указателей char * из функции разделения, как это, так как это накладывает большую ответственность на вызывающего, чтобы освободить их правильно.Интерфейс, который я предпочитаю, состоит в том, чтобы позволить вызывающей стороне передавать функцию обратного вызова и вызывать ее для каждого токена, как я описал здесь: Разделить строку в C .

2 голосов
/ 28 января 2016

Мой код (проверено):

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

Результат:

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
2 голосов
/ 09 сентября 2015

Этот оптимизированный метод создает (или обновляет существующий) массив указателей в * result и возвращает количество элементов в * count.

Используйте «max», чтобы указать максимальное количество ожидаемых строк (когдаВы указываете существующий массив или любой другой код), в противном случае установите для него значение 0

. Для сравнения со списком разделителей определите разделитель как символ * и замените строку:

if (str[i]==delim) {

с двумя следующими строками:

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

Enjoy

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

Пример использования:

#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 голосов
/ 25 марта 2015

Мой подход состоит в том, чтобы отсканировать строку и позволить указателям указывать на каждый символ после разделителей (и первого символа), в то же время присваивая появления разделителя в строке '\ 0'.
Сначала сделайте копию исходной строки (так как она постоянна), затем найдите количество разделений, отсканировав его, передав его параметру указателя len . После этого наведите первый указатель результата на указатель строки копирования, затем просканируйте строку копирования: как только встретите разделитель, назначьте его на \ 0, таким образом, предыдущая строка результата завершается, и укажите указатель следующей строки результата на следующий символьный указатель

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;
}
1 голос
/ 08 февраля 2016

Разнесение и развертывание - исходная строка остается неизменной, динамическое распределение памяти

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

Использование:

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;
}
1 голос
/ 09 февраля 2012

Не проверено, возможно, неправильно, но должно дать вам хорошее представление о том, как это должно работать:

*char[] str_split(char* str, char delim) {

    int begin = 0;
    int end = 0;
    int j = 0;
    int i = 0;
    char *buf[NUM];

    while (i < strlen(str)) {

        if(*str == delim) {

            buf[j] = malloc(sizeof(char) * (end-begin));
            strncpy(buf[j], *(str + begin), (end-begin));
            begin = end;
            j++;

        }

        end++;
        i++;

    }

    return buf;

}
0 голосов
/ 17 ноября 2018
static int count_token(char *iptr, char delim) {

        int token_count = 0;
        while (*iptr && isspace(*iptr))
            iptr++;
        while (*iptr) {
            if ((*iptr != delim)) {
                token_count++;
                while (*iptr && (*iptr != delim))
                    iptr++;
            }
            else {
                iptr++;
            }
        }
        return token_count;
    }

    static char** split(char* input, int* argc){
        char**  argv;
        int token_count = count_token(input, ' ');
        argv = (char**)malloc(sizeof(char*)*token_count);

        int i = 0;
        char *token = strtok(input, " ");
        while(token) {
            puts(token);
            argv[i] = strdup(token);
            token = strtok(NULL, " ");
            i++;
        }
        assert(i == token_count);
        *argc = token_count;
        return argv;
    }
0 голосов
/ 16 декабря 2017

Это другой подход, который работает и для больших файлов.

/******************************************************************************

                            Online C Compiler.
                Code, Compile, Run and Debug C program online.
Write your code in this editor and press "Run" button to compile and execute it.

*******************************************************************************/

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

char* aux_slice(const char* str, char delimiter, const char** pRet);
char **split(const char *str, char delimiter, int *ret_size);



int main()
{
    int count = 0, i = 0;
    char** splits = split(",,, 1,2,3,4,5,6,7,8,9,10, aaaa, a,a a,aa,,a,,,,a,a a,a a,a,,,,,ashajhsas asjas,,a,a,,aa"
    "aaaaaaaaaaa.......,p,p,p,p,p,p,p, this is last,,,,,,,,,,", ',', &count);
    printf("Strings (%d)\n", count); 
    for (i=0 ; i < count; i++) {
        printf("%s\n", splits[i]);
    }
    for(i=0; i < count; i++) {
        free(splits[i]);
    }
    free(splits); 
    return 0;
}




char* aux_slice(const char* str, char delimiter, const char** pRet)
{
    int size = 0, i = 0;
    const char* begin = str;
    char  *ret = NULL;
    int match = 0;

    if (!str) {
        return NULL;
    }

    while (*begin != '\0') {
        if (*begin == delimiter) {
            match++;
            break;
        }
        size++;
        begin++;
    }

    ret = (char*)malloc(sizeof(char) * size);

    if(ret == NULL) {
        return NULL;
    }

    if (match) {
        /* we have a delimiter ??? */
        for(i = 0; str[i] != delimiter; ++i) {
            ret[i] = str[i];
        }

        ret[i] = '\0';

        while (*begin == delimiter) {
            begin++;
        }
        (*pRet) = begin;

    } else {
        /* or we just copy the remaining string.... */
        for(i=0; str[i] != '\0'; ++i) {
            ret[i] = str[i];
        }
        ret[i] = '\0';
        (*pRet) = NULL;
    }

    return ret;
}


char **split(const char *str, char delimiter, int *ret_size)
{
    int diff = 0, splits = 0, i=0;
    const char* begin = str;
    const char* end = &str[strlen(str)-1];
    while (*begin == delimiter) begin++;
    while (*end == delimiter) end--;
    diff = (end - begin)+1;

    while (i < diff) {
        // avoid cases of adjacent delimiters
        // like "str1,str2,,,,,str3
        if (begin[i] == delimiter) {
            while (begin[i] == delimiter) i++;
            splits++;
        }
        i++;
    }
    splits += 1;
    *ret_size = splits;
    char** split_str = (char**)malloc(sizeof(char**)*splits);

    if (split_str == NULL) {
        return NULL;
    }
    for(i=0; i < splits; ++i) {
        split_str[i] = aux_slice(begin, delimiter, &begin);
    }
    return split_str;
}

Демо: https://onlinegdb.com/BJlWVdzGf

...