C - Чтение слов ограниченной длины в строках неограниченной длины - PullRequest
0 голосов
/ 25 августа 2018

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

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

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

Полагаю, я мог бы читать символ за символом, игнорировать пробелы и искать '\ n'. Есть ли более элегантный способ?

Спасибо

РЕДАКТИРОВАТЬ: я ограничен использованием C99 и стандартных библиотек.

1 Ответ

0 голосов
/ 27 августа 2018

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

  1. Он не верит, что пользователь знает, что он предоставляет, поскольку данные должны подчиняться определенным правилам, поэтому предполагается, что пользователь будет злоупотреблять этими правилами.
  2. Следовательно, он записывает все слова, найденные в каждой строке, записывает слова во всю длину и, следовательно, использует динамическое выделение памяти.

До того, как я опубликовал сообщение, он прошел довольно кислотное тестированиеЭто.Вы компилируете с make UFLAGS=-DTEST, чтобы получить более короткие фрагменты строк (64 байта против 4096 по умолчанию), и это также дает вам дополнительный диагностический вывод.Я много тестировал с MAX_LINE_LEN в 6 вместо 64 - это было хорошо для отладки проблем со словами, продолженными по нескольким фрагментам строки.

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

enum { MAX_WORD_CNT = 8 };

#ifdef TEST
static int debug = 1;
enum { MAX_LINE_LEN = 64 };
#else
static int debug = 0;
enum { MAX_LINE_LEN = 4096 };
#endif /* TEST */

typedef struct Word
{
    size_t length;
    char  *word;
} Word;

typedef struct WordList
{
    size_t  num_words;
    size_t  max_words;
    Word   *words;
} WordList;

typedef struct LineControl
{
    size_t   line_length;
    bool     part_word;
    size_t   part_len;
    WordList list;
} LineControl;

static void init_wordlist(WordList *list)
{
    list->num_words = 0;
    list->max_words = 0;
    list->words = 0;
}

static void free_wordlist(WordList *list)
{
    assert(list != 0);
    for (size_t i = 0; i < list->num_words; i++)
        free(list->words[i].word);
    free(list->words);
    init_wordlist(list);
}

static void extend_word(const char *extn, size_t ext_len, Word *word)
{
    if (debug)
        printf("old (%zu) = [%s]; extra (%zu) = [%.*s]\n", word->length, word->word,
                ext_len, (int)ext_len, extn);
    size_t space = word->length + ext_len + 1;
    char *new_space = realloc(word->word, space);
    if (new_space == 0)
    {
        fprintf(stderr, "failed to reallocate %zu bytes of memory\n", space);
        exit(EXIT_FAILURE);
    }
    word->word = new_space;
    memmove(word->word + word->length, extn, ext_len);
    word->length += ext_len;
    word->word[word->length] = '\0';
    if (debug)
        printf("new (%zu) = [%s]\n", word->length, word->word);
    }

static void addword_wordlist(const char *word, size_t word_len, WordList *list)
{
    if (list->num_words >= list->max_words)
    {
        assert(list->num_words == list->max_words);
        size_t new_max = list->max_words * 2 + 2;
        Word *new_words = realloc(list->words, new_max * sizeof(*new_words));
        if (new_words == 0)
        {
            fprintf(stderr, "failed to allocate %zu bytes of memory\n", new_max * sizeof(*new_words));
            exit(EXIT_FAILURE);
        }
        list->max_words = new_max;
        list->words = new_words;
    }
    list->words[list->num_words].word = malloc(word_len + 1);
    if (list->words[list->num_words].word == 0)
    {
        fprintf(stderr, "failed to allocate %zu bytes of memory\n", word_len + 1);
        exit(EXIT_FAILURE);
    }
    Word *wp = &list->words[list->num_words];
    wp->length = word_len;
    memmove(wp->word, word, word_len);
    wp->word[word_len] = '\0';
    list->num_words++;
}

static void init_linectrl(LineControl *ctrl)
{
    ctrl->line_length = 0;
    ctrl->part_word = false;
    ctrl->part_len = 0;
    init_wordlist(&ctrl->list);
}

