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.