0

I am working on a project for school and I have run into a little bit of trouble. The gist of the project is to write a program that reads in a file of text and formats that file so that it fits in a specific width.To format this file, the user specifies the input file, the length of an output line, and the justification for the output text. An example would be this:

$ ./format test.dat 15 right
The quick brown
 fox jumps over
   the lazy old
           dog.
$ ./format test.dat 15 left
The quick brown
fox jumps over
the lazy old
dog.
$ ./format test.dat 15 center
The quick brown
fox jumps over
 the lazy old
     dog.

Anyway, I am basically stuck on how to go about outputting the file based on this. Attached below is my code for reading in the file, and what little I have done on outputting the file. I am mainly looking for tips or suggestions on how to go about doing it. I know I need to use printf with a width and such, but I am confused on how to move to the next line.

char **inputFile(FILE *fp, int size) {
    int i = 0;
    char *token;
    char **str;
    str = malloc(sizeof(char *) * size);
    token = readToken(fp);
    while(!feof(fp)) {
        if(i+1 == size) {
            realloc(str, size * 2);
        }

        str[i] = token;
        token = readToken(fp);
        i++;
    }
    return str;
}

void toPrint(char **string, int width, int indent) {
    int i;
    int curLineLength;
    if(indent == 0) {
       for(i = 0; I < strlen(string); i++
            char *token = string[i];
            if(curLineLength + strlen(*string) > width) {
                if(curLineLength > 0) {
                    printf("\n");
                    curLineLength = 0;
                }
            }
            printf("%s ", token);
            curLineLength += strlen(*string);
    }
    /*
    if(indent == 1) {

    }
    if(indent == 2) {

    }
    */
}
  • 4
    Unrelated to the problem you're asking about, but you might want to read [Why is “while ( !feof (file) )” always wrong](http://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong). – Some programmer dude Oct 14 '15 at 17:36
  • I know, I've been told this a bunch already but this is how we are taught so I am going to use it this way for simplicity's sake. – Adam Alred Oct 14 '15 at 17:37
  • 1
    `realloc(str, size * 2);` --> `{ str = realloc(str, sizeof *str * size * 2); size *=2; }` (plus error handling, of course ...) – wildplasser Oct 14 '15 at 17:42
  • Can you explain why? – Adam Alred Oct 14 '15 at 17:49
  • Also: `if(i+1 == size) {` -->> `if(i == size) {` @AdamAlred: yes, just look at your initial malloc, which *does* scale by `sizeof (char*)` – wildplasser Oct 14 '15 at 17:50
  • @AdamAlred `realloc` frees `str` and returns a pointer to the newly allocated block of memory on success. If you are not assigning the return of `realloc`, you are just blowing away everything in `str`... You should really `realloc` with a temp variable (e.g. `char *tmp = realloc (str, sizeof *str * size * 2); if (tmp) str = tmp;`) This protects against losing all data in `str` in the event `realloc` fails (it returns `NULL`) – David C. Rankin Oct 14 '15 at 18:00
  • @AdamAlred, If your think about it, there is only 1 piece of information you need to worry about to write your output routine. That is the number of spaces to print before your line. This applies to center and right justified text. In the case of centered text, it is 1/2 what it would be for right justified. So you know the width it should fit in (say `w`), you know the length of each line from `strlen` (say `l`), then the number of spaces to print before the right justified text will be `w - l`, and in the case of centered `(w - l)/2`. Give it a go. – David C. Rankin Oct 14 '15 at 18:12
  • @DavidC.Rankin I just edited what I think might help fix it. I am able to print the first three words in the left justification, can you tell me if this is on the right track? – Adam Alred Oct 14 '15 at 18:16

1 Answers1

1

Following on from the comment, your task of justifying the lines is more a logic challenge for structuring your output function than it is a difficult one. You are getting close. There are a number of ways to do it, and you probably need to add error checking to make sure width isn't less than the longest line.

Here is a quick example you can draw from. Make sure you understand why each line was written the way it was, and pay close attention to the variable length array definition (you will need to compile as c99 - or if using gcc, rely on the gcc extension (default)). Let me know if you have questions:

/*  format 'n' lines of output of 't' justified as specified in 'just'
    (left 'l', right 'r' or centered 'c') within a width 'w'. 
    NOTE: 'w' must be larger than the longest line in 't'.
*/
void formatted (char **t, char just, size_t w, size_t n)
{
    if (!t || !*t) return;
    size_t i = 0;
    size_t lmax = 0;
    size_t len[n];

    /* calculate the length of each line, set lmax */
    for (i = 0; i < n; i++) {
        len[i] = strlen (t[i]);
        if (len[i] > lmax) lmax = len[i];
    }

    /* handle w < lmax reformat or error here */
    if (w < lmax) {
        fprintf (stderr, "%s() error: invalid width < lmax (%zu).\n",
                 __func__, lmax);
        return;
    }

    /* left justified output */
    if (just == 'l') {
        for (i = 0; i < n; i++) {
            printf ("%s\n", t[i]);
        }
        return;
    }

    /* center or right justified output */
    for (i = 0; i < n; i++) {
        int spaces = w - len[i];
        if (just == 'c')
            printf ("%*s%s\n", spaces/2, " ", t[i]);
        else if (just == 'r')
            printf ("%*s%s\n", spaces, " ", t[i]);
    }
}

Note: if you are on windows, change __func__ to the function name in each of the error statements.

Logic of Function - Long Version

Let's look a little closer at what the function is doing and why it does it the way it does. First, lets look at the paramaters it takes:

void formatted (char **t, char just, size_t w, size_t n)

char **t, your 'string', well... actually your array or pointers to type char*. When you pass the array of pointers to your function, and this may be where your confusion is, you only have 2 ways to iterate over the array an print each of the lines of text: (1) pass the number of valid pointers that point to strings containing text, or (2) provide a sentinel within the array that is pointed to by the pointer following the last pointer that points to a valid line of text. (usually just NULL) The sentinel serves as the pointer in the array that tells you ("Hey dummy, stop trying to print lines -- you already printed the last one...")

This takes a little more explanation. Consider your array of pointers in memory. Normally you will always allocate some reasonably anticipated number of pointers to fill, and when you fill the last pointer, you will realloc the array to contain more space. What this means is you will always have at least 1 pointer at the end of your array that is not filled. If you will intialize your array to contain NULL pointers at the very beginning (by allocating with calloc instead of malloc) -- you automatically provide a sentinel, or, you can always explicitly set the next pointer to NULL as you fill your array. This will leave your array of pointers to char* looking similar to the following:

  Pointer         The pointers in the array of pointers to char*, char **t;
  Address         point to the first character in each associated string.
+-----------+
| 0x192a460 | --> The quick brown
+-----------+
| 0x192a480 | --> fox jumps over
+-----------+
| 0x192a4a0 | --> a lazy
+-----------+
| 0x192a4c0 | --> dog.
+-----------+
|   NULL    |
+-----------+
|   NULL    |
+-----------+
    ...

Understand: your string, my t is an array of pointers to type char* not the strings themselves. Your string is the left column of pointers above. string is an array of the starting addresses of each real string and your string[i] will be the start of actual string itself -- at that address. i will range from 0-3 (4 total) where string[0] = "The quick brown", string[1] = "fox jumps over", etc.. To help, change the name in your code of string to array or str_array to help keep this straight.

Your 2 options for iterating over the array to print each string become (1) (with size 'n' passed to function):

for (i = 0; i < n; i++)
    printf ("%s\n", t[i]);

or (2) (relying on a sentinel):

while (t[i]))
    printf ("%s\n", t[i++]);

