JQ143
JQ143

Reputation: 31

How to use getc() instead of fgets() in my program

So I am writing which is supposed to do this for example

              This info is supposed to
        be      flipped   back
        for this code. 

        empty line  was above this one.   

would become

to supposed is info This
back flipped be
code. this for
one. this above was line empty   

Now I have a function that does this perfectly but I was told by my professor to read character by character and then process a line once you hit new line. Then continue onto the next line after than until EOF.

I on the other hand have an fgets and I load the whole input/file. Then I deal with that inside my function. On the other hand if a line is longer than 80 chars then I shouldn't process it.

Here is my working code but how can I convert it so it uses getc() instead of fgets? Thanks

void reverser(FILE *input){
    char c[82]; //Character size limited per line
    if(outputFlag)
        fp = freopen(fileName, "a+", stdout);
    while (fgets(c, sizeof c, input)!=NULL){
        int counter =0;
        int x = 0;
        while(*(c+x)=='\t' || *(c+x)==SPACE)
            x++;
        if(*(c+x) == '\n')
            continue;
        char *pCar[80];
        int i, n = 0;
        char *tok = strtok(c, " \t\n");
        while (tok != NULL && n < 80){
            *(pCar+n++) = tok;
            tok = strtok(NULL, " \t\n");
        }
        for(i = n-1; i>=0; --i){                    //Counting the char length of the lines
            counter+=(int)strlen(*(pCar+i));
            if(i)
                counter++;
        }
        if(counter>80){                             //Throw error if character length is > 80
            fprintf(stderr, "LINE TOO LONG\n");
            returnVal = 1;
            continue;
        }
        for(i = n-1; i>=0; --i){            
            printf("%s", *(pCar+i));
            if(i)
                putchar(' ');
        }
        printf("\n");
    }
    fclose(input);
}

I would love to get some hints.

Upvotes: 2

Views: 1290

Answers (2)

Jonathan Leffler
Jonathan Leffler

Reputation: 753695

This code seems to work for me, more or less.

Source file z.c

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

char *read_line(char *buffer, size_t buflen, FILE *fp);
void reverser(FILE *input);

char *read_line(char *buffer, size_t buflen, FILE *fp)
{
    int c;
    char *data = buffer;
    char *end = buffer + buflen - 1;
    while (data < end && (c = getc(fp)) != '\n')
    {
        if (c == EOF)
        {
            *data = '\0';
            return (data == buffer) ? NULL : buffer;
        }
        *data++ = c;
    }
    if (data < end)
    {
        *data++ = c;
        *data = '\0';
    }
    else
    {
        // Overlong line - report error, read and discard to EOL
        // or EOF, and return empty string (not even newline).
        fprintf(stderr, "Line too long: discarded!\n");
        while ((c = getc(fp)) != EOF && c != '\n')
            ;
        *buffer = '\0';
    }
    return buffer;
}

void reverser(FILE *input)
{
    char c[82];
    while (read_line(c, sizeof c, input) != NULL)
    {
        int x = 0;
        while (c[x] == '\t' || c[x] == ' ')
            x++;
        if (c[x] == '\n' || c[x] == '\0')
            continue;
        char *pCar[80];
        int i, n = 0;
        char *tok = strtok(c, " \t\n");
        while (tok != NULL && n < 80)
        {
            *(pCar + n++) = tok;
            tok = strtok(NULL, " \t\n");
        }
        for (i = n - 1; i >= 0; --i)
        {
            printf("%s", *(pCar + i));
            if (i)
                putchar(' ');
        }
        printf("\n");
    }
    // fclose(input);  // Don't close what you didn't open!
}

int main(void)
{
    reverser(stdin);
    return 0;
}

I made minimal changes in reverser(), removing no longer relevant code, using c[x] in place of *(c + x) because it is easier to type and read, replacing SPACE with ' ', removing the code to reopen standard output, etc.

The program compiles cleanly using:

 gcc -Wall -Wextra -Werror z.c -o z

(Tested on an Ubuntu 14.04 derivative with GCC 5.1.0.)

Sample output:
$ ./z
pedagogical anti-orthodoxy grammatically correct infelicitous nonsense munged with catatonic isotopes of prejudice and grunge lead to positive recognition of abusive memes in the genetic diversity of alphabets.
Line too long: discarded!
pedagogical anti-orthodoxy grammatically correct infelicitous nonsense
nonsense infelicitous correct grammatically anti-orthodoxy pedagogical
 munged with catatonic isotopes of prejudice and grunge lead