static int parse_fragment(const char *line, LineControl *ctrl)
{
    char   whisp[] = " \t";
    size_t offset = 0;
    bool   got_eol = false;

    /* The only newline in the string is at the end, if it is there at all */
    assert(strchr(line, '\n') == strrchr(line, '\n'));
    assert(strchr(line, '\n') == 0 || *(strchr(line, '\n') + 1) == '\0');
    if (debug && ctrl->part_word)
    {
        assert(ctrl->list.num_words > 0);
        printf("Dealing with partial word on entry (%zu: [%s])\n",
               ctrl->part_len, ctrl->list.words[ctrl->list.num_words - 1].word);
    }

    size_t o_nonsp = 0;
    while (line[offset] != '\0')
    {
        size_t n_whisp = strspn(line + offset, whisp);
        size_t n_nonsp = strcspn(line + offset + n_whisp, whisp);
        if (debug)
            printf("offset %zu, whisp %zu, nonsp %zu\n", offset, n_whisp, n_nonsp);
        got_eol = false;
        ctrl->line_length += n_whisp + n_nonsp;
        if (line[offset + n_whisp + n_nonsp - 1] == '\n')
        {
            assert(n_nonsp > 0);
            got_eol = true;
            n_nonsp--;
        }
        if (n_whisp + n_nonsp == 0)
        {
            o_nonsp = 0;
            break;
        }

        if (n_whisp != 0)
        {
            ctrl->part_word = false;
            ctrl->part_len = 0;
        }

        /* Add words to list if the list is not already full */
        if (n_nonsp > 0)
        {
            const char *word = line + offset + n_whisp;
            if (ctrl->part_word)
            {
                assert(ctrl->list.num_words > 0);
                extend_word(word, n_nonsp,
                            &ctrl->list.words[ctrl->list.num_words - 1]);
            }
            else
            {
                addword_wordlist(word, n_nonsp, &ctrl->list);
            }
        }

        offset += n_whisp + n_nonsp;
        if (line[offset] != '\0')
        {
            ctrl->part_word = false;
            ctrl->part_len = 0;
        }
        o_nonsp = n_nonsp;
        if (got_eol)
            break;
    }

    /* Partial word detection */
    if (o_nonsp > 0 && !got_eol)
    {
        ctrl->part_word = true;
        ctrl->part_len += o_nonsp;
    }
    else
    {
        ctrl->part_word = false;
        ctrl->part_len = 0;
    }

    /* If seen newline; line complete */
    /* If No newline; line incomplete */
    return !got_eol;
}

int main(void)
{
    char line[MAX_LINE_LEN];
    size_t lineno = 0;

    while (fgets(line, sizeof(line), stdin) != 0)
    {
        LineControl ctrl;
        init_linectrl(&ctrl);
        lineno++;
        if (debug)
            printf("Line %zu: (%zu) [[%s]]\n", lineno, strlen(line), line);

        int extra = 0;
        while (parse_fragment(line, &ctrl) != 0 &&
               fgets(line, sizeof(line), stdin) != 0)
        {
            if (debug)
                printf("Extra %d for line %zu: (%zu) [[%s]]\n",
                       ++extra, lineno, strlen(line), line);
        }

        WordList *list = &ctrl.list;
        printf("Line %zu: length %zu, words = %zu\n",
               lineno, ctrl.line_length, list->num_words);
        size_t num_words = list->num_words;
        if (num_words > MAX_WORD_CNT)
            num_words = MAX_WORD_CNT;
        for (size_t i = 0; i < num_words; i++)
        {
            printf("  %zu: (%zu) %s\n",
                   i + 1, list->words[i].length, list->words[i].word);
        }
        putchar('\n');
        free_wordlist(&ctrl.list);
    }

    return 0;
}

