【问题标题】:How can I add together the numbers in a file?如何将文件中的数字相加?
【发布时间】:2020-01-22 20:26:37
【问题描述】:

我有一个大文本文件。在这个文件中有一些我想加在一起的数字。

我尝试过的:

int sum = 0, i = 0;
file = fopen(filename, "r");
while ((i = fgetc(file)) != EOF) {
    if (isdigit(i)) {
        sum++;
    }
}
printf("Sum of numbers is: %i", sum);
fclose(file);

但是 isdigit(i) 只是这个文件包含多少位的计数器,而不是数字的总和。

输入是:"This text 15 is very 19 nice."
结果应该是:Sum of numbers is: 34

【问题讨论】:

  • 你能计算出一个数字字符的吗?您将如何获取这些值的序列并计算它们一起代表的数字?你如何判断这样的序列何时开始和停止?
  • sum += i - '0'; 但那不是15 + 19,应该是1 + 5 + 1 + 9
  • 这看起来更像是一个关于解析而不是关于编码的问题。这使它变得困难。
  • @dunno:如果它对您有帮助,您可以通过单击其分数下方的灰色复选标记来接受其中一个答案。

标签: c while-loop sum numbers fopen


【解决方案1】:

考虑小数位

问题代码中缺少的部分是累加数字(而不是用sum++; 计算数字)并在添加下一个数字之前将前一个累加数字乘以十。

答案在: number = number * 10 + i - '0';

- '0' 部分正在将 ASCII 数字转换为数字。

以下代码中的所有其他内容都经过检查,以确保没有明显的溢出,并且正确支持与数字相邻的减号,以及忽略后面的数字小数点。我确信它并不完美,但这里的想法是提供一个工作示例来说明如何完成它,而不是提供经过良好测试的代码并使用库调用来为您完成。

应大众需求(cmets 现已删除),我添加了一个简单但有效的溢出检查:

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <error.h>
#include <limits.h>

int main(int argc, char* argv[]) {
  int sum = 0, state = 0, i = 0, dir = 1;
  unsigned int number = 0, check;
  if (argc < 2) {
    fprintf(stderr, "Missing filename\n");
    return EXIT_FAILURE;
  }
  char* filename = argv[1];
  FILE* file = fopen(filename, "r");
  if (!file) {
    perror(filename);
    return EXIT_FAILURE;
  }
  while (i != EOF) {
    i = fgetc(file);
    if (isdigit(i)) {
      if (dir) {
        state = 1;
        check = number;
        number = number * 10 + i - '0';
        if (check > number || number > INT_MAX) {
          fprintf(stderr, "Single number overflow error\n");
          fclose(file);
          return EXIT_FAILURE;
        }
      }
    } else {
      if (state && dir) {
        check = number;
        if (dir < 0 && sum < 0)
          check -= sum;
        else if (dir > 0 && sum > 0)
          check += sum;
        if (check > INT_MAX) {
          fprintf(stderr, "Sum overflow error\n");
          fclose(file);
          return EXIT_FAILURE;
        }
        sum += number * dir;
        number = 0;
      }
      state = 0;
      dir = i == '-' ? -1 : i == '.' ? 0 : 1;
    }
  }
  printf("Sum of numbers is: %i\n", sum);
  fclose(file);
  return EXIT_SUCCESS;
}

试运行:

$ cat opString.txt 
This text 15 is very 19 nice.
$ ./test2 opString.txt 
Sum of numbers is: 34
$ 

如果您使用的是 64 位 linux 系统,并且需要更高的性能(您提到了大文件),下面的代码将映射整个文件(即使文件大于内存,内核也会很好地处理它)并且不会对每个字符进行库调用。在我的测试中,isdigit()strtol() 显着减慢了速度。

#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <limits.h>
#include <sys/mman.h>

int addToSum(unsigned int* number, int* sum, int dir, FILE* file) {
  unsigned int check;
  check = *number;
  if (dir < 0 && *sum < 0)
    check -= *sum;
  else if (dir > 0 && *sum > 0)
    check += *sum;
  if (check > INT_MAX) {
    fprintf(stderr, "Sum overflow error\n");
    fclose(file);
    exit(EXIT_FAILURE);
  }
  *sum += *number * dir;
  *number = 0;
}