lead grunge and prejudice of isotopes catatonic with munged
to positive recognition of abusive memes in the genetic diversity of alphabets.
alphabets. of diversity genetic the in memes abusive of recognition positive to
$

Context for the answer

Originally, there were a lot of comments associated with the question, seeking and obtaining elucidation of the problem and outlining solutions. Those comments were all removed — see this MSO question. Fortunately, the browser on which I developed the answer retained the comments to the question overnight. I make no claim that the comments were great; I make no claim that the question is/was great; I make no claim that my answer is great. I agree that many of the comments by JQ143 should have been made as edits to the question. I do assert that the comments provided necessary context for my answer and should not have been deleted wholesale.

The comments below are not verbatim copies of what was there; they have been curated. A couple of comments by wildplasser have been omitted.

Jonathan Leffler commented:

There are similar questions on SO already. Why would you want to use getc() instead of fgets()? You need to process whole lines, so reading whole lines makes a lot of sense. One easy way around it is to write your own function, maybe char *read_line(char *buffer, size_t buflen, FILE *fp) that uses getc() but simulates fgets(). More likely, he has in mind that you'll use getc() to read characters into the first word, then the second, then ..., and when you hit EOL (newline), you print out the saved words in reverse order, zero things, and repeat.

I note that BLUEPIXY's answer substantially implements the "use getc() to read a word at a time" version of the code.

ChuckCottrill noted:

The instructor wants you do demonstrate that you know how to build your own fgets() function using the getc() function as a base, or perhaps gather one word at a time, pushing those words onto a stack, and then pop them from the stack and emit them.

JQ143 commented (but should ideally have modified the question to state):

So what he basically wants is to keep adding characters to an array until you hit newline. Once you hit newline you can process that array and print them backwards. Then repeat this with character starting after that old newline... Any idea how I can just use getc()? He said it should be easy to fix.

@ChuckCottrill my professor is saying the fgets won't work with this program. He said it'll break, which I don't see how it could.

I noted:

That's what my proposed read_line() does and the existing fgets() does. Ignoring buffer overflow checking, that's trivial:

char *read_line(char *buffer, size_t buflen, FILE *fp)
{
    int c;
    char *data = buffer;
    while ((c = getc(fp)) != '\n')
    {
        if (c == EOF)
        {
            *data = '\0';
            return (data == buffer) ? 0 : buffer;
        }
        *data++ = c;
    }
    *data++ = c;
    *data = '\0';
    return buffer;
}

Adding buffer overflow checking is not difficult, but is left as an exercise for you. If you need to ignore lines that are too long, or read and discard the excess material if the line is too long, that is also easily done.

