通过 C 中的标准输入读取大型列表

reading large lists through stdin in C

如果我的程序要通过 stdin 传入大量数字列表,那么最有效的读取方法是什么?

我要传递给程序的输入将采用以下格式:

3,5;6,7;8,9;11,4;; 

我需要处理输入,以便我可以使用冒号之间的数字(即我希望能够使用 3 和 5、6 和 7 等)。 ;; 表示它是行尾。

我正在考虑使用缓冲 reader 读取整行然后使用 parseInt。

这是最有效的方法吗?

我对 C 有点生疏,但这对你有用吗?

char[1000] remainder;
int first, second;
fp = fopen("C:\file.txt", "r"); // Error check this, probably.
while (fgets(&remainder, 1000, fp) != null) { // Get a line.
    while (sscanf(remainder, "%d,%d;%s", first, second, remainder) != null) {
        // place first and second into a struct or something
    }
}

您可以使用 fgets or fgetc. You could also use getline()stdin 读取,因为您正在从 stdin 读取。

读入该行后,您可以使用 strtok() with the delimiter for ";" to split the string into pieces at the semicolons. You can loop through until strok() is null, or in this case, ';'. Also in C you should use atoi() 将字符串转换为整数。

例如:

int length = 256;
char* str = (char*)malloc(length);
int err = getline(&str, &length, stdin);

我会读入命令参数,然后使用 strtok() 库方法

进行解析

http://man7.org/linux/man-pages/man3/strtok.3.html

(上面URL引用的网页还有使用方法的代码示例。)

这是一个可行的解决方案
一种方法是使用 strtok() 并将值存储在数组中。理想情况下,动态分配。

 int main(int argc, char *argv[])
{
    int lst_size=100;
    int line_size=255;

    int lst[lst_size];
    int count=0;

    char buff[line_size];
    char * token=NULL;
    fgets (buff, line_size, stdin); //Get input

通过传递“,”和“;”来使用 strtok作为分隔符。

    token=strtok(buff, ";,");
    lst[count++]=atoi(token); 
    while(token=strtok(NULL, ";,")){
          lst[count++]=atoi(token);
    }

最后你必须考虑双“;;”通过将计数减 1,因为对于这种情况 atoi(token) 将 return 0 并将其存储在第 n 个索引中。你不想要的。

  count--;

}

getchar_unlocked() 就是您要找的。

代码如下:

#include <stdio.h>

inline int fastRead_int(int * x)
{
  register int c = getchar_unlocked();
  *x = 0;

  // clean stuff in front of + look for EOF
  for(; ((c<48 || c>57) && c != EOF); c = getchar_unlocked());
  if(c == EOF)
    return 0;

  // build int
  for(; c>47 && c<58 ; c = getchar_unlocked()) {
    *x = (*x<<1) + (*x<<3) + c - 48;
  }
  return 1;
}

int main()
{
  int x;
  while(fastRead_int(&x))
    printf("%d ",x);
  return 0;
}

对于输入 1;2;2;;3;;4;;;;;54;;;; 上面的代码生成 1 2 2 3 4 54.

我保证,这个解决方案比本主题中介绍的其他解决方案快得多。它不仅使用getchar_unlocked(),还使用registerinline以及乘以10的技巧:(*x<<1) + (*x<<3).

祝你好运,找到更好的解决方案。

另一种相当优雅的处理方法是允许 strtol 通过将要读取的字符串推进到 strtol 返回的 endptr 来解析输入。根据需要结合数组 allocated/reallocated ,您应该能够处理任何长度的行(直到内存耗尽)。下面的示例使用单个数组作为数据。如果你想存储多行,每行作为一个单独的数组,你可以使用相同的方法,但从指向 int 指针数组的指针开始。 (即 int **numbers 并分配指针,然后分配每个数组)。如果您有任何问题,请告诉我:

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

#define NMAX 256

