1

I am trying to create a program that ask the user to input a sentence and then takes that sentence and puts it in a list. Then the user should be able to search that list for words they already entered, add a new word to the list and get a updated alphabetical list. I feel like the last two parts will not be too taxing. The problem I am having is when I try to create and print the list to the console nothing is coming out.

This is the code I have so far.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WORD1 256
#define WORD2 256
void word_swap(char** w1, char** w2){return 0;};

int main(int argc, char **argv)
{
    int i, j, ans, count, k=0, l=0, swapped=0;
    char firstSentence[WORD1][WORD2];
    char ch;
    char* sentence;


    puts ("Could you please type a sentence:");
    gets (firstSentence);
    puts("\nYour sentence has been saved.");
    puts("\n******************************");
    puts("\nWhat would you like to do now?");
    puts("\n\n1) Read your sentence.");
    puts("2) See a list of words in your sentence.");
    puts("3) Add a word to your sentencene list.");
    puts("4) Search for a word in your sentence list.");
    puts("5) Exit the program.");


    do
    {
    puts("\nType your response:");
    scanf("%d", &ans);
    printf("\n\n");


    switch (ans){
        case(1): 
            puts(firstSentence);
            break; 

        case(2): 
            for (i=0; i<WORD1*WORD2; i++) {
                if (*(sentence+i) == '\0') {
                    firstSentence[k][l] = '\0';
                    count = k+1;
                    break;
                }
            if (*(sentence+i) != ' ') {
                firstSentence[k][l] = *(sentence+i);
                l++;
            }
            else {
                firstSentence[k][l] = '\0';
                k++;
                l=0;
            }
         do {
            for (i=0; i<count-1; i++) {
                if (strcmp(firstSentence[i], firstSentence[i+1]) > 0) {
                    word_swap(&firstSentence[i], &firstSentence[i+1]);
                    swapped = 1;
                    break;
                } else swapped = 0;
            }
        } while (swapped != 0);
        for (i=0; i<count; i++){
            printf("%s\n", firstSentence[i]);
        }
    }
        case(3):/* puts("Please enter a new word.");
                        scanf("%c", &words);
                        break;
                        puts("\n\nWould you like to see your new word list now?");
                        puts("Please enter 'Y' or 'N'");
                        gets(ch);
                        if (strcmp(ch, toupper(ch))>= 1){
                            puts(sentence);
                        break;
                        }*/
        case(4):
        case(5): exit(1);
    }
    }while ((ans < 1) || (ans > 5));


    system("pause");
    return 0;
} 