int main(int argc, char* argv[]) {
  int sum = 0, state = 0, i = 0, dir = 1;
  unsigned int number = 0, check;
  if (argc < 2) {
    fprintf(stderr, "Missing filename\n");
    return EXIT_FAILURE;
  }
  char* filename = argv[1];
  FILE* file = fopen(filename, "r");
  if (!file) {
    perror(filename);
    return EXIT_FAILURE;
  }
  if (fseek(file, 0L, SEEK_END) < 0) {
    perror("fseek failed");
    fclose(file);
    return EXIT_FAILURE;
  }

  long fsize = ftell(file);
  char* fmap = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fileno(file), 0);
  if (fmap == MAP_FAILED) {
    perror("map failed");
    fclose(file);
    return EXIT_FAILURE;
  }

  long pos = 0;
  while (pos < fsize) {
    i = fmap[pos++];
    if (i >= '0' && i <= '9') {
      if (dir) {
        state = 1;
        check = number;
        number = number * 10 + i - '0';
        if (check > number || number > INT_MAX) {
          fprintf(stderr, "Single number overflow error\n");
          fclose(file);
          return EXIT_FAILURE;
        }
      }
    } else {
      if (state && dir) addToSum(&number, &sum, dir, file);
      state = 0;
      dir = i == '-' ? -1 : i == '.' ? 0 : 1;
    }
  }
  if (state && dir) addToSum(&number, &sum, dir, file);
  printf("Sum of numbers is: %i\n", sum);
  fclose(file);
  return EXIT_SUCCESS;
}

