Как читать словами из входного файла, который игнорирует пунктуацию с помощью fscanf? - PullRequest
1 голос
/ 06 апреля 2019

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

Я также пробовал "%20[a-zA-Z]" и "%20[a-zA-Z] " в fscanf.

char** input;
input = (char **)malloc(numWordsInput*sizeof(char*));

for (i = 0; i < numWordsInput; i++)
{
  fscanf(in_file, "%s", buffer);
  sLength = strlen(buffer)+1;
  input[i] = (char *)malloc(sLength*sizeof(char));
}
rewind(in_file);
for (i = 0; i < numWordsInput; i++)
{
  fscanf(in_file, "%20[a-zA-Z]%*[a-zA-Z]", input[i]);
}

1 Ответ

0 голосов
/ 06 апреля 2019

Неясно, почему вы пытаетесь создать указатель на указатель до char для каждого слова, а затем выделяете для каждого слова, а просто классифицируете символы [a-zA-Z] в C-библиотеке есть ряд макросов в ctype.h, таких как isalpha(), которые делают именно это.

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

Чтобы обработать ввод файла и проверить, является ли каждый символ [a-zA-Z], все, что вам нужно сделать, это открыть файл и использовать символьно-ориентированную функцию ввода, такую ​​как fgetc, и проверить каждый символ с помощью isalpha(). Короткий пример, который делает именно это:

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

int main (int argc, char **argv) {

    int c;
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while ((c = fgetc (fp)) != EOF)     /* read each char in file */
        if (isalpha (c) || c == '\n')   /* is it a-zA-Z or \n  */
            putchar (c);                /* output it */

    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    return 0;
}

(базовый поток ввода-вывода в любом случае буферизуется (8192 байт в Linux), поэтому вы не понесете штраф за не чтение в больший буфер)

Пример входного файла

Так что, если у вас был грязный входной файл:

$ cat ../dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a

- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495

Пример использования / Вывод

... и просто хотел выбрать из него [a-zA-Z] символов (и '\n' символов, чтобы сохранить межстрочный интервал для примера), вы получите:

$ ./bin/readalpha ../dat/10intmess.txt
aa
aa

TheQuick
BrownFox
JumpsOver
A
Lazya
Dog

Если вы хотите включить [0-9], вы просто используете isalnum (c) вместо isalpha (c).

Вы также можете прочитать строку за раз (или слово за раз) и просто пройтись по буферу, делая то же самое. Например, вы можете сделать:

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

#define MAXC 4096

int main (int argc, char **argv) {

    char buf[MAXC];
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (fgets (buf, MAXC, fp)) {             /* read each line in file */
        char *p = buf;                          /* pointer to bufffer */
        while (*p) {                            /* loop over each char */
            if (isalpha (*p) || *p == '\n')     /* is it a-zA-Z or \n  */
                putchar (*p);                   /* output it */
            p++;
        }
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    return 0;
}

(вывод такой же)

Или, если вы предпочитаете использовать индексы, а не указатель, вы можете использовать:

    while (fgets (buf, MAXC, fp))               /* read each line in file */
        for (int i = 0; buf[i]; i++)            /* loop over each char */
            if (isalpha (buf[i]) || buf[i] == '\n') /* is it a-zA-Z or \n  */
                putchar (buf[i]);               /* output it */

(вывод такой же)

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

Распределение и хранение отдельных слов только буквенных символов

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

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

Хорошо читать отдельные слова с помощью fscanf. Затем, чтобы убедиться, что у вас есть альфа-символы для хранения, имеет смысл извлечь альфа-символы в отдельный временный буфер и проверить, действительно ли они были сохранены, за 1073 * до , выделяя хранилище для этого слова. Самое длинное слово в немедицинском постоянном словаре - 29 символов, поэтому достаточно фиксированного буфера, большего, чем этот (1024 символов используются ниже - Не экономьте на размере буфера! )

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

#define NPTR    8   /* initial number of pointers */
#define MAXC 1024
...
    char **input,           /* pointers to words */
        buf[MAXC];          /* read buffer */
    size_t  nptr = NPTR,    /* number of allcoated pointers */
            used = 0;       /* number of used pointers */

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

    while (fscanf (fp, "%s", buf) == 1) {       /* read each word in file  */
        size_t ndx = 0;                         /* alpha char index */
        char tmp[MAXC];                         /* temp buffer for alpha */
        if (used == nptr)                       /* check if realloc needed */
            input = xrealloc2 (input, sizeof *input, &nptr);    /* realloc */
        for (int i = 0; buf[i]; i++)            /* loop over each char */
            if (isalpha (buf[i]))               /* is it a-zA-Z or \n  */
                tmp[ndx++] = buf[i];            /* store alpha chars */
        if (!ndx)                               /* if no alpha-chars */
            continue;                           /* get next word */
        tmp[ndx] = 0;                           /* nul-terminate chars */
        input[used] = dupstr (tmp);             /* allocate/copy tmp */
        if (!input[used]) {                     /* validate word storage */
            if (used)           /* if words already stored */
                break;          /* break, earlier words still good */
            else {              /* otherwise bail */
                fputs ("error: allocating 1st word.\n", stderr);
                return 1;
            }
        }
        used++;                                 /* increment used count */
    }

( примечание: когда число указателей used равно выделенному числу, тогда input перераспределяется в два раза по сравнению с текущим числом указателей)

Функции xrealloc2 и dupstr являются просто вспомогательными функциями. xrealloc2 просто вызывает realloc и удваивает размер текущего выделения, проверяя выделение и возвращая перераспределенный указатель в случае успеха или в настоящее время завершая работу в случае сбоя - вы можете изменить его на возврат NULL для обработки ошибки, если вы лайк.

/** realloc 'ptr' of 'nelem' of 'psz' to 'nelem * 2' of 'psz'.
 *  returns pointer to reallocated block of memory with new
 *  memory initialized to 0/NULL. return must be assigned to
 *  original pointer in caller.
 */
void *xrealloc2 (void *ptr, size_t psz, size_t *nelem)
{   void *memptr = realloc ((char *)ptr, *nelem * 2 * psz);
    if (!memptr) {
        perror ("realloc(): virtual memory exhausted.");
        exit (EXIT_FAILURE);
        /* return NULL; */
    }   /* zero new memory (optional) */
    memset ((char *)memptr + *nelem * psz, 0, *nelem * psz);
    *nelem *= 2;
    return memptr;
}

Функция dupstr - это обычное strdup, но поскольку не все компиляторы предоставляют strdup, она используется для обеспечения переносимости.

/** allocate storage for s + 1 chars and copy contents of s
 *  to allocated block returning new sting on success,
 *  NULL otherwise.
 */
char *dupstr (const char *s)
{
    size_t len = strlen (s);
    char *str = malloc (len + 1);

    if (!str)
        return NULL;

    return memcpy (str, s, len + 1);
}

Использование помощников просто сохраняет основной корпусвашего кода немного чище, а не втиснуть все это в ваш цикл.

Поместив все вместе, вы можете сделать:

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

#define NPTR    8   /* initial number of pointers */
#define MAXC 1024

void *xrealloc2 (void *ptr, size_t psz, size_t *nelem);
char *dupstr (const char *s);

int main (int argc, char **argv) {

    char **input,           /* pointers to words */
        buf[MAXC];          /* read buffer */
    size_t  nptr = NPTR,    /* number of allcoated pointers */
            used = 0;       /* number of used pointers */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    input = malloc (nptr * sizeof *input);  /* allocate nptr pointers */
    if (!input) {                           /* validate every allocation */
        perror ("malloc-input");
        return 1;
    }

    while (fscanf (fp, "%s", buf) == 1) {       /* read each word in file  */
        size_t ndx = 0;                         /* alpha char index */
        char tmp[MAXC];                         /* temp buffer for alpha */
        if (used == nptr)                       /* check if realloc needed */
            input = xrealloc2 (input, sizeof *input, &nptr);    /* realloc */
        for (int i = 0; buf[i]; i++)            /* loop over each char */
            if (isalpha (buf[i]))               /* is it a-zA-Z or \n  */
                tmp[ndx++] = buf[i];            /* store alpha chars */
        if (!ndx)                               /* if no alpha-chars */
            continue;                           /* get next word */
        tmp[ndx] = 0;                           /* nul-terminate chars */
        input[used] = dupstr (tmp);             /* allocate/copy tmp */
        if (!input[used]) {                     /* validate word storage */
            if (used)           /* if words already stored */
                break;          /* break, earlier words still good */
            else {              /* otherwise bail */
                fputs ("error: allocating 1st word.\n", stderr);
                return 1;
            }
        }
        used++;                                 /* increment used count */
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < used; i++) {
        printf ("word[%3zu]: %s\n", i, input[i]);
        free (input[i]);    /* free storage when done with word */
    }
    free (input);           /* free pointers */

    return 0;
}

/** realloc 'ptr' of 'nelem' of 'psz' to 'nelem * 2' of 'psz'.
 *  returns pointer to reallocated block of memory with new
 *  memory initialized to 0/NULL. return must be assigned to
 *  original pointer in caller.
 */
void *xrealloc2 (void *ptr, size_t psz, size_t *nelem)
{   void *memptr = realloc ((char *)ptr, *nelem * 2 * psz);
    if (!memptr) {
        perror ("realloc(): virtual memory exhausted.");
        exit (EXIT_FAILURE);
        /* return NULL; */
    }   /* zero new memory (optional) */
    memset ((char *)memptr + *nelem * psz, 0, *nelem * psz);
    *nelem *= 2;
    return memptr;
}

/** allocate storage for s + 1 chars and copy contents of s
 *  to allocated block returning new sting on success,
 *  NULL otherwise.
 */
char *dupstr (const char *s)
{
    size_t len = strlen (s);
    char *str = malloc (len + 1);

    if (!str)
        return NULL;

    return memcpy (str, s, len + 1);
}

(используется тот же входной файл)

Пример использования / вывода

$ ./bin/readalphadyn ../dat/10intmess.txt
word[  0]: a
word[  1]: a
word[  2]: a
word[  3]: a
word[  4]: The
word[  5]: Quick
word[  6]: Brown
word[  7]: Fox
word[  8]: Jumps
word[  9]: Over
word[ 10]: A
word[ 11]: Lazy
word[ 12]: a
word[ 13]: Dog

Проверка использования памяти / ошибок

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

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

Для Linux valgrind - нормальный выбор.Для каждой платформы есть похожие проверки памяти.Все они просты в использовании, просто запустите вашу программу через него.

$ valgrind ./bin/readalphadyn ../dat/10intmess.txt
==8765== Memcheck, a memory error detector
==8765== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8765== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==8765== Command: ./bin/readalphadyn ../dat/10intmess.txt
==8765==
word[  0]: a
word[  1]: a
word[  2]: a
word[  3]: a
word[  4]: The
word[  5]: Quick
word[  6]: Brown
word[  7]: Fox
word[  8]: Jumps
word[  9]: Over
word[ 10]: A
word[ 11]: Lazy
word[ 12]: a
word[ 13]: Dog
==8765==
==8765== HEAP SUMMARY:
==8765==     in use at exit: 0 bytes in 0 blocks
==8765==   total heap usage: 17 allocs, 17 frees, 796 bytes allocated
==8765==
==8765== All heap blocks were freed -- no leaks are possible
==8765==
==8765== For counts of detected and suppressed errors, rerun with: -v
==8765== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

( примечание: Нет необходимости приводить к возврату malloc, это не нужно. См .: Использовать ли я результат malloc? )

Чтобы пропустить одиночный-символы слов (или выберите желаемый предел), вы можете просто изменить:

    if (ndx < 2)                            /* if 0/1 alpha-chars */
        continue;                           /* get next word */

Выполнение этого изменило бы ваши сохраненные слова на:

$ ./bin/readalphadyn ../dat/10intmess.txt
word[  0]: The
word[  1]: Quick
word[  2]: Brown
word[  3]: Fox
word[  4]: Jumps
word[  5]: Over
word[  6]: Lazy
word[  7]: Dog
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...