(That code doesn't compile under my normal compiler options because buflen is not used.)

JQ143 responded:

I am sorry but I am having a hard time figuring out how to incorporate that into my program.

To which my reply was:

Change: while (fgets(c, sizeof c, input)!=NULL){ to while (read_line(c, sizeof c, input)!=NULL){

Note that using the name c for an array of char is not conventional; c normally denotes a single char variable: char c;.

Also note that the code if(outputFlag) fp = freopen(fileName, "a+", stdout); makes this function logically incohesive. The calling code should handle the redirection. It can either pass the output file stream to this function (so void reverser(FILE *input, FILE *output)) or arrange it so that stdout is modified with freopen(). But this function should not be accessing the global variables fileName and outputFlag.


jxh noted:

See: fgets implementation (K&R); it was the top link of my Google search.

As to how the program might fail, if a line length N is longer than 80 characters, it looks like you will reverse the last (N % 81) bytes of the line (81 since sizeof(c) is 82).

JQ143 riposted:

@jxh so if a line length is greater than 80. It will just throw an stderr saying line too long. Which is actually part of the program.

And jxh responded:

You print an error, but you continue the loop.

JQ143:

@jxh Oh..according to my professor, I have to process lines that are less than 80 but throw an error for those that are longer.

jxh:

You process the remaining lines, yes. But, you also process the rest of the currently too long line. If the line was 100 bytes long, you read in 81 bytes of it. When you determine the line is too long, you print your error, and continue. When you continue, you end up reading in 19 more bytes of the too long line. 100 % 81 is 19.

JQ143:

@jxh you are actually right. I'll try to fix that.


JQ143:

Thank you!, but upon doing so [changing fgets() to read_line()] my program went into a crazy infinite loop.

I countered with:

I just compiled the code extracted from my comment (the read_line() function), and it seems to work OK as long as you don't have overlong lines. A revised version of the code is:

char *read_line(char *buffer, size_t buflen, FILE *fp)
 {
     int c;
     char *data = buffer;
     char *end = buffer + buflen - 1;
     while (data < end && (c = getc(fp)) != '\n')
     {
         if (c == EOF)
         {
             *data = '\0';
             return (data == buffer) ? 0 : buffer;
         }
         *data++ = c;
     }
     if (data < end)
        *data++ = c;
     else
         ungetc(c, fp);
     *data = '\0';
     return buffer;
 }

This variant of the function leaves extra data on a line to be read by the next call to the function, like fgets() does.

JQ143:

Any hints on how to deal with lines longer than 80 chars?

Using your variable name, I'd use

char c[4096];
while (read_line(c, sizeof(c), input) != 0)
{
    if (strlen(c) > 80)
    {
        fprintf(stderr, "Too long at %zu (line <<%s>>)\n", strlen(c), c);
        continue; /* or break; */
    }
    ...

reading lines up to 4 KiB and only processing ones that are short enough. That runs the (rather small) risk that you'll get a line of 4150 bytes, and reject the first 4095 bytes, but process the remainder as a 55 byte line. If that's not acceptable, modify the code in read_line() to detect overlong input (passing an 82 byte buffer), and have it gobble to EOL or EOF when necessary.

This is basically affirming what @jxh said about the behaviour of the code when the line is too long, and outlining ways of working around it.

JQ143:

@JonathanLeffler So I pretty much want to throw an stderr (just once) if I detect a sentence is longer than 80 chars. I think reading it up to 80 and not reading the rest is a good way. But how can I do that in read_line() instead of doing that weird counter stuff in my other method? I also wanna thank you for helping me. I appreciate it so much.

At this point, it became possible to produce an answer; the specification was sufficiently clear — albeit that the information should be in the question rather than the comments.

ChuckCottrill noted:

An interesting variant solution would be to build a readline function that would increase the allocated space (see realloc) as the buffer size was exhausted...

That's known as POSIX getline().

As chux noted, my code does not handle buflen of zero. In my defence, sizeof(object) never produces 0 in C, so you have to be trying to run into that problem. One crude modification to the function would be:

assert(*buffer != 0 && buflen != 0 && fp != 0);
if (buffer == 0 || buflen == 0 || fp == 0)
    return 0;

The assert enforces correct use in a development environment; the test simulates EOF in a production environment and avoids catastrophic misbehaviour at runtime.

Upvotes: 4

BLUEPIXY
BLUEPIXY

Reputation: 40145

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

#define MAXCHARS 80

void reverser(FILE *input){
    char c[MAXCHARS+1+1];
    char *pCar[MAXCHARS/2];
    int ch, i;
    int n_ch = 0, n_word = 0;
    char prev = ' ';

    for(;;){
        ch = fgetc(input);
        if(isspace(ch) || ch == EOF){
            if(prev != ' '){
                c[n_ch++] = '\0';
            }
            if(ch == '\n' || ch == EOF){//print words
                for(i=n_word - 1; i >= 0; --i){
                    printf("%s", pCar[i]);
                    if(i)
                        putchar(' ');
                }
                if(n_word)
                    putchar('\n');
                n_ch = 0;
                n_word = 0;
            }
            if(ch == EOF)
                break;
            prev = ' ';
        } else {
            if(n_ch >= MAXCHARS){
                fprintf(stderr, "LINE TOO LONG\n");
                while((ch = fgetc(input)) != '\n' && ch != EOF)
                    ;//drop this line
                //stop process for this line
                n_ch = 0;
                n_word = 0;
            } else {
                if(prev == ' '){
                    pCar[n_word++] = &c[n_ch];
                }
                prev = c[n_ch++] = ch;
            }
        }
    }
}

int main (void){
    reverser(stdin);

    return 0;
}

Upvotes: 1

Related Questions