# Counting and printing sentences and words

Any sequence of characters starting with a non-whitespace character and ending with any of the following is considered to be a sentence by this program :-

1. a full stop '.' followed by whitespace
2. an exclamation mark '!' followed by whitespace
3. a question mark '?' followed by whitespace
4. end-of-file

Also, any sequence of non-whitespace characters is considered to be a word by this program.

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

char * get_string_from_user(void);
void count_and_print_units(char *, bool);

int main(void)
{
printf("Enter a string (EOF to stop):-\n");
char * string = get_string_from_user();

count_and_print_units(string, true);
count_and_print_units(string, false);

free(string);

return 0;
}

char * get_string_from_user(void)
{
// string is freed in main().
size_t assumed_size_of_string_array = 100;
char * string = (char *) malloc(assumed_size_of_string_array);
if (string == NULL)
exit(EXIT_FAILURE);

size_t i = 0;
int c;
while ((c = getchar()) != EOF)
{
string[i] = c;

i++;
if (i == assumed_size_of_string_array)
{
assumed_size_of_string_array *= 2;

char * temp = realloc(string, assumed_size_of_string_array);
if (temp == NULL)
exit(EXIT_FAILURE);
string = temp;
}
}
string[i] = '\0';

size_t actual_size_of_string_array = i+1;

char * temp =
realloc(string, actual_size_of_string_array);
if (temp == NULL)
exit(EXIT_FAILURE);
string = temp;

return string;
}

void count_and_print_units(char * string, bool flag)
{
// If (flag == true), then 'units' means 'sentences'. Else, 'units' means 'words'.

// Any sequence of characters starting with a non-whitespace character and ending with either
// (1) a full stop followed by whitespace, or
// (2) an exclamation mark followed by whitespace, or
// (3) a question mark followed by whitespace, or
// (4) End-of-file
// is considered to be a sentence by this function.

// Any sequence of non-whitespace characters is considered to be a word by this function.

size_t assumed_number_of_units = (flag ? 10 : 100);

char ** starting_addresses_of_units = (char **) malloc(assumed_number_of_units * sizeof(char *));
exit(EXIT_FAILURE);

size_t * lengths_of_units = (size_t *) calloc(assumed_number_of_units, sizeof(size_t));
if (lengths_of_units == NULL)
exit(EXIT_FAILURE);

size_t i = 0, j = 0;
while (true)
{
while (isspace(string[j]))
j++;

if (string[j] == '\0')
break;

if (i == assumed_number_of_units)
{
assumed_number_of_units *= 2;

char ** temp1 = realloc(starting_addresses_of_units, assumed_number_of_units * sizeof(char *));
if (temp1 == NULL)
exit(EXIT_FAILURE);

size_t * temp2 = realloc(lengths_of_units, assumed_number_of_units * sizeof(size_t));
if (temp2 == NULL)
exit(EXIT_FAILURE);
lengths_of_units = temp2;
}

while (string[j] != '\0')
{
if (flag)
{
if (isspace(string[j]))
if ((string[j-1] == '.') || (string[j-1] == '!') || (string[j-1] == '?'))
break;
}
else
{
if (isspace(string[j]))
break;
}

(lengths_of_units[i])++;
j++;
}

i++;
}

size_t actual_number_of_units = i;

if (actual_number_of_units)
{
char ** temp1 = realloc(starting_addresses_of_units, actual_number_of_units * sizeof(char *));
if (temp1 == NULL)
exit(EXIT_FAILURE);

size_t * temp2 = realloc(lengths_of_units, actual_number_of_units * sizeof(size_t));
if (temp2 == NULL)
exit(EXIT_FAILURE);
lengths_of_units = temp2;
}
else
{

free(lengths_of_units);
lengths_of_units = NULL;
}

printf("\n\nNumber of %s = %zu\n\n", (flag ? "sentences" : "words"), i);

for (size_t x = 0; x < actual_number_of_units; x++)
{
printf("%s No. %03zu: ", (flag ? "Sentence" : "Word"), x+1);
for (size_t y = 0; y < lengths_of_units[x]; y++)
{
putchar(' ');
else
}
printf("\n");
}

free(lengths_of_units);
}

• Be careful when editing your code that you don't invalidate the answers, which breaks the question-and-answer nature of this site. See What should I do when someone answers my question?. In this case, the edit appears benign, but in general we prefer that the code isn't edited after receiving your first answer. Thanks! Sep 5, 2021 at 7:38

There's no need to cast the return value from malloc() family of functions - in C, void* is convertible to any kind of object pointer without a cast.

The error handling is very primitive - when allocation fails, I'd expect at least some message to stderr before terminating. It's a shame that we've wasted the care taken to avoid memory leak in realloc() - the call to exit() means we might as well have just written foo = realloc(foo, n) for what it's worth!

We shouldn't need to do any allocation in the counting function anyway - and we shouldn't need to modify the input string, so accept const char* as argument.

isspace(string[j]) is risky, because char may be a signed type. The functions in <ctype.h> take integers that represent values; the usual method is to cast to unsigned char so that promotion to int keeps valid characters positive.

The name count_and_print_units() should be a clue that this function has two unrelated responsibilities. If we can separate those, then our functions become more reusable (e.g. we could do counting in a GUI program more easily).

The name flag is very vague. A better name would be count_sentences.

This is problematic:

if (isspace(string[j]))
if ((string[j-1] == '.')


What if the first character is a space?

This code block is redundant:

else
{

free(lengths_of_units);
lengths_of_units = NULL;
}


We could leave those variables as they are, and the same actions will happen later in the function.

• This is problematic: if (isspace(string[j])) if ((string[j-1] == '.'). What if the first character is a space?. I believe that while (isspace(string[j])) j++; at the beginning of the outer loop takes care of this possibility. Sep 5, 2021 at 4:02
• Yes, I missed that - it's some distance away. Sep 5, 2021 at 7:29

regarding:

char ** temp1 = realloc(starting_addresses_of_units, assumed_number_of_units * sizeof(char *));
if (temp1 == NULL)
exit(EXIT_FAILURE);


before exiting, pass all the allocated memory areas to free()

• Why do you recommend that? exit() terminates the process, so all allocated memory will be released anyway. Sep 5, 2021 at 21:35
• @TobySpeight, when you get past the 'toy' programs and enter the reliem of real time embedded programs (and/or server programs) The program ios rarely ever exited, so the programs need to cleanup after their selves. Expecting the OS to perform the cleanup for you will get you seriously burned Sep 6, 2021 at 22:25
• You should be clearer in the answer that this is more of a general good habit then, since that concern obviously doesn't apply to this program. (Another good reason is to help keep Valgrind output uncluttered so we can easily identify genuine problems). Sep 7, 2021 at 7:15