У меня была более простая версиябез динамического выделения памяти, но он не работал должным образом, когда слово было разделено на два фрагмента строки (поэтому, если размер фрагмента строки был 6 (5 символов плюс нулевой байт), а максимальная длина слова была 16Скажем, тогда код столкнулся с трудностями при сборке фрагментов. Следовательно, я принял более простой подход - хранить все каждое слово. Из вопроса о том, каковы максимальные размеры слова, не ясно. Если код должен возражать против чего-либо другогочем 0, 3 или 4 слова, данные доступны для подачи этих жалоб. Если код должен возражать против слов, которые длиннее некоторой длины, например, 32, данные также доступны для подачи этих жалоб.

Один из более простых тестовых файлов: test-data.1:

    a b   
    a b      c         d                                                        

1123xxsdfdsfsfdsfdssa          1234ddfxxyff            frrrdds
1123dfdffdfdxxxxxxxxxas                        1234ydfyyyzm   knsaaass      1234asdafxxfrrrfrrrsaa    
               1123werwetrretttrretertre       aaaa     bbbbbb      ccccc        
k
                                                apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                              apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                                      apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                                                  apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                                                           

. Здесь есть все виды вкладок, как показано на рисунке.Эта версия тех же данных, где вкладки отображаются как \t:

    a b   
    a b      c         d                                                        
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
1123xxsdfdsfsfdsfdssa          1234ddfxxyff            frrrdds
1123dfdffdfdxxxxxxxxxas                        1234ydfyyyzm   knsaaass      1234asdafxxfrrrfrrrsaa    
               1123werwetrretttrretertre       aaaa     bbbbbb      ccccc        
k
  \t\t \t \t\t\t \t \t \t\t\t\tapoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper\t\t\t    \t\t\t\tapoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper  \t \t \t \t\t\t\t \t \tapoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper\t\t           \t\t\t\t \t \t \t \t\tapoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper\t\t\t\t\t\t    \t \t \t \t      \t \t \t 

Запуск этого скрипта awk анализирует данные:

$ awk '{ printf "%3d %d [%s]\n", length($0) + 1, NF, $0 }' test-data.1
  1 0 []
  5 0 [    ]
 11 2 [    a b   ]
 81 4 [    a b      c         d                                                        ]
 20 0 [                                                     ]
 63 3 [1123xxsdfdsfsfdsfdssa          1234ddfxxyff            frrrdds]
103 4 [1123dfdffdfdxxxxxxxxxas                        1234ydfyyyzm   knsaaass      1234asdafxxfrrrfrrrsaa    ]
 82 4 [               1123werwetrretttrretertre       aaaa     bbbbbb      ccccc        ]
  2 1 [k]
494 4 [                                                 apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                              apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                                      apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                      apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper                                           ]
$

Вывод изПрограмма для этого файла данных:

Line 1: length 1, words = 0

Line 2: length 5, words = 0

Line 3: length 11, words = 2
  1: (1) a
  2: (1) b

Line 4: length 81, words = 4
  1: (1) a
  2: (1) b
  3: (1) c
  4: (1) d

Line 5: length 20, words = 0

Line 6: length 63, words = 3
  1: (21) 1123xxsdfdsfsfdsfdssa
  2: (12) 1234ddfxxyff
  3: (7) frrrdds

Line 7: length 103, words = 4
  1: (23) 1123dfdffdfdxxxxxxxxxas
  2: (12) 1234ydfyyyzm
  3: (8) knsaaass
  4: (22) 1234asdafxxfrrrfrrrsaa

Line 8: length 82, words = 4
  1: (25) 1123werwetrretttrretertre
  2: (4) aaaa
  3: (6) bbbbbb
  4: (5) ccccc

Line 9: length 2, words = 1
  1: (1) k

Line 10: length 494, words = 4
  1: (98) apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper
  2: (98) apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper
  3: (98) apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper
  4: (98) apoplectic-catastrophe-mongers-of-the-world-unite-for-you-have-nothing-to-lose-but-your-bad-temper

Вы можете видеть данные из скрипта awk, появляющегося в выходных данных.

Этот код доступен в моем SOQ (Вопросы переполнения стека) в GitHub в виде файлов scan59.c, test-data.1, test-data.2 и test-data.3 в / Users / jleffler / soq / src / so-5201-4002 subкаталог.В частности, файл test-data.3 содержит одну строку с 9955 символами и 693 словами, а также другие строки, которые являются менее строгими тестами.

Код запускается, компилируется и запускается корректно на Mac под управлением macOS 10.13.6 High Sierra, используя GCC 8.2.0 и Valgrind 3.14.0.GIT.(Хотя makefile предусматривает C11, в этом коде нет ничего специфичного для C11; он полностью совместим с C99. Он также аккуратно компилируется с make SFLAGS='-std=c99 -pedantic'.)

...