Как разбить строку с разделителем больше, чем один символ? - PullRequest
0 голосов
/ 28 марта 2010

Предположим, у меня есть это:

"foo bar 1 and foo bar 2"

Как я могу разделить его на:

foo bar 1
foo bar 2

Я пытался strtok() и strsep(), но ни один не работал. Они не распознают "и" как разделитель, они распознают "a", "n" и "d" как разделители.

Любая функция, которая поможет мне в этом, или мне придется разделить пробел и выполнить некоторые манипуляции со строками?

Ответы [ 4 ]

5 голосов
/ 28 марта 2010

Основная проблема с разбиением строк в C заключается в том, что это неизбежно приводит к некоторому динамическому управлению памятью, и этого, как правило, следует избегать по стандартной библиотеке, когда это возможно. Именно поэтому ни один из стандартных Функции C имеют дело с динамическим распределением памяти, только malloc / calloc / realloc сделай это.

Но это не так уж сложно сделать самому. Позволь мне провести тебя через это.

Нам нужно вернуть несколько строк, и самый простой способ сделать это это вернуть массив указателей на строки, который заканчивается Нулевой элемент. За исключением конечного NULL, каждый элемент в массиве указывает на динамически размещаемая строка.

Сначала нам понадобится пара вспомогательных функций для работы с такими массивами. Самый простой - это тот, который вычисляет количество строк (элементов до окончательного NULL):

/* Return length of a NULL-delimited array of strings. */
size_t str_array_len(char **array)
{
    size_t len;

    for (len = 0; array[len] != NULL; ++len)
        continue;
    return len;
}

Еще одна простая функция - освободить массив:

/* Free a dynamic array of dynamic strings. */
void str_array_free(char **array)
{
    if (array == NULL)
        return;
    for (size_t i = 0; array[i] != NULL; ++i)
        free(array[i]);
    free(array);
}

Несколько сложнее функция, которая добавляет копию строки в массив. Он должен обрабатывать ряд особых случаев, например, когда массив еще не существует (весь массив равен NULL). Кроме того, это должно обрабатывать строки, которые не заканчиваются на '\ 0', чтобы было легче наша фактическая функция расщепления, чтобы просто использовать части входной строки, когда добавление.

/* Append an item to a dynamically allocated array of strings. On failure,
   return NULL, in which case the original array is intact. The item
   string is dynamically copied. If the array is NULL, allocate a new
   array. Otherwise, extend the array. Make sure the array is always
   NULL-terminated. Input string might not be '\0'-terminated. */
char **str_array_append(char **array, size_t nitems, const char *item, 
                        size_t itemlen)
{
    /* Make a dynamic copy of the item. */
    char *copy;
    if (item == NULL)
        copy = NULL;
    else {
        copy = malloc(itemlen + 1);
        if (copy == NULL)
            return NULL;
        memcpy(copy, item, itemlen);
        copy[itemlen] = '\0';
    }

    /* Extend array with one element. Except extend it by two elements, 
       in case it did not yet exist. This might mean it is a teeny bit
       too big, but we don't care. */
    array = realloc(array, (nitems + 2) * sizeof(array[0]));
    if (array == NULL) {
        free(copy);
        return NULL;
    }

    /* Add copy of item to array, and return it. */
    array[nitems] = copy;
    array[nitems+1] = NULL;
    return array;
}

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

Наконец, у нас есть функция расщепления. Это также необходимо обрабатывать некоторые особые случаи:

  • Входная строка может начинаться или заканчиваться разделителем.
  • Рядом могут быть разделители.
  • Входная строка может вообще не включать разделитель.

Я решил добавить пустую строку к результату, если разделитель рядом с началом или концом входной строки или рядом с другой разделитель. Если вам нужно что-то еще, вам нужно настроить Код.

Кроме особых случаев и некоторой обработки ошибок, расщепление теперь достаточно просто.

/* Split a string into substrings. Return dynamic array of dynamically
   allocated substrings, or NULL if there was an error. Caller is
   expected to free the memory, for example with str_array_free. */