The problem is in the case(2) section but I included the full code just in case there is something in there I am missing. Case(1) works fine and case(3) was me getting ahead of myself because I can't really add a word to a list that I haven't created (or is it created and the problem is I can't see it in the console???). When I build and press 2 after the menu it drops 2 empty lines and does not take anymore input nor does it print anything out nor does the 'press any key to continue' prompt come up.

Side note, I am very new to coding this is my 3rd day working on C and any help would be appreciated.

K Brown
  • 11
  • 4
  • Refactor brackets for `for` in `case(2)` - they are in the wrong positions. – Maneevao Jan 20 '20 at 04:40
  • See: [Why gets() is so dangerous it should never be used!](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used) and `for (i=0; i= 256` or `k >= 256` in `firstSentence[k][l]`. – David C. Rankin Jan 20 '20 at 04:42
  • @Maneevao do you mean the indentation of them are off? They look a little different in my editor and are lined up to the start of the line that they close. – K Brown Jan 20 '20 at 04:44
  • @KBrown No, you placed a close bracket after `case(5)`. – Maneevao Jan 20 '20 at 04:47
  • @DavidC.Rankin ok my thinking was gets() would be more helpful since I am trying to get a full sentence at one time. So scanf("%[^\n]")? – K Brown Jan 20 '20 at 04:50
  • @Maneevao ok cleaned that up. Didn't change anything in the output but I know its necessary. – K Brown Jan 20 '20 at 04:52
  • The problem is `firstSentence` is a 2D array and you are attempting to read with `gets(firstSentence);`. When you access `firstSentence` the type is `char (*)[256]` (a *pointer-to-array* [256]). The first level of indirection for an array is converted to a pointer. See [C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p3) (note the 4-exceptions). You are better off using `fgets (firstSentence[0], WORD2, stdin)` and use `firstSentence[0][strcspn (firstSentence[0], "\n")] = 0;` to trim the `'\n'`. – David C. Rankin Jan 20 '20 at 05:15

1 Answers1

1

OK, it's obvious, you are quite lost. Continuing from my comment above, let's start with what variables your program needs. (you can do this any number of ways, this is just one). You need a character array (buffer) of sufficient size to hold your sentence. This is where your WORD1*WORD2 (65K) size comes in. If you allow a maximum of WORDS1 words in your sentence and a maximum of WORDS2 characters in each word in the sentence, then you will need WORD1*WORD2+1 characters to ensure the max number of max sized words fits (the +1 is for the nul-terminating character (which you can safely omit in this case as the WORDS2 word length is way overkill as the longest word in the Unabridged Dictionary (non-medical) is only 29-characters))

So you can declare char sentence[WORD1*WORD2]; and declare a second array to hold a copy, e.g. char copy[WORD1*WORD2]; because we will use strtok to separate the sentence into individual words (and strtok modifies the string it operates on -- so make a copy if you need to preserve the original -- and you do). Your 2D array for storing the separated words char firstSentence[WORD1][WORD2]; is fine, but WORDS2 is still overkill for the same reason -- but fine for now.

So the basic approach is read your sentence into sentence and then remove the trailing '\n' by overwriting the '\n' with the '\0' character (0 is equivalent, See ASCII Table and Description. Then simply make a copy of sentence in the equal size array copy and then use strtok to separate the individual words in copy (using delimiters of " \t\n" (space, tab, newline)) and copy the individual words to the appropriate row in firstSentence. That process can be:

int main (void)     /* you don't take any arguments, specify (void) */
{
    int i, ans, k=0, ch;
    char sentence[WORD1*WORD2];                 /* 65K char array */
    char copy[WORD1*WORD2];                     /* copy to use w/strtok */
    char firstSentence[WORD1][WORD2];           /* 256x256 array of char */

    puts ("Could you please type a sentence:");
    if (!fgets (sentence, WORD1, stdin)) {      /* read w/fgets & validate */
        fputs ("error: user canceled input.\n", stderr);
        return 1;
    }
    sentence[strcspn (sentence, "\n")] = 0;     /* trim trailing '\n' */
    strcpy (copy, sentence);    /* copy sentence - strtok modifies string */
    /* tokenize individual words into firstSentence[0...WORD1-1] */
    for (char *p = strtok(copy," \t\n"); p && k < WORD1; p = strtok (NULL, " \t\n"))
        strcpy (firstSentence[k++], p);

    puts ("\nYour sentence has been saved.\n");
    ...

With your sentence read and separated into individual words, it's time to display your menu and take the users choice (note: you must display the menu *within the loop, otherwise after it is displayed once and the user is left wondering what to do). For example:

    do
    {   /* you need to display menu within loop */
        puts ("\n******************************\n"  /* you only need 1 puts */
            "What would you like to do now?\n\n"
            "  1) Read your sentence.\n"
            "  2) See a list of words in your sentence.\n"
            "  3) Add a word to your sentencene list.\n"
            "  4) Search for a word in your sentence list.\n"
            "  5) Exit the program.\n\n"
            "Type your response:");

        if (scanf ("%d", &ans) != 1) {           /* validate EVERY input */
            fputs ("error: invalid integer input.\n", stderr);
            exit (EXIT_FAILURE);
        }
        printf("\n\n");
        ...

Now on to your switch() statement that can be simplified quite a bit from your first attempt. For example to display the sentence, all you need do is puts (sentence);, e.g.

        switch (ans) {
            case(1): 
                puts (sentence);                /* buffer contains \n */
                break; 
            ...

Listing the words simply requires looping over the filled elements of firstSentence. That can be done like:

            case(2): 
                for (i = 0; i < k; i++) 
                    puts (firstSentence[i]);     /* output word in sentence */
                break;
            ...

Your case 3: that is currently commented, isn't that difficult, but does have a few caveats given your choice of mixing string and character input. That is doable, but all input is better read with fgets and then parse the resulting buffer to get the values you need. The largest benefit is that a line-of-input at-a-time is consumed and you don't have to worry about what remains in stdin messing up your next attempted read. But we will do it the hard way similar to what you had.

The basic approach is read the word into the next element of firstSentence. scanf is used below with the "%s" conversion specifier which stops reading when it encounters the first whitespace (and a '\n' is considered whitespace). Since scanf stop reading -- it also stops extracting characters from stdin leaving the '\n' in stdin unread -- just waiting to bite you when you attempt to read a single-character for your Y/N test.

Thankfully that is solvable by simply reading from stdin with getchar() until a '\n' or EOF is encountered. So if there are extraneous character you need to discard from stdin, just loop with getchar() until one of those conditions is reached. A handy helper-function called empty_stdin can be written to do this for you. At the top of the source file, I have included:

/* function to empty stdin of all chars */
void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

That help with your case 3: to keep things tidy when you need to empty stdin, e.g.

            case(3):
                puts("Please enter a new word.");
                    if (scanf("%255s", firstSentence[k]) == 1) {
                        strcat (sentence, " ");
                        strcat (sentence, firstSentence[k]);
                        k++;
                        empty_stdin();          /* remove chars from stdin */
                        puts ("\n\nWould you like to see your new word list now?");
                        puts ("Please enter 'Y' or 'N'");
                        ch = getchar();         /* ch must be type int */
                        empty_stdin();          /* remove chars from stdin */
                        if (toupper(ch) == 'Y')
                            for (i = 0; i < k; i++)
                                puts (firstSentence[i]);
                    }
                break;

(note: if using scanf to fill a character array, always provide the field-width modifier (of 1-less than the array size) to protect against writing beyond the end of your array if the string you are reading is larger than the number of characters available)

Your case 4: isn't provided, and currently falls-through to case 5: (no break; in 4 -- falls through to case 5:) where 5 calls exit(1); -- exit(0); would be better as returning 1 to your shell generally indicates error.

You should consider adding a default: case to handle integer values outside your menu range, for example:

            case(4):
            case(5): exit(1);
            default:
                fputs ("error: invalid entry.\n", stderr);
                break;
        }
    } while (ans != 5);

(note: also the loop continues as long as the user has not entered 5 to exit -- which you can replace with while(1); as you use case 5: to break the loop and exit.

Putting it altogether and adding a simple check so those on Linux do not reach the system("pause"); code presumably used to hold your terminal window open on Windows (which isn't really needed in this code anyway), you could do something like:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>      /* for toupper() */

#define WORD1 256
#define WORD2 WORD1

/* function to empty stdin of all chars */
void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

int main (void)     /* you don't take any arguments, specify (void) */
{
    int i, ans, k=0, ch;
    char sentence[WORD1*WORD2];                 /* 65K char array */
    char copy[WORD1*WORD2];                     /* copy to use w/strtok */
    char firstSentence[WORD1][WORD2];           /* 256x256 array of char */

    puts ("Could you please type a sentence:");
    if (!fgets (sentence, WORD1, stdin)) {      /* read w/fgets & validate */
        fputs ("error: user canceled input.\n", stderr);
        return 1;
    }
    sentence[strcspn (sentence, "\n")] = 0;     /* trim trailing '\n' */
    strcpy (copy, sentence);    /* copy sentence - strtok modifies string */
    /* tokenize individual words into firstSentence[0...WORD1-1] */
    for (char *p = strtok(copy," \t\n"); p && k < WORD1; p = strtok (NULL, " \t\n"))
        strcpy (firstSentence[k++], p);

    puts ("\nYour sentence has been saved.\n");
    do
    {   /* you need to display menu within loop */
        puts ("\n******************************\n"  /* you only need 1 puts */
            "What would you like to do now?\n\n"
            "  1) Read your sentence.\n"
            "  2) See a list of words in your sentence.\n"
            "  3) Add a word to your sentencene list.\n"
            "  4) Search for a word in your sentence list.\n"
            "  5) Exit the program.\n\n"
            "Type your response:");

        if (scanf ("%d", &ans) != 1) {           /* validate EVERY input */
            fputs ("error: invalid integer input.\n", stderr);
            exit (EXIT_FAILURE);
        }
        printf("\n\n");


        switch (ans) {
            case(1): 
                puts (sentence);                /* buffer contains \n */
                break; 

            case(2): 
                for (i = 0; i < k; i++) 
                    puts (firstSentence[i]);     /* output word in sentence */
                break;

            case(3):
                puts("Please enter a new word.");
                    if (scanf("%255s", firstSentence[k]) == 1) {
                        strcat (sentence, " ");
                        strcat (sentence, firstSentence[k]);
                        k++;
                        empty_stdin();          /* remove chars from stdin */
                        puts ("\n\nWould you like to see your new word list now?");
                        puts ("Please enter 'Y' or 'N'");
                        ch = getchar();         /* ch must be type int */
                        empty_stdin();          /* remove chars from stdin */
                        if (toupper(ch) == 'Y')
                            for (i = 0; i < k; i++)
                                puts (firstSentence[i]);
                    }
                break;

            case(4):
            case(5): exit(1);
            default:
                fputs ("error: invalid entry.\n", stderr);
                break;
        }
    } while (ans != 5);

#if defined (_WIN32) || defined (_WIN64)
    system("pause");
#endif
    return 0;
}

Example Use/Output

An example user sessions with your code could be similar to the following that exercises all case statements in your switch() (except for case 4: -- boring...)

$ ./bin/readsentenceintowords
Could you please type a sentence:
My dog has fleas

Your sentence has been saved.


******************************
What would you like to do now?

  1) Read your sentence.
  2) See a list of words in your sentence.
  3) Add a word to your sentencene list.
  4) Search for a word in your sentence list.
  5) Exit the program.

