Разделить строку символов с многосимвольным разделителем в 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);
}
}