char **str_split(const char *input, const char *sep)
{
    size_t nitems = 0;
    char **array = NULL;
    const char *start = input;
    char *next = strstr(start, sep);
    size_t seplen = strlen(sep);
    const char *item;
    size_t itemlen;

    for (;;) {
        next = strstr(start, sep);
        if (next == NULL) {
            /* Add the remaining string (or empty string, if input ends with
               separator. */
            char **new = str_array_append(array, nitems, start, strlen(start));
            if (new == NULL) {
                str_array_free(array);
                return NULL;
            }
            array = new;
            ++nitems;
            break;
        } else if (next == input) {
            /* Input starts with separator. */
            item = "";
            itemlen = 0;
        } else {
            item = start;
            itemlen = next - item;
        }
        char **new = str_array_append(array, nitems, item, itemlen);
        if (new == NULL) {
            str_array_free(array);
            return NULL;
        }
        array = new;
        ++nitems;
        start = next + seplen;
    }

    if (nitems == 0) {
        /* Input does not contain separator at all. */
        assert(array == NULL);
        array = str_array_append(array, nitems, input, strlen(input));
    }

    return array;
}

Вот вся программа в одном куске. Он также включает в себя основную программу запустить несколько тестовых случаев.

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


/* Append an item to a dynamically allocated array of strings. On failure,
   return NULL, in which case the original array is intact. The item
   string is dynamically copied. If the array is NULL, allocate a new
   array. Otherwise, extend the array. Make sure the array is always
   NULL-terminated. Input string might not be '\0'-terminated. */
char **str_array_append(char **array, size_t nitems, const char *item, 
                        size_t itemlen)
{
    /* Make a dynamic copy of the item. */
    char *copy;
    if (item == NULL)
        copy = NULL;
    else {
        copy = malloc(itemlen + 1);
        if (copy == NULL)
            return NULL;
        memcpy(copy, item, itemlen);
        copy[itemlen] = '\0';
    }

    /* Extend array with one element. Except extend it by two elements, 
       in case it did not yet exist. This might mean it is a teeny bit
       too big, but we don't care. */
    array = realloc(array, (nitems + 2) * sizeof(array[0]));
    if (array == NULL) {
        free(copy);
        return NULL;
    }

    /* Add copy of item to array, and return it. */
    array[nitems] = copy;
    array[nitems+1] = NULL;
    return array;
}


/* Free a dynamic array of dynamic strings. */
void str_array_free(char **array)
{
    if (array == NULL)
        return;
    for (size_t i = 0; array[i] != NULL; ++i)
        free(array[i]);
    free(array);
}


/* Split a string into substrings. Return dynamic array of dynamically
   allocated substrings, or NULL if there was an error. Caller is
   expected to free the memory, for example with str_array_free. */
char **str_split(const char *input, const char *sep)
{
    size_t nitems = 0;
    char **array = NULL;
    const char *start = input;
    char *next = strstr(start, sep);
    size_t seplen = strlen(sep);
    const char *item;
    size_t itemlen;

    for (;;) {
        next = strstr(start, sep);
        if (next == NULL) {
            /* Add the remaining string (or empty string, if input ends with
               separator. */
            char **new = str_array_append(array, nitems, start, strlen(start));
            if (new == NULL) {
                str_array_free(array);
                return NULL;
            }
            array = new;
            ++nitems;
            break;
        } else if (next == input) {
            /* Input starts with separator. */
            item = "";
            itemlen = 0;
        } else {
            item = start;
            itemlen = next - item;
        }
        char **new = str_array_append(array, nitems, item, itemlen);
        if (new == NULL) {
            str_array_free(array);
            return NULL;
        }
        array = new;
        ++nitems;
        start = next + seplen;
    }

    if (nitems == 0) {
        /* Input does not contain separator at all. */
        assert(array == NULL);
        array = str_array_append(array, nitems, input, strlen(input));
    }

    return array;
}


/* Return length of a NULL-delimited array of strings. */
size_t str_array_len(char **array)
{
    size_t len;

    for (len = 0; array[len] != NULL; ++len)
        continue;
    return len;
}


#define MAX_OUTPUT 20


int main(void)
{
    struct {
        const char *input;
        const char *sep;
        char *output[MAX_OUTPUT];
    } tab[] = {
        /* Input is empty string. Output should be a list with an empty 
           string. */
        {
            "",
            "and",
            {
                "",
                NULL,
            },
        },
        /* Input is exactly the separator. Output should be two empty 
           strings. */
        {
            "and",
            "and",
            {
                "",
                "",
                NULL,
            },
        },
        /* Input is non-empty, but does not have separator. Output should
           be the same string. */
        {
            "foo",
            "and",
            {
                "foo",
                NULL,
            },
        },
        /* Input is non-empty, and does have separator. */
        {
            "foo bar 1 and foo bar 2",
            " and ",
            {
                "foo bar 1",
                "foo bar 2",
                NULL,
            },
        },
    };
    const int tab_len = sizeof(tab) / sizeof(tab[0]);
    bool errors;

    errors = false;

    for (int i = 0; i < tab_len; ++i) {
        printf("test %d\n", i);

        char **output = str_split(tab[i].input, tab[i].sep);
        if (output == NULL) {
            fprintf(stderr, "output is NULL\n");
            errors = true;
            break;
        }
        size_t num_output = str_array_len(output);
        printf("num_output %lu\n", (unsigned long) num_output);

        size_t num_correct = str_array_len(tab[i].output);
        if (num_output != num_correct) {
            fprintf(stderr, "wrong number of outputs (%lu, not %lu)\n",
                    (unsigned long) num_output, (unsigned long) num_correct);
            errors = true;
        } else {
            for (size_t j = 0; j < num_output; ++j) {
                if (strcmp(tab[i].output[j], output[j]) != 0) {
                    fprintf(stderr, "output[%lu] is '%s' not '%s'\n",
                            (unsigned long) j, output[j], tab[i].output[j]);
                    errors = true;
                    break;
                }
            }
        }

        str_array_free(output);
        printf("\n");
    }

    if (errors)
        return EXIT_FAILURE;   
    return 0;
}
5 голосов
/ 28 марта 2010

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

2 голосов
/ 28 марта 2010

Вот хороший короткий пример, который я только что написал, показывающий, как использовать strstr для разбиения строки на заданную строку:

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

void split(char *phrase, char *delimiter)
{
    char *loc = strstr(phrase, delimiter);
    if (loc == NULL)
    {
        printf("Could not find delimiter\n");
    }
    else
    {
        char buf[256]; /* malloc would be much more robust here */
        int length = strlen(delimiter);
        strncpy(buf, phrase, loc - phrase);
        printf("Before delimiter: '%s'\n", buf);
        printf("After delimiter: '%s'\n", loc+length);
    }
}

int main()
{
    split("foo bar 1 and foo bar 2", "and");
    printf("-----\n");
    split("foo bar 1 and foo bar 2", "quux");
    return 0;
}

Выход:

Before delimiter: 'foo bar 1 '
After delimiter: ' foo bar 2'
-----
Could not find delimiter

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

0 голосов
/ 05 февраля 2014

Если вы знаете тип разделителя, например, запятую или точку с запятой, вы можете попробовать это:

#include<stdio.h>
#include<conio.h>
int main()
{
  int i=0,temp=0,temp1=0, temp2=0;
  char buff[12]="123;456;789";
   for(i=0;buff[i]!=';',i++)
   {
     temp=temp*10+(buff[i]-48);
   }
   for(i=0;buff[i]!=';',i++)
   {
     temp1=temp1*10+(buff[i]-48);
   }
   for(i=0;buff[i],i++)
   {
     temp2=temp2*10+(buff[i]-48);
   }
    printf("temp=%d temp1=%d temp2=%d",temp,temp1,temp2);
    getch();
  return 0;
}

Выход:

temp=123 temp1=456 temp2=789
...