Type your response:
1


My dog has fleas

******************************
What would you like to do now?

  1) Read your sentence.
  2) See a list of words in your sentence.
  3) Add a word to your sentencene list.
  4) Search for a word in your sentence list.
  5) Exit the program.

Type your response:
2


My
dog
has
fleas

******************************
What would you like to do now?

  1) Read your sentence.
  2) See a list of words in your sentence.
  3) Add a word to your sentencene list.
  4) Search for a word in your sentence list.
  5) Exit the program.

Type your response:
3


Please enter a new word.
--bummer


Would you like to see your new word list now?
Please enter 'Y' or 'N'
y
My
dog
has
fleas
--bummer

******************************
What would you like to do now?

  1) Read your sentence.
  2) See a list of words in your sentence.
  3) Add a word to your sentencene list.
  4) Search for a word in your sentence list.
  5) Exit the program.

Type your response:
1


My dog has fleas --bummer

******************************
What would you like to do now?

  1) Read your sentence.
  2) See a list of words in your sentence.
  3) Add a word to your sentencene list.
  4) Search for a word in your sentence list.
  5) Exit the program.

Type your response:
5

Look things over and let me know where you have questions. You were way out in left-field on some of the topics, so it is likely you will have one or two questions about what was done and why? I'm happy to help further.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Sorry for the delay I haven't been able to work on this in a little while. This worked for me and the way you explained it was very good. The only question I have is how do I get to loop back to the menu. I thought about putting another do loop at the top of the menu and giving a binary return at the end of each case to have it start over but I feel like that is cluttered and even though it would be fine in the scope of this program it would be a messy way to work in larger programs. Thanks again for your help! – K Brown Jan 22 '20 at 18:22
  • Easy way is just to put an infinite loop (e.g. `for (;;) {...}` or `while (1) {...}`) beginning before `puts ("Could you please type a sentence:");` and ending after the close of the current menu loop. Then change `case 5:` to include `goto done;` before `break;` and add `done:;` just after the end of your infinite loop so on exit you jump out of your nested loops (for which `goto` is the only option). Give it a go and let me know if you run into problems. – David C. Rankin Jan 22 '20 at 18:29
  • Ok I am getting the loop to work fine. Could you clarify the `if (scanf("%255s", firstSentence[k]) == 1)` line for me. I'm not sure why the 255 comes is in the string identifier. I changed some of my global variables to be smaller for the string inputs and in my working code I changed that number to reflect that, thinking the 255 was for the 256 string deceleration with room for a null zero. – K Brown Jan 22 '20 at 19:13
  • Since the max number of characters you can store any any `firstSentence` string is `256` characters, and you have to save `+1` for the *nul-terminating* character at the end, you set the *field-width* modifier to read no more than `255` characters into your `firstSentence[k]` array. So instead of `"%s"`, add the *field-width* modifier, `"%255s"` and you eliminate the ability to read more characters than you can store. The `if (scanf("%255s", firstSentence[k]) == 1) ` validates that 1 conversion succeeded -- validating that characters were read into your string. **VALIDATE EVERY INPUT** – David C. Rankin Jan 22 '20 at 19:48
  • Also, if you spend an hour or so really reading (and understanding) [man 3 scanf](http://man7.org/linux/man-pages/man3/scanf.3.html) it will save you tens or hundreds of hours of frustration later `:)` – David C. Rankin Jan 22 '20 at 19:55