Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I add together the numbers in a file?

I have a large text file. In this file there are some numbers I want to add together.

What I've tried:

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);

But that isdigit(i) is just a counter of how many digits this file contains, not what the sum of the numbers is.

The input is: "This text 15 is very 19 nice."
The result should be: Sum of numbers is: 34

like image 246
dunno Avatar asked Jan 22 '20 20:01

dunno


1 Answers

Consider the decimal placement

The missing part in the question's code is accumulating the digits (as opposed to counting the digits with sum++;) AND multiplying by ten the previous accumulated number before adding the next digit.

The answer is in: number = number * 10 + i - '0';

The - '0' part is converting ASCII digit to a number.

Everything else in the below code is checks to make sure there are no obvious overflows and correctly supporting minus sign also adjacent to the numbers, as well as ignoring digits after the decimal point(s). I'm sure it is not perfect, but the idea here is to provide a working example of how it could be done, rather than a well tested code and using a library call to do it for you.

By popular demand (comments were deleted now) I've added a simple-but-working overflow check:

#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;
}

Test run:

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

And just in case you are on 64bit linux system, and need much higher performance (you mentioned large file) the below code will map the entire file (even file larger than memory, the kernel will handle it nicely) and will not make a library call on every char. In my tests, isdigit() and strtol() slows it down significantly.

#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;
}
like image 52
niry Avatar answered Sep 23 '22 16:09

niry