【讨论】:

    【解决方案2】:

    输入文件名:file.txt

    This text 15 is very 19 nice.
    

    输出:

    Sum of numbers is: 34
    

    代码:

    #include <stdio.h>
    #include <ctype.h>
    
    int main()
    {
    int sum = 0, num, i = 0;
    int state = 0;
    FILE* f;
    
        if ((f = fopen("file.txt", "r")) == NULL)
            return -1;
    
        while ((i = fgetc(f)) != EOF) {
            switch(state) {
            case 0: // processing text
                if (isdigit(i)) {
                    num = i - '0';
                    state = 1;
                }
                break;
            case 1: // processing number
                if (isdigit(i)) {
                    num *= 10;
                    num += i - '0';
                }
                else {
                    sum += num;
                    num = 0;
                    state = 0;
                }
                break;
            }
        }
    
        if (state == 1) {
            sum += num;
        }
    
        printf("Sum of numbers is: %i\n", sum);
        fclose(f);
        return 0;
    }
    

    【讨论】:

    • 您介意解释一下这是做什么的吗?
    • 负一不是错误返回。你应该改用EXIT_FAILURE
    • 如果你愿意,你也可以if(!state) else
    • 谢谢。好建议。我会试试的。
    • 如果最后一个数字后没有尾随分隔符,代码将忽略最后一个数字。
    【解决方案3】:

    但是 isdigit(i) 只是一个计数器,这个文本内容有多少个数字,而不是数字的总和是多少。

    请记住,函数isdigit() 每次调用读取一个字符。因此,例如,如果它读取字符 9,则值 sum 应该增长 i - '0'(或 57 - 48,或 9)。如果序列中有两个字符,例如92,一次读取一个字符,则sum 的值同样会增加9+2 -&gt; 11,而不是92。鉴于这是你想要的,这里是如何做到的:

    您确定的数字值实际上是ASCII 值,因此在查看此表时,您可以看到所有数字的值都从“0”到“9”(或在ASCII 中为48 到57)。因此,在您的代码中,您可以简单地更改一行来总结值计数:

    int sum = 0;
        file = fopen(filename, "r");
    
        while ((i = fgetc(file)) != EOF) {
            if (isdigit(i))
                sum += (i - '0');//subtract 48 from every 'i' verified as digit
                                 //Sum will therefore add up values 
                                 //between (48-48) to (57-48)
                                 //(or between 0 to 9) 
            }
        printf(f,"Sum of numbers is: %i", sum);
    fclose(file);
    

    然而,如果你想对一个缓冲区中由一系列数字表示的数值求和,那么代码就不同了。在读取缓冲区内容时需要保留一个标志。
    在伪代码中

    char accumlator[10] = {0}; max possible sequential digits (change as needed)
    int found = 0;//flag
    int sum = 0;
    
     while ((i = fgetc(file)) != EOF) 
     {
        if (isdigit(i))
        {
            accumulator[found] = i;
            found++;
        }
        else
        {
            if(found != 0)
            {
                sum += atoi(accumulator);
                found = 0;
                accumulator[0] = 0;
                }
            }             
        }
    }
    

    【讨论】:

      【解决方案4】:

      函数fgetc 只读取一个作为潜在数字的字符。它不会一次读取由多个数字组成的整数。如果要读取整数,则必须更改程序的逻辑。您可以执行以下操作之一:

      1. 像现在一样使用 fgetc,并通过使用数字的ASCII Code 并使用算术将其转换为数字,使您的程序将各个数字组合成一个数字。单个 ASCII 字符数字的数值可以使用表达式ascii_code - '0' 获得。
      2. 改为使用fgets 一次读取整行,然后使用strtol 将找到的任何数字转换为数字。
      3. 使用fscanf()解析文件并一步提取数字(not recommended unless you know exactly what you are doing)。

      【讨论】:

        【解决方案5】:

        您的程序只计算文件中的位数。这是一个简单的解决方案,使用getc() 计算文件中所有整数的个数,无需状态机:

        #include <stdio.h>
        
        int count_numbers(const char *filename) {
            FILE *fp;
            unsigned long long total = 0, current = 0;
            int c;
        
            if ((fp = fopen(filename, "r")) == NULL) {
                printf("Cannot open input file %s\n", filename);
                return -1;
            }
        
            while ((c = getc(fp)) != EOF) {
                if (c >= '0' && c <= '9') {
                    current = current * 10 + (c - '0');
                } else {
                    total += current;
                    current = 0;
                }
            }
            total += current; /* add the last number if at the very end of the file */
            printf("Sum of numbers is: %llu\n", total);
            fclose(fp);
            return 0;
        }
        

        注意事项:

        • 此程序不处理负数,它忽略负号。
        • 它不处理浮点数:处理1.1 将产生2 的总和。
        • 很长的数字会产生模 ULLONG_MAX+1 的结果,但至少定义了行为。

        【讨论】:

          【解决方案6】:

          如果您想在其中添加所有数字字符串,那么您必须搜索数字,然后使用strtol 将它们从字符串转换为整数。这样做的方法如下:

          #include <ctype.h>
          #include <errno.h>
          #include <stdio.h>
          #include <stdlib.h>
          
          #define OVERFLOW_STR "Number cannot be represented in an int!\n"
          
          int main(int argc, char **argv)
          {
              FILE *file;
          
              char n[12];
              char *p = n;
              int i = 0, sum = 0, tmp, old_errno;
          
              if(argc != 2)
              {
                  fprintf(stderr, "Usage: %s file\n", argv[0]);
                  return EXIT_FAILURE;
              }
          
              file = fopen(argv[1], "r");
          
              if(!file)
              {
                  perror("Error opening file");
                  return EXIT_FAILURE;
              }
          
              while(i != EOF)
              {
                  while( (i = fgetc(file)) != EOF &&
                         i != '-' &&
                         i != '+' &&
                         !isdigit(i) )
                  {
                      /* empty loop */ ;
                  }
                  if(i != EOF)
                  {
                      do {
                          if( p == n + sizeof(n) - 1 ||
                              ((*n != '-' || *n != '+') &&
                              p == n + sizeof(n) - 2) )
                          {
                              fprintf(stderr, "Error: " OVERFLOW_STR);
                              return EXIT_FAILURE;
                          }
                          *p++ = i;
                      } while( (i = fgetc(file)) != EOF && isdigit(i) );
                  }
                  else
                      break;
          
                  *p = 0;
          
                  old_errno = errno;
                  errno = 0;
                  tmp = strtol(n, NULL, 10);
                  if( tmp == 0 && errno != 0 )
                  {
                      perror("Error converting string");
                      break;
                  }
                  errno = old_errno;
                  sum += tmp;
                  p = n;
              }
          
              printf("Sum of numbers is: %d\n", sum);
              fclose(file);
          
              return 0;
          }
          

          这主要是经过错误检查的。我可能遗漏了一些极端案例。

          输入(包含在名称作为命令行参数传递的文件中):

          这个文字15很好19。

          输出:

          数字总和是:34

          【讨论】:

          • 为什么投反对票?如果你有话要对我说,请说。我愿意交谈。
          • 我没有投反对票,但溢出测试不正确:if( tmp == 0 &amp;&amp; errno != 0) 将始终失败。 tmp 具有值 LONG_MAXLONG_MIN 以防溢出。您不需要 tp save errno,只需在调用strtol() 之前将其设置为0 并检查if (errno != 0) 以检测溢出。另请注意,合法号码的位数可以超过 11 位,例如 00000000000000000000000001
          • 您处理负数的尝试很有趣,但代码将无法为1-1 生成0
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-13
          • 2018-03-28
          • 1970-01-01
          • 2015-01-04
          相关资源
          最近更新 更多