int main () {

    char *ln = NULL;                /* NULL forces getline to allocate  */
    size_t n = 0;                   /* max chars to read (0 - no limit) */
    ssize_t nchr = 0;               /* number of chars actually read    */
    int *numbers = NULL;            /* array to hold numbers            */
    size_t nmax = NMAX;             /* check for reallocation           */
    size_t idx = 0;                 /* numbers array index              */

    if (!(numbers = calloc (NMAX, sizeof *numbers))) {
        fprintf (stderr, "error: memory allocation failed.");
        return 1;
    }

    /* read each line from stdin - dynamicallly allocated   */
    while ((nchr = getline (&ln, &n, stdin)) != -1)
    {
        char *p = ln;       /* pointer for use with strtol  */
        char *ep = NULL;

        errno = 0;
        while (errno == 0)
        {
            /* parse/convert each number on stdin   */ 
            numbers[idx] = strtol (p, &ep, 10);
            /* note: overflow/underflow checks omitted */
            /* if valid conversion to number */
            if (errno == 0 && p != ep)
            {
                idx++;              /* increment index      */
                if (!ep) break;     /* check for end of str */
            }

            /* skip delimiters/move pointer to next digit   */
            while (*ep && (*ep <= '0' || *ep >= '9')) ep++;
            if (*ep) 
                p = ep;
            else 
                break;

            /* reallocate numbers if idx = nmax */
            if (idx == nmax)
            {
                int *tmp = realloc (numbers, 2 * nmax * sizeof *numbers);
                if (!tmp) {
                    fprintf (stderr, "Error: struct reallocation failure.\n");
                    exit (EXIT_FAILURE);
                }
                numbers = tmp;
                memset (numbers + nmax, 0, nmax * sizeof *numbers);
                nmax *= 2;
            }
        }
    }

    /* free mem allocated by getline */
    if (ln) free (ln);

    /* show values stored in array   */
    size_t i = 0;
    for (i = 0; i < idx; i++)
        printf (" numbers[%2zu]  %d\n", i, numbers[i]);

    /* free mem allocate to numbers  */
    if (numbers) free (numbers);

    return 0;
}

输出

$ echo "3,5;6,7;8,9;11,4;;" | ./bin/prsistdin
 numbers[ 0]  3
 numbers[ 1]  5
 numbers[ 2]  6
 numbers[ 3]  7
 numbers[ 4]  8
 numbers[ 5]  11
 numbers[ 6]  4

也适用于字符串存储在文件中的情况:

$ cat dat/numsemic.csv | ./bin/prsistdin
or
$ ./bin/prsistdin < dat/numsemic.csv

使用 fgets 且不使用 size_t

进行了一些修改,我很高兴删除了 getline 并替换了 fgetsgetline 更灵活,为您处理 space 的分配,而 fgets 则由您决定。 (更不用说 getline 无需调用 strlen 即可返回实际读取的字符数)。

我的目标是保留读取任意长度行的能力以满足您的要求。这要么意味着最初分配一些巨大的行缓冲区(浪费),要么想出一个方案,在输入行缓冲区比 space 最初分配给 ln 长的情况下根据需要重新分配输入行缓冲区。 (这就是 getline 做得很好的地方)。我对结果相当满意。 注意: 我将重新分配代码放在函数中以保持 main 相当干净。 脚注 2

看看下面的代码。请注意,我在代码中保留了 DEBUG 预处理器指令,如果您想在每次分配时都将其吐出,则允许您使用 -DDEBUG 标志进行编译。 [脚注 1] 你可以编译代码:

gcc -Wall -Wextra -o yourexename yourfilename.c

或者如果您想要调试输出(例如将 LMAX 设置为 2 或小于行长度的值),请使用以下内容:

gcc -Wall -Wextra -o yourexename yourfilename.c -DDEBUG

如果您有任何问题,请告诉我:

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

#define NMAX 256
#define LMAX 1024

char *realloc_char (char *sp, unsigned int *n); /* reallocate char array    */
int *realloc_int (int *sp, unsigned int *n);    /* reallocate int array     */
char *fixshortread (FILE *fp, char **s, unsigned int *n); /* read all stdin */

