2

The input file will have the name of a lake on one line, then a comma, then the volume of that lake in units of hundreds of cubic miles. The next line will contain the name of another lake, a comma, then it's volume, etc, etc. Each line of the file contains exactly the name of a lake, a comma, and a float value for volume.The name of a lake may contain more than one word; for example "dead horse lake", but it will be on one formatted line. The volume may have decimals in it, as 16.9. Your program will use a subroutine that takes as arguments the name of a lake and its volume. This subroutine will then print out the name of the lake on one line to the screen, followed by a set of consecutive asterisks denoting the volume in units of hundreds of cubic miles on the next line rounded to the nearest hundred cubic miles. For example, if the lake is 15.6 cubic miles in volume, the subroutine will print out 16 asterisks on the line following the name of the lake.

Right now my program only reads the first line and displays the name and asterisk, but any other information in my lakes.txt file is not read and the program terminates. Could someone please tell me what I am doing wrong? The original project was one without the comma, but the prof. decided to add a comma in there. I just changed the %19 to %20 and added a comma in the brackets. I do not know what difference it made, but it worked. I would like to understand why.

I'm new to SO. Sorry if my text is a little off. I'm a new programmer and I would really love to understand and become a good programmer. The more elaborate and in depth your answers are, the more helpful they are to me. Thank you for your help!

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

void asterisks_code( char lake[257], float vol );

int main()
{
    char lake[257], tempt[100];
    float vol;
    FILE *fp;
    fp = fopen( "lakes.txt", "r" );
    if( fp == NULL) {//starts 1st if statement
        printf( "File does not exist.");
        return 0;
    }//closes 1st if statement
    while( ( fgets( tempt, sizeof (tempt), fp ) ) != NULL ) {
        if (sscanf(tempt, " %19[A-Za-z, ]%f", lake, &vol) != 2) {//starts 2nd if statement
            fprintf(stderr, "Unexpected data\n");
            break;
        }//closes 2nd if statement
        asterisks_code( lake, vol );
    }//closes while loop
    fclose( fp );
    return 0;
}//closes main function

void asterisks_code( char lake[257], float vol )
{//start of asterisks_code function
    int counter;
    printf( "%s\n", lake );
    for( counter = 0; counter < roundf(vol); counter++ ) {//start of for loop
        printf( "*" );
    }//closes for loop
    printf( "\n" );
}//closes asterisk_code function
Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
Werner
  • 21
  • 3
  • 4
    Would you like some `strtok()`? – EOF Jun 18 '15 at 20:49
  • 4
    Consider `if (sscanf(tempt, " %[^,],%f", lake, &vol) == 2) { ... OK ... } else { ...bad data ... }` inside your `fgets()` loop. Nominally, add a suitable length to the scan set (e.g. `%99[^,]`) to prevent overflows, but your line seems to be limited to 100 chars and the lake name to 256 chars, so you actually can't have an overflow here. – Jonathan Leffler Jun 18 '15 at 20:57
  • 1
    Can you show the first three lines of data in the file? It shouldn't be hard. Have you printed each line as it is read (at the top of the loop body)? When you say "it doesn't read any more", do you mean the `fgets()` returns NULL, or something else happens? You might decide to only call `roundf(vol)` once before the loop starts, rather than on each iteration, but that's a refinement, not a cause of the trouble. There isn't an obvious cause of trouble in the code; are you sure the data file didn't get truncated somehow? – Jonathan Leffler Jun 18 '15 at 21:26
  • You can use the same technique as [this](http://stackoverflow.com/a/30902712/971127). – BLUEPIXY Jun 18 '15 at 21:58
  • Cannot reproduce : you code is not very good because you keep the comma and eventual spaces in the name, but it correctly reads as many lines as are given in file. You are lacking some include (`stdio.h`) but that should not cause the described problem – Serge Ballesta Jun 18 '15 at 22:10

1 Answers1

1

I feel for you. Your professor has misguided you from the very start.

First of all, fgets( tempt, sizeof (tempt), fp ) doesn't guarantee that a full line will be read from fp. If sizeof tempt bytes are read and there are more left in the line, the leftover lines will be left on the stream for the next call to fgets. I've written solutions to that problem in another answer, but in all honesty you don't need fgets. You could use it if you wanted to, but all you need to solve this exercise is fscanf and fgetc.

I thoroughly advise reading only one element at a time using fscanf... at least until you've read and fully understood the fscanf manual (it's very lengthy, suggesting that it's quite complex and easy to get wrong... hmmmm!). Even so, you should still at least read it before you try to use it.

Start by writing a function to read your 19-byte wide field...

int read_lake_name(FILE *fp, char *lake) {
    int n = fscanf(fp, "%19[^,]", lake);
    int left_over = 0;
    fscanf(fp, "%*[^,]%n", &left_over);
    if (left_over > 0) {
        puts("WARNING: Lake name was longer than 19 bytes and has been discarded...");
    }
    fgetc(fp);
    return n;
}

If more than 19 bytes are provided, there will be some left-over on the stream. Those bytes need to be discarded. Notice the * in the second fscanf, and that there's no corresponding array argument? That's an example of assignment suppression using fscanf; the left-over bytes will be read and discarded, rather than assigned into an array. The %n directive instructs fscanf to assign the number of bytes fscanf has read into left_over, which tells us whether or not the lake's name was truncated. Following that, fgetc is called to discard the remaining comma.

This code is great at reading (and truncating) 19 bytes of user input, up to the next comma, but it won't read the volume. Once you've read those up-to-19-bytes and discarded the comma (and any trailing data prior to the comma), you'll need to read a floating point value, then discard anything prior to the newline, and discard the newline (of course)...

int read_lake_volume(FILE *fp, float *v) {
    int n = fscanf(fp, "%f", v);
    int left_over = 0;
    fscanf(fp, "%*[^\n]%n", &left_over);
    if (left_over > 0) {
        puts("WARNING: Lake volume contained trailing invalid characters which have been discarded...");
    }
    fgetc(fp);
    return n;
}

Do you notice any similarities here? With these two virtually identical functions it should be trivial to construct a working loop, but here goes:

while (read_lake_name(fp, name) == 1 && read_lake_volume(fp, &vol) == 1) {
    write_asterisks(name, vol);
}
Community
  • 1
  • 1
autistic
  • 1
  • 3
  • 35
  • 80