(while not readily apparent here, this provides significant benefits as your code becomes more complex and you need to pass the array between many different functions)

Now that you know how you can access each of your strings, start with the case where you will just print the string left justified. You don't care about the length and you don't care about the width (as long as your string will fit within the width). All you need to do is print each of the strings. Since we pass the number of strings 'n' to the funciton, we can simply use the for loop (method 1) to print each string. (the length is computed prior to printing to insure each string will fit in width, since we will use it later, we store each length in the variable length array len so we don't have to make redudant calls to strlen later)

The more interesting cases are the center and right justified cases. In addition to 'n' you need to know which justification the user wants. You can pass any type flag you want to pass that information to the function. A char (1-byte) is simply the most efficient and does not require conversion to a number when read as input to the program. That is essentially why I chose to pass just as a char instead of an int (4-bytes + conversion on input) (or short (2-bytes), etc..)

Let's first look at how we right-justify the output. For discussion, let's consider an output width of 20 characters you want to right-justify your strings in. Your first string is 15 (printable) characters long (it's actually 16 chars in memory due to the null-terminating char at the end). Let's visualize what our string of printable characters would look like in a 20-character buffer (which you would use to save a right-justified copy of the string in memory, rather than printing)

|<-------  20 character width  -------->|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | |T|h|e| |q|u|i|c|k| |b|r|o|w|n|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |<--  15 character length  -->|

This makes it easy to see how many spaces are needed before we start printing our string. The task here is to turn this into a generalized piece of code to handle any length string. Not too difficult:

spaces = width - length;

Then to utilize this to print the string right-justified, we make use of the minimum field width directive in the printf format string. Of particular use here, is the ability to specify the field width directive as a variable in the printf argument list. Constructing the format string and argument list to accomplish our goal, we have:

printf ("%*s%s\n", spaces, " ", t[i]);

Which says, print a minimum field width of spaces for the string " " (which essentially prints spaces number of spaces -- poor choice of names) followed by the actual string in our array of pointers t[i].

Looking at the diagram again, what would we have to do to shift the string to the center of the 20-character width? Instead of shifing the whole width - length number of spaces, we could only shift it 1/2 that much and it would end up where we want it. (I can hear the gears in your head grinding, and I can smell the smoke -- "but, wait... 5 is an odd number and we are using integers!" -- it doesn't matter, integer division will take care of it, and if we shift by 2 instead of 2.5, it's just fine, you can't print 1/2 a character... So putting it all together, to handle centered or right justified text, all you need is:

for (i = 0; i < n; i++) {
    int spaces = w - len[i];
    if (just == 'c')
        printf ("%*s%s\n", spaces/2, " ", t[i]);
    else if (just == 'r')
        printf ("%*s%s\n", spaces, " ", t[i]);
}

Putting It All Together With The Rest

Sometimes seeing how the whole thing fits together helps. Same rules. Go though it function-by-function, line-by-line, and ask questions when you get struck. C is a low-level language, meaning you have to understand where things are in memory. When it comes down to it, programming is really about how to manipulate what you load into memory. Other languages try to hide that from you. C doesn't. That's its strength, and also where you have to concentrate a good part of your learning. Enough babble, here is the code:

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

#define MAXC 256
#define MAXL 64

char **inputfile (char ***t, FILE *fp, size_t *sz);
void formatted (char **t, char just, size_t w, size_t n);
void *xcalloc (size_t n, size_t s);
void freecdp (char **p, size_t n);
unsigned chr2lower (unsigned c);

int main (int argc, char **argv) {

    if (argc < 4) {  /* validate required arguments on command line */
        fprintf (stderr, "error: insufficient input.  "
                        "usage: %s just width [filename (stdin)]\n",
                        argv[0]);
        return 1;
    }

    char **text = NULL;    /* initialize all variables for main   */
    size_t lines = 0;
    char j = 0;            /* the following are ternary operators */
    char just = argc > 1 ? *argv[1] : 'l'; /* sets defaults if no input */
    size_t width = argc > 2 ? (size_t)strtol (argv[2], NULL, 10) : 0;
    FILE *fp = argc > 3 ? fopen (argv[3], "r") : stdin;

    /* read input from file */
    if (!(inputfile (&text, fp, &lines))) { /* check if return is NULL */
        fprintf (stderr, "error: file read failed '%s'.\n", 
                argc > 3 ? argv[3] : "stdin");
        return 1;
    }

    j = chr2lower (just);  /* force user input to lower-case */
    if (j != 'l' && j != 'r' && j != 'c')
        fprintf (stderr, "error: invalid justification '%c' "
                        "(defaulting to 'left').\n", just);

    /* print output in requested justification */
    formatted (text, j, width, lines);

    /* free all memory allocated in program */
    freecdp (text, lines);

    return 0;
}

char **inputfile (char ***t, FILE *fp, size_t *sz)
{
    if (!t || !sz) {  /* validate parameters are not NULL */
        fprintf (stderr, "%s() error: invalid parameters.\n", __func__);
        return NULL;
    }

    if (!fp) {        /* check that file pointer is valid */
        fprintf (stderr, "%s() error: file open failed.\n", __func__);
        return NULL;
    }

    size_t idx = 0;       /* declare/initialize function variables */
    size_t maxl = MAXL;
    char ln[MAXC] = {0};

    /* allocate MAXL number of pointers */
    *t = xcalloc (MAXL, sizeof **t);

    while (fgets (ln, MAXC, fp)) {   /* read each line in file */

        size_t len = strlen (ln);    /* calculate length */

        /* remove trailing newline (or carriage return) */
        while (len && (ln[len-1] == '\n' || ln[len-1] == '\r'))
            ln[--len] = 0;

        /* allocate & copy ln saving pointer in t[i], increment i by 1 */
        (*t)[idx++] = strdup (ln);     /* strdup allocates & copies    */

        if (idx == maxl) {  /* check if you reached limit, realloc if needed */
            void *tmp = realloc (*t, maxl * sizeof **t * 2);
            if (!tmp) {
                fprintf (stderr, "%s() virtual memory exhausted.\n", __func__);
                return NULL;

            }
            *t = tmp;  /* set new pointers NULL below (sentinel) */
            memset (*t + maxl, 0, maxl * sizeof **t);
            maxl *= 2;
        }
    }
    *sz = idx;  /* update value at address of sz so it is available in main */

    if (fp != stdin) fclose (fp);

    return *t;
}

/*  format 'n' lines of output of 't' justified as specified in 'just'
    (left 'l', right 'r' or centered 'c') within a width 'w'. 
    NOTE: 'w' must be larger than the longest line in 't'.
*/
void formatted (char **t, char just, size_t w, size_t n)
{
    if (!t || !*t) return;
    size_t i = 0;
    size_t lmax = 0;
    size_t len[n];

    /* calculate the length of each line, set lmax */
    for (i = 0; i < n; i++) {
        len[i] = strlen (t[i]);
        if (len[i] > lmax) lmax = len[i];
    }

    /* handle w < lmax reformat or error here */
    if (w < lmax) {
        fprintf (stderr, "%s() error: invalid width < lmax (%zu).\n", 
                __func__, lmax);
        return;
    }

    /* left justified output */
    if (just == 'l') {
        for (i = 0; i < n; i++) {
            printf ("%s\n", t[i]);
        }
        return;
    }

    /* center or right justified output */
    for (i = 0; i < n; i++) {
        int spaces = w - len[i];
        if (just == 'c')
            printf ("%*s%s\n", spaces/2, " ", t[i]);
        else if (just == 'r')
            printf ("%*s%s\n", spaces, " ", t[i]);
    }
}

/* help functions below for calloc, free, and to lower-case */
void *xcalloc (size_t n, size_t s)
{
    register void *memptr = calloc (n, s);
    if (memptr == 0) {
        fprintf (stderr, "%s() error: virtual memory exhausted.\n",
                __func__);
        exit (EXIT_FAILURE);
    }

    return memptr;
}

void freecdp (char **p, size_t n)
{
    if (!p) return;
    size_t i;

    for (i = 0; i < n; i++)
        free (p[i]);
    free (p);
}

unsigned chr2lower (unsigned c)
{   return ('A' <= c && c <= 'Z') ? c | 32 : c; }

Example Use/Output

$ ./bin/str_justify l 15 dat/fox_4lines.txt
The quick brown
fox jumps over
a lazy
dog.

$ ./bin/str_justify c 15 dat/fox_4lines.txt
 The quick brown
 fox jumps over
    a lazy
     dog.

$ ./bin/str_justify r 15 dat/fox_4lines.txt
 The quick brown
 fox jumps over
         a lazy
           dog.
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Why are you checking the length of the line? The 15 given in the example is how many spots you can have per one line, or is that what you are doing? – Adam Alred Oct 14 '15 at 20:08
  • Well, you have to check the length of the line to figure out how many spaces you need to print before the text for `c` centered or `r` right justified text. So since you will need to compute and use the length more than once, for efficiency sake, why not simply calculate the length once and store it in an array `len` for later use. – David C. Rankin Oct 14 '15 at 20:14
  • Okay, I think that is making sense. Sorry, I'm freaking out so much about this project that I'm not really thinking straight:/ I appreciate the help though – Adam Alred Oct 14 '15 at 20:22
  • No problem. I find that if I'm stuck on part of a problem, I'll go do something else for a little while and let my mind continue thinking about the different ways to approach the problem. 99% of the time, your mind just naturally sifts through the possibilities and hits on the solution -- as long as you give it time to think. If you sit there and continue to bang your head into the monitor without a break, you generally end up with nothing more than a sore forehead. 10 minutes or so is usually long enough. If you get totally wrapped around the axle, I have the rest I can post. – David C. Rankin Oct 14 '15 at 20:50
  • `for(i = 0; I < strlen(string);` This won't work. `string` is `char **`. I'll add a long explanation that I think will help. – David C. Rankin Oct 15 '15 at 00:47
  • @AdamAlred what I want to make sure doesn't get lost in the fray is the fact that there are probably 2 or more ways to approach any part of the code. For instance in `inputfile` where i use `strdup` to allocate and copy the current line to a new block of memory and assign it to the address to `(*t)[idx]` instead of using 2 lines `malloc` and then `strncpy`, doesn't mean the `malloc/strncpy` approach is incorrect. Quite the opposite, both are 100% correct ways to accomplish the task. So don't think you are not free to do it the way you like (as long as it is also 100%) correct `:)` – David C. Rankin Oct 15 '15 at 02:08