int main () {

    char *ln = NULL;                    /* dynamically allocated for fgets  */
    int *numbers = NULL;                /* array to hold numbers            */
    unsigned int nmax = NMAX;           /* numbers check for reallocation   */
    unsigned int lmax = LMAX;           /* ln check for reallocation        */
    unsigned int idx = 0;               /* numbers array index              */
    unsigned int i = 0;                 /* simple counter variable          */
    char *nl = NULL;

    /* initial allocation for numbers */
    if (!(numbers = calloc (NMAX, sizeof *numbers))) {
        fprintf (stderr, "error: memory allocation failed (numbers).");
        return 1;
    }

    /* initial allocation for ln */
    if (!(ln = calloc (LMAX, sizeof *ln))) {
        fprintf (stderr, "error: memory allocation failed (ln).");
        return 1;
    }

    /* read each line from stdin - dynamicallly allocated   */
    while (fgets (ln, lmax, stdin) != NULL)
    {
        /* provide a fallback to read remainder of line
        if the line length exceeds lmax */
        if (!(nl = strchr (ln, '\n')))
            fixshortread (stdin, &ln, &lmax); 
        else
            *nl = 0;

        char *p = ln;       /* pointer for use with strtol  */
        char *ep = NULL;

        errno = 0;
        while (errno == 0)
        {
            /* parse/convert each number on stdin   */
            numbers[idx] = strtol (p, &ep, 10);
            /* note: overflow/underflow checks omitted */
            /* if valid conversion to number */
            if (errno == 0 && p != ep)
            {
                idx++;              /* increment index      */
                if (!ep) break;     /* check for end of str */
            }

            /* skip delimiters/move pointer to next digit   */
            while (*ep && (*ep <= '0' || *ep >= '9')) ep++;
            if (*ep)
                p = ep;
            else
                break;

            /* reallocate numbers if idx = nmax */
            if (idx == nmax)
                realloc_int (numbers, &nmax);
        }
    }

    /* free mem allocated by getline */
    if (ln) free (ln);

    /* show values stored in array   */
    for (i = 0; i < idx; i++)
        printf (" numbers[%2u]  %d\n", (unsigned int)i, numbers[i]);

    /* free mem allocate to numbers  */
    if (numbers) free (numbers);

    return 0;
}

/* reallocate character pointer memory */
char *realloc_char (char *sp, unsigned int *n)
{
    char *tmp = realloc (sp, 2 * *n * sizeof *sp);
#ifdef DEBUG
    printf ("\n  reallocating %u to %u\n", *n, *n * 2);
#endif
    if (!tmp) {
        fprintf (stderr, "Error: char pointer reallocation failure.\n");
        exit (EXIT_FAILURE);
    }
    sp = tmp;
    memset (sp + *n, 0, *n * sizeof *sp); /* memset new ptrs 0 */
    *n *= 2;

    return sp;
}

/* reallocate integer pointer memory */
int *realloc_int (int *sp, unsigned int *n)
{
    int *tmp = realloc (sp, 2 * *n * sizeof *sp);
#ifdef DEBUG
    printf ("\n  reallocating %u to %u\n", *n, *n * 2);
#endif
    if (!tmp) {
        fprintf (stderr, "Error: int pointer reallocation failure.\n");
        exit (EXIT_FAILURE);
    }
    sp = tmp;
    memset (sp + *n, 0, *n * sizeof *sp); /* memset new ptrs 0 */
    *n *= 2;

    return sp;
}

/* if fgets fails to read entire line, fix short read */
char *fixshortread (FILE *fp, char **s, unsigned int *n)
{
    unsigned int i = 0;
    int c = 0;

    i = *n - 1;
    realloc_char (*s, n);
    do
    {
        c = fgetc (fp);
        (*s)[i] = c;
        i++;
        if (i == *n)
            realloc_char (*s, n);
    } while (c != '\n' && c != EOF);
    (*s)[i-1] = 0;

    return *s;
}

脚注 1

单词 DEBUG 的选择没什么特别的(它可能是 DOG,等等),要点是如果你想有条件地 include/exclude代码,您可以简单地使用预处理器标志来执行此操作。您只需添加 -Dflagname 即可将 flagname 传递给编译器。

脚注 2

您可以将重新分配函数组合成一个单独的 void* 函数,该函数接受 void 指针作为其参数以及要重新分配的类型的 size 和 returns void指向重新分配的 space 的指针——但我们将把它留到以后再说。