1

I am trying to loop through an array that contains a string with is several words separated by spaces. Ultimately I want to make each word within the string a new variable so I can compare it to other variables.

My approach is to loop though assigning that index of the array to another array until I hit a space where it then starts a new loop starting off where the other one fished again storing to another array. This doesn't seem to be working for me.

What is the simplest way of doing this? I am very new to this so the simpler the better. I've attached what I've tried so far.

#include <stdio.h>

int main(){

    char input[100];
    char order1[20];
    char order2[20];
    char order3[20];
    char order4[20];
    char space = ' ';

    int counter = 0;

//input is an order that is separated by spaces
    fgets(input, 100, stdin);

    printf("The order is: %s \n", input);

    for(int i; i<20; i++){
        printf("%c \n", input[i]);
    }


    for(int i=counter; i<100; i++) {
        if(input[i] == space){
            break;
        }
        else {
            order1[12] += input[i];
        }
    }

    for(int j=counter; j<100; j++) {
        if(input[j] == space){
            break;
        }
        else {
            order2[12] += input[j];
        }
    }
    
    for(int k=counter; k<100; k++) {
        if(input[k] == space){
            break;
        }
        else {
            order3[12] += input[k];
        }
    }

    for(int l=counter; l<100; l++) {
        if(input[l] == space){
            break;
        }
        else {
            order4[12] += input[l];
        }
    }


    printf("Order 1: %s \n", order1);
    printf("Order 2: %s \n", order2);
    printf("Order 3: %s \n", order3);
    printf("Order 4: %s", order4);
    return 0;


    return 0;
}
10
  • I will give a look at your code, but for starters, the limiting length in your "fgets" input should be one less than the character array capacity so that there is accommodation for the '\0' terminator. Commented Mar 16, 2024 at 16:33
  • 1
    One other thing. When compiling your code, I received a warning about the variable "i" not being initialized in "for(int i; i<20; i++){". You should pay attention to all compiler warnings as they can indicate possible undefined behavior. Second, you probably want to include the <string.h> include reference to be able to use the "strtok" function for simple parsing. Following is one link in that regard ("educative.io/answers/splitting-a-string-using-strtok-in-c") Commented Mar 16, 2024 at 16:53
  • 1
    @NoDakker: supplying sizeof(buffer) as the size argument to fgets(buffer, sizeof(buffer), fp) is perfectly safe. Don't provide an array of 1 byte; you'll achieve nothing useful. And the buffer needs to be a local or global array, not a function parameter. Commented Mar 16, 2024 at 17:09
  • Good point. I kinda read through it too quickly. Commented Mar 16, 2024 at 17:10
  • 1
    Consider applying strtok() to the input string, in a loop. Commented Mar 16, 2024 at 23:10

2 Answers 2

0

If I understood correctly, the problem is to split the string at spaces and store those tokens in a separate string.

If this is the case, one might ask what utility the following construct has:

for (int i=counter; i<100; i++) { //from offset 0 to 100
                                  //counter is set to 0 and never modified again
    if (input[i] == space) {    //if input at offset i is space
        break;                  //end loop
    } else {                    //else for every other character in input
        order1[12] += input[i]; //increase the value at offset 12 in array order1
                                //by the value at offset i in input
                                //(considering that the chars are most likely 
                                // alphanumeric, we reach the limit of a char 
                                // very fast)
    }
}

Since you're not doing anything else with the array order1 (except printing), the problem is that 19 out of 20 characters are uninitialized and the value at offset 12 is most likely not what you expect.

First, we get rid of the magic numbers and define meaningful symbols

#define MAX_INPUT_LENGTH 100 //maximum amount of chars in input
#define MAX_ORDER_LENGTH 20  //maximum amount of chars in an order

char input[MAX_INPUT_LENGTH];

char order1[MAX_ORDER_LENGTH];
char order2[MAX_ORDER_LENGTH];
//...

but instead of single variables it would be better if we had an array of strings, therefore

#define MAX_NUM_ORDER 4 //maximum amount of orders
char order[MAX_NUM_ORDER][MAX_ORDER_LENGTH];

we now have an array of array of char or in other words, a list of orders, where each order has a specific length.

Let's fill them up:

//read input
if (fgets(input, MAX_INPUT_LENGTH, stdin) == NULL) {
    fputs("read input failed.\n", stderr); //print to error stream
    exit(EXIT_FAILURE); //exit program with error status code
}

//at this point, we should have a valid input
printf("The order is: %s\n", input);

char *pos = input; //pointer to first char in input
char *mark = pos;  //order token start
int num_order = 0; //number of orders, also slot offset
int done = 0;      //loop condition

//for each occurrence of space in input string
do {
    //find first occurrence of space
    pos = strchr(pos, ' ');
    if (pos == NULL) { //not found
        //set pos to eol (end of line)
        pos = input + strlen(input);
        //indicate end of loop
        done = 1; 
    }

    //order token length
    const size_t order_length = pos - mark;

    //test string size limit (excluding one for '\0')
    if (order_length < MAX_ORDER_LENGTH) { 

        //test list size limit
        if (num_order < MAX_NUM_ORDER) { 

            //copy from input into order string
            memcpy(order[num_order], mark, order_length); 
            //make null terminated string
            order[num_order][order_length] = '\0'; 
            //increasee the number of orders by one
            ++num_order; 
            //set new order token start (skip space)
            mark = ++pos; 

        } else {
            fputs("not able to store more orders.\n", stderr);
            exit(EXIT_FAILURE);
        }

    } else {
        fputs("order is too long.\n", stderr);
        exit(EXIT_FAILURE);
    }

} while (!done);

//print all orders
for (int i=0; i < num_order; ++i) {
    printf("Order %d: %s\n", i, order[i]);
}
Sign up to request clarification or add additional context in comments.

Comments

0

You have two primary challenges in your question:

  1. separating the space-separated word read into input; and
  2. storage for each word.

While there are may ways to "tokenize" (separate) input, when wanting space-separated words, a very simple way to do that is to just read with fgets() to fill input (as you have done) and then simply loop over each word in input using sscanf() with the %s conversion specifier (with a proper field-width modifier to protect your array bounds) and using the %n pseudo-conversion specifier to capture the number of character read on each call to sscanf() so you can capture the next word by providing an offset from the beginning of input.

Using multiple character arrays to hold each word quickly becomes unwieldy (as I suspect you have found). What if you don't know the number of words you are going to read?

Since you emphasize using a "simple" approach, by far the simplest is to use a 2D array of characters where each row holds a word. Now you can simply reference each word by the index for the 2D array (a 2D array actually being a 1D "array-of-arrays" in C - so the first index points to the 1st array, and so on). Here you are limited to reading no more than your rows number of words -- but with a reasonable number of rows set, that provides a great deal of flexibility. You must remember to check each time you add a word against the number of rows you have so you don't attempt to add more words to your 2D array than you have rows for.

A slightly more complex approach would be to declare a pointer-to-array of characters which would allow you to reallocate storage for more words with the only limit being the amount of memory your computer has. (a pointer-to-array allocation also has the benefit of a single-free)

One step further would be to allocate the exact number of characters needed to hold each word and the exact number of pointers needed to keep track of each word in your collection -- but that gets well beyond your "simple" request. Just know there are more flexible ways you will learn in the future.

What Is This Simple Approach?

Before looking at the code, let's think through what you want to do. You will need to:

  1. Prompt the user and read the words into input using fgets()
  2. Loop checking that you still have a row available to hold the next word; and
  3. Pass input plus an offset to sscanf()
  • separating the next word with "%s" (with appropriate field-width modifier) to capture the word (you can use a temporary array to hold the separated word and copy to your array, or you can attempt the read directly into your 2D array providing an index); and
  • saving the number of characters sscanf() read to process that word with "%n" so you add that to the offset within input to read the next word on your next call to sscanf().
  1. You ALWAYS validate that sscanf() (or any function) succeeds before going further and handle any error that may arise,
  2. Increment the word count (nwords) after successfully adding the word to your 2D array of words; and
  3. Add the number of characters used by sscanf() to an offset variable so you are ready to process the next word.
  4. After your loop with sscanf() ends, you simply loop nwords times outputting each word in the format you desire.

A short bit of code that does that could be:

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

#define MAXWRDS   64
#define WORDLN    64
#define MAXCHR  1024

int main (void) {

  char  input[MAXCHR] = "",             /* storage for line of input */
        words[MAXWRDS][WORDLN] = {""},  /* 2D array of MAXWRDS WORDLN words */
        word[WORDLN] = "";              /* temporary storage for each word */
  size_t nwords = 0;                    /* number of words (tokens) read */
  int offset = 0,
      nchars = 0;

  fputs ("input: ", stdout);        /* prompt */

  /* validate EVERY input */
  if (fgets (input, sizeof input, stdin) == NULL) {
    return 1;
  }

  /* Loop over every whitespace separated sequence
   * of chars reading into word. Use sscanf() to read
   * whitespace separated sequences of chars. Use %n to
   * get the number of characters processed in each call to
   * sscanf() to provide offset to next word in input.
   *
   * Do NOT forget field-width modifier to protect word array bounds
   * for reading into word and check you read no more than MAXWRDS words.
   */
  while (nwords < MAXWRDS &&
          sscanf (input + offset, "%63s%n", word, &nchars) == 1) {
    strcpy (words[nwords], word);   /* copy word to array at nwords index */
    nwords += 1;                    /* increment nwords */

    offset += nchars;               /* increment offset by no. of chars */
  }

  putchar ('\n');   /* optional newline before order output */

  /* loop over each word stored in words outputting order */
  for (size_t i = 0; i < nwords; i++) {
    printf ("order %2zu: %s\n", i + 1, words[i]);
  }
}

Using a #define up top for each constant you need provides a convenient place to make a single change should you need to adjust how your code behaves later.

Example Use/Output

Compiling the code (ALWAYS with full-compiler-warnings-enabled), your code will now do what you want. With the code compiled to the file named sscanfwordsinput2d, you can do:

$ ./sscanfwordsinput2d
input: my dog has fleas but my cat has none -- lucky cat!

order  1: my
order  2: dog
order  3: has
order  4: fleas
order  5: but
order  6: my
order  7: cat
order  8: has
order  9: none
order 10: --
order 11: lucky
order 12: cat!

If you are using gcc / clang, a good compile string for the code, with full-warnings, would be:

$ gcc -Wall -Wextra -pedantic -Wshadow -Werror -std=c11 -O2 -o sscanfwordsinput2d sscanfwordsinput2d.c

If using Microsoft cl.exe as your compiler (VS, etc..), then a roughly equivalent compile string would be:

$ cl /W3 /wd4996 /Wx /O2 /Fesscanfwordsinput2d /Tcsscanfwordsinput2d.c

Looks things over and let me know if you have questions.

3 Comments

Nice and short, but i'm wondering if we could write (scan) directly into words[nwords] and eliminate an extra string copy. I mean, if the scan fails, nwords will not be incremented and the list of orders will remain valid.
Sure, writing directly into words[nwords] is fine. I mention it in the post. I broke word out into a separate variable for readability given the "simple" request and the negligible cost of an additional 64 bytes and a strcpy().
Oh my bad, you mention it in paragraph 3 of your remarks, i must have missed it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.