Разбить строку на список токенов, используя другую строку в качестве разделителя? - PullRequest
2 голосов
/ 16 февраля 2020

Допустим, у меня есть эта строка:

char *myTestString = "Hello AND test AND test2";

Я хочу разбить это на множество {Hello, test, test2}, которое я, наконец, могу повторить.

Или у меня есть

char *myTestString2 = "Hi AND there AND test AND test2";

Я хочу разбить это на множество {Hi, there, test, test2}, которое позже можно будет повторить .

Как мне добиться этого, используя C?

РЕДАКТИРОВАТЬ : еще один пример разделения "Hello there AND test" должен выдать набор {Hello there, test }. Для уточнения "AND" здесь есть разделитель.

Ответы [ 5 ]

3 голосов
/ 16 февраля 2020

Если код не хочет изменять исходную строку, используйте strcspn(s, delimet) для поиска начальной части s , а не , составляющей delimit. Возвращает смещение.

Используйте strspn(s, delimet) для поиска начальной части s, состоящей из delimit. Возвращает смещение.

2 голосов
/ 16 февраля 2020

Примечание: как уже упоминалось, strtok() не подходит для строковых литералов, и в этом случае вам следует go с ответом Чукса (strcspn), но если это не проблема, и Вы можете работать со строками, хранящимися в массивах, а затем продолжить чтение. Последним средством было бы работать с копией строкового литерала.


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

Затем используйте strtok() для вашей строки, и если текущий токен отличается от "AND" (или любой другой строки, которую следует игнорировать - вы у меня также будет набор игнорируемых строк), затем вставьте его в набор, в противном случае перейдите к следующему токену.


Вот базовый c Завершить минимальный пример, чтобы начать работу. :

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

#define N 3     // Max size of set
#define LEN 32  // Max length of word - 1

int main ()
{
  char set[N][LEN] = {0};
  char* ignore_str = "AND";
  char str[] ="Hello AND test AND test2";
  char* pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," ");
  int i = 0;
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    if(strcmp(pch, ignore_str))
      strcpy(set[i++], pch);
    pch = strtok (NULL, " ");
  }
  printf("My set is: {");
  for(int j = 0; j < i; ++j)
    printf("%s, ", set[j]);
  printf("}\n");
  return 0;
}

Вывод:

Splitting string "Hello AND test AND test2" into tokens:
Hello
AND
test
AND
test2
My set is: {Hello, test, test2, }

Здесь я использовал массив для представления набора, предполагая, что максимальный размер набора будет равен 3. Конечно, вы можете использовать вместо этого используется больше динамический c (например, динамический c выделенный памяти массив или список).

1 голос
/ 16 февраля 2020

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

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

int main ( void) {
    char *myTestString = "   AND SANDY AND Hello there AND AND test AND test2 AND test3    ";
    char *match = "AND";
    char *first = myTestString;
    char *start = myTestString;
    char *find = myTestString;
    int len = strlen ( match);

    while ( isspace ( (unsigned char)*start)) {//skip leading whitespace
        ++start;
        ++first;
    }
    while ( ( find = strstr ( start, match))) {
        if ( find != first) {
            //check for leading and trailing space or terminating zero
            while ( ! (isspace ( (unsigned char)*(find - 1))
            &&  ( isspace ( (unsigned char)*(find + len)) || 0 == *(find + len)))) {
                find = strstr ( find + 1, match);
                if ( ! find) {
                    find = start + strlen ( start);
                    while ( isspace ( (unsigned char)*(find - 1))) {
                        --find;
                    }
                    break;
                }
            }
            int span = (int)(find - start);
            if ( span) {
                printf ( "%.*s\n", span, start);
            }
        }
        start = find + strlen ( match);
        while ( isspace ( (unsigned char)*start)) {//skip trailing whitespace
            ++start;
        }
    }
    if ( *start) {
        int end = strlen ( start) - 1;
        while ( isspace ( (unsigned char)start[end])) {
            --end;//remove trailing whitspace
        }
        printf ("%.*s\n", end + 1, start);
    }

    return 0;
}

Выделите память для char**, выделите память и скопируйте каждый токен.

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

char **freetokens ( char **tokens);
void showtokens ( char **tokens);
char **addtoken ( char **tokens, int *count, char *text, int size);

int main ( void) {
    char *myTestString = "   AND SANDY AND Hello there test AND AND test2 AND test3   ";
    char *match = "AND";
    char *first = myTestString;
    char *start = myTestString;
    char *find = myTestString;
    char **tokens = NULL;
    int items = 0;
    int len = strlen ( match);

    while ( isspace ( (unsigned char)*start)) {//skip leading whitespace
        ++start;
        ++first;
    }
    while ( ( find = strstr ( start, match))) {
        if ( find != first) {
            //check for leading and trailing space or terminating zero
            while ( ! (isspace ( (unsigned char)*(find - 1))
            &&  ( isspace ( (unsigned char)*(find + len)) || 0 == *(find + len)))) {
                find = strstr ( find + 1, match);
                if ( ! find) {
                    find = start + strlen ( start);
                    while ( isspace ( (unsigned char)*(find - 1))) {
                        --find;//remove trailing whitespace
                    }
                    break;
                }
            }
            int span = (int)(find - start);
            if ( span) {
                tokens = addtoken ( tokens, &items, start, span);
            }
        }
        start = find + strlen ( match);
        while ( isspace ( (unsigned char)*start)) {//skip trailing whitespace
            ++start;
        }
    }
    if ( *start) {
        int end = strlen ( start);
        while ( isspace ( (unsigned char)start[end - 1])) {
            --end;
        }
        tokens = addtoken ( tokens, &items, start, end);
    }

    showtokens ( tokens);

    tokens = freetokens ( tokens);

    return 0;
}

char **addtoken ( char **tokens, int *count, char *text, int size) {
    char **temp = NULL;
    if ( NULL == ( temp = realloc ( tokens, sizeof *tokens * ( *count + 2)))) {
        fprintf ( stderr, "problem realloc tokens\n");
        return tokens;
    }
    tokens = temp;
    tokens[*count + 1] = NULL;//sentinel
    if ( NULL == ( tokens[*count] = malloc ( size + 1))) {
        fprintf ( stderr, "problem realloc tokens[]\n");
        return tokens;
    }
    memmove ( tokens[*count], text, size);
    tokens[*count][size] = 0;//terminate
    ++*count;

    return tokens;
}

char **freetokens ( char **tokens) {
    int each = 0;
    while ( tokens && tokens[each]) {
        free ( tokens[each]);
        ++each;
    }
    free ( tokens);

    return NULL;
}
1 голос
/ 16 февраля 2020

Вот, пожалуйста.

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

char ** split( const char *s1, const char *s2 )
{
    char **tokens = malloc( sizeof( char * ) );
    int success = tokens != NULL;

    if ( success )
    {
        const char *delim = " \t";
        *tokens = NULL;

        for ( size_t n = 1, len = strlen( s2 ); success && *s1; )
        {
            s1 += strspn( s1, delim );

            if ( *s1 )
            {
                const char *p = s1;

                s1 += strcspn( s1, delim );

                if ( strncmp( p, s2, len ) != 0 )
                {
                    char **tmp = realloc( tokens, ( n + 1 ) * sizeof( char * ) );

                    if ( ( success = tmp != NULL ) )
                    {
                        tokens = tmp;

                        success = ( tokens[n-1] = calloc( 1, s1 - p + 1 ) )  != NULL;
                        strncpy( tokens[n-1], p, s1 - p );
                        tokens[n] = NULL;
                        ++n;
                    }

                    if ( !success )
                    {
                        for ( size_t i = 0; i < n; i++ ) free( tokens[i] );
                        free( tokens );
                    }
                }
            }
        }
    }       

    return tokens;
}

int main(void) 
{
    const char *s1 = "Hi AND there AND test AND test2";
    const char *s2 = "AND";

    char **tokens = split( s1, s2 );

    if ( tokens != NULL )
    {
        for ( char **p = tokens; *p != NULL; ++p )
        {
            puts( *p );
        }

        char **p = tokens;
        do
        {
            free( *p );
        } while ( *p++ != NULL );

        free( tokens );
    }

    return 0;
}

Вывод программы:

Hi
there
test
test2

Функция возвращает NULL, если выделение памяти не было успешным. В противном случае он возвращает указатель на массив типа элемента char *, последний элемент которого является нулевым указателем.

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

После вашего комментария к моему предыдущему решению вам понадобится следующее

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

char ** split( const char *s1, const char *s2 )
{
    char **tokens = malloc( sizeof( char * ) );
    int success = tokens != NULL;

    if ( success )
    {
        const char *delim = " \t";
        *tokens = NULL;

        for ( size_t n = 1, len2 = strlen( s2 ); success && *s1; )
        {
            for ( int empty = 1; empty; ) 
            {
                s1 += strspn( s1, delim );
                if ( ( empty = strncmp( s1, s2, len2 ) == 0 ) )
                {
                    s1 += len2;
                }
            }               

            if ( *s1 )
            {
                const char *p = strstr( s1, s2 );

                size_t len1 = p == NULL ? strlen( s1 ) : p - s1;

                char **tmp = realloc( tokens, ( n + 1 ) * sizeof( char * ) );

                if ( ( success = tmp != NULL ) )
                {
                    tokens = tmp;

                    success = ( tokens[n-1] = calloc( 1, len1 +  1 ) )  != NULL;
                    strncpy( tokens[n-1], s1, len1 );
                    tokens[n] = NULL;
                    ++n;

                    s1 += p == NULL ? len1 : len1 + len2; 
                }

                if ( !success )
                {
                    for ( size_t i = 0; i < n; i++ ) free( tokens[i] );
                    free( tokens );
                }
            }
        }
    }       

    return tokens;
}

int main(void) 
{
    const char *s1 = "Hi there AND test test2";
    const char *s2 = "AND";

    char **tokens = split( s1, s2 );

    if ( tokens != NULL )
    {
        for ( char **p = tokens; *p != NULL; ++p )
        {
            puts( *p );
        }

        char **p = tokens;
        do
        {
            free( *p );
        } while ( *p++ != NULL );

        free( tokens );
    }

    return 0;
}

Вывод программы:

Hi there 
test test2

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

0 голосов
/ 17 февраля 2020

strstr() - инструмент, который вы ищете. Он может найти строку внутри другой строки.

Вот простое решение с этими дополнительными спецификациями:

  • возвращаемое значение представляет собой массив (n + 1) записей, последний один - нулевой указатель.
  • строка-разделитель может появляться где угодно, в том числе внутри слова.
  • подстроки обрезаются: начальный и конечный пробел удаляется
  • выделены подстроки с strndup(), который стандартизирован в POSIX.
  • строка разделителя должна иметь длину не менее 1
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *strdup_trim(const char *s, size_t n) {
    while (n > 0 && isspace((unsigned char)*s)) {
        s++;
        n--;
    }
    while (n > 0 && isspace((unsigned char)s[n - 1])) {
        n--;
    }
    return strndup(s, n);
}

char **split(const char *str, const char *sep) {
    size_t i, n, sep_len = strlen(sep);
    char **a;
    const char *p, *p0;

    if (sep_len == 0)
        return NULL;
    for (n = 0, p = str; (p = strstr(p, sep)) != NULL; n++, p += sep_len)
        continue;
    a = malloc(sizeof(*a) * (n + 2));
    if (a == NULL)
        return NULL;
    for (i = 0, p = str; (p = strstr(p0 = p, sep)) != NULL; i++, p += sep_len) {
        a[i] = strdup_trim(p0, p - p0);
    }
    a[i++] = strdup_trim(p0, strlen(p0));
    a[i] = NULL;
    return a;
}

void free_split(char **a) {
    if (a) {
        for (size_t i = 0; a[i]; i++)
            free(a[i]);
        free(a);
    }
}

void test(const char *str, const char *sep) {
    char **a = split(str, sep);
    printf("split('%s', '%s') -> {", str, sep);
    for (size_t i = 0; a[i]; i++)
        printf("%s '%s'", &","[!i], a[i]);
    printf(" }\n");
    free_split(a);
}

int main() {
    test("Hello AND test AND test2", "AND");
    test("Hi AND there AND test AND test2", "AND");
    test("Hello there AND test", "AND");
    return 0;
}

Вывод:

split('Hello AND test AND test2', 'AND') -> { 'Hello', 'test', 'test2' }
split('Hi AND there AND test AND test2', 'AND') -> { 'Hi', 'there', 'test', 'test2' }
split('Hello there AND test', 'AND') -> { 'Hello there', 'test' }
...