Разделить строку символов с многосимвольным разделителем в C

Я хочу разделить char *string на основе многосимвольного разделителя. я знаю это strtok() используется для разделения строки, но работает с разделителем из одного символа

Я хочу разделить строку char * на основе подстроки, такой как "abc" или любая другая подстрока. Как этого можно достичь?

5 ответов

Решение

Найти точку, в которой происходит желаемая последовательность, довольно легко: strstr поддерживает это:

char str[] = "this is abc a big abc input string abc to split up";
char *pos = strstr(str, "abc");

Итак, на данный момент, pos указывает на первое местоположение abc в большей строке. Здесь вещи становятся немного уродливыми. strtok имеет неприятный дизайн, где он 1) изменяет исходную строку, и 2) хранит указатель на "текущее" местоположение в строке внутри.

Если бы мы не возражали делать примерно то же самое, мы могли бы сделать что-то вроде этого:

char *multi_tok(char *input, char *delimiter) {
    static char *string;
    if (input != NULL)
        string = input;

    if (string == NULL)
        return string;

    char *end = strstr(string, delimiter);
    if (end == NULL) {
        char *temp = string;
        string = NULL;
        return temp;
    }

    char *temp = string;

    *end = '\0';
    string = end + strlen(delimiter);
    return temp;
}

Это работает. Например:

int main() {
    char input [] = "this is abc a big abc input string abc to split up";

    char *token = multi_tok(input, "abc");

    while (token != NULL) {
        printf("%s\n", token);
        token = multi_tok(NULL, "abc");
    }
}

производит примерно ожидаемый результат:

this is
 a big
 input string
 to split up

Тем не менее, это неуклюже, трудно сделать потокобезопасным (вы должны сделать его внутренним string переменная thread-local) и вообще просто дерьмовый дизайн. Использование (для одного примера) интерфейса что-то вроде strtok_rмы можем исправить хотя бы проблему безопасности потока:

typedef char *multi_tok_t;

char *multi_tok(char *input, multi_tok_t *string, char *delimiter) {
    if (input != NULL)
        *string = input;

    if (*string == NULL)
        return *string;

    char *end = strstr(*string, delimiter);
    if (end == NULL) {
        char *temp = *string;
        *string = NULL;
        return temp;
    }

    char *temp = *string;

    *end = '\0';
    *string = end + strlen(delimiter);
    return temp;
}

multi_tok_t init() { return NULL; }

int main() {
    multi_tok_t s=init();

    char input [] = "this is abc a big abc input string abc to split up";

    char *token = multi_tok(input, &s, "abc");

    while (token != NULL) {
        printf("%s\n", token);
        token = multi_tok(NULL, &s, "abc");
    }
}

Я думаю, я пока оставлю это здесь - чтобы получить действительно чистый интерфейс, мы действительно хотим изобрести что-то вроде сопрограмм, и это, вероятно, немного много, чтобы опубликовать здесь.

РЕДАКТИРОВАТЬ: Рассмотрены предложения от Алана и Sourav и написали основной код для того же.

#include <stdio.h>

#include <string.h>

int main (void)
{
  char str[] = "This is abc test abc string";

  char* in = str;
  char *delim = "abc";
  char *token;

  do {

    token = strstr(in,delim);

    if (token) 
      *token = '\0';

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

    in = token+strlen(delim);

  }while(token!=NULL);


  return 0;
}

Вы можете легко написать свой собственный парсер, используя strstr() добиться того же. Основной алгоритм может выглядеть так

  • использование strstr() найти первое вхождение всей строки разделителя
  • отметить индекс
  • скопируйте с начала до отмеченного индекса, это будет ваш ожидаемый токен.
  • чтобы проанализировать входные данные для последующих записей, отрегулируйте расстановку исходной строки, чтобы продвинуться по длине токена + длина строки разделителя.

Я написал простую реализацию, ориентированную на потоки:

      struct split_string {
    int len;
    char** str;
};
typedef struct split_string splitstr;
splitstr* split(char* string, char* delimiter) {
    int targetsize = 0;
    splitstr* ret = malloc(sizeof(splitstr));
    if (ret == NULL)
        return NULL;
    ret->str = NULL;
    ret->len = 0;
    char* pos;
    char* oldpos = string;
    int newsize;
    int dlen = strlen(delimiter);
    do {
        pos = strstr(oldpos, delimiter);
        if (pos) {
            newsize = pos - oldpos;
        } else {
            newsize = strlen(oldpos);
        }
        char* newstr = malloc(sizeof(char) * (newsize + 1));
        strncpy(newstr, oldpos, newsize);
        newstr[newsize] = '\0';
        oldpos = pos + dlen;
        ret->str = realloc(ret->str, (targetsize+1) * sizeof(char*));
        ret->str[targetsize++] = newstr;
        ret->len++;
    } while (pos != NULL);
    return ret;
}

Использовать:

      splitstr* ret = split(contents, "\n");
for (int i = 0; i < ret->len; i++) {
    printf("Element %d: %s\n", i, ret->str[i]);
}

Модифицированная реализация strsep , поддерживающая многобайтовый разделитель.

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

/**
 * Split a string into tokens
 * 
 * @in: The string to be searched
 * @delim: The string to search for as a delimiter
 */
char *strsep_m(char **in, const char *delim) {
  char *token = *in;

  if (token == NULL)
    return NULL;
    
  char *end = strstr(token, delim);
  
  if (end) {
    *end = '\0';
    end += strlen(delim);
  }
  
  *in = end;
  return token;
}

int main() {
  char input[] = "a##b##c";
  char delim[] = "##";
  char *token = NULL;
  char *cin = (char*)input;
  while ((token = strsep_m(&cin, delim)) != NULL) {
    printf("%s\n", token);
  }
}
Другие вопросы по тегам