1

Edit: strtok() proved successful in solving my issue. Thank you Barmar.

I'm still new to C programming, and I have spent several hours now trying to complete a university assignment. What we are supposed to do is take input from a text file containing the availability of food items, and then output those items based on their availability. In essence, the program is supposed to take the name of the file from user input, read the file, store the information into four predefined string arrays, then output the available food items in the format:

name (category) -- description.

The text file has each line ordered as: category, name, description, and availability. The category, name, description, and availability are separated by '\t' characters, which we are supposed to omit.

The issue that I have been having is that I can't seem to figure out how to get the output to print correctly. When I run the program, the output delivers the following mess:

b'Ham sandwich\x04 (Sandwiches\xec\x9dO\x7f) -- Classic ham sandwich\nCaesar salad\x10 (Salads\xff\xff) -- Chunks of romaine heart lettuce dressed with lemon juice\nWater\x96\x19 (Beverages) -- 16oz bottled water\nBeef tacos\x0f\x9eO\x7f (Mexican food) -- Ground beef in freshly made tortillas\n'

I believe the mess to be one of the predefined strings failing to terminate when I print it, almost certainly because it lacks a '\0' character. I also believe that this problem stems from some issue in the way I take each line of text and place each category, name, description, and availability into their respective array. What the true issue is, and how to solve it, is something that I haven't been able to figure out. I have emailed my professor for help, and they said they would get back to me about, but I have yet to get a response from them.

With all that said, here is my current code for the assignment:

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

int main(void) {
   const int MAX_LINES = 25;   //Maximum number of lines in the input text file
   const int MAX_STRING_LENGTH = 100;  //Maximum number of characters in each column of the input text file
   const int MAX_LINE_LENGTH = 200;  //Maximum number of characters in each line of the input text file 
   
   // Declare 4 string arrays to store the 4 columns from the input text file
   char column1[MAX_LINES][MAX_STRING_LENGTH];
   char column2[MAX_LINES][MAX_STRING_LENGTH];
   char column3[MAX_LINES][MAX_STRING_LENGTH];
   char column4[MAX_LINES][MAX_STRING_LENGTH];
   
   //get name of file, then open it
   FILE* inputFile;
   char fileName[MAX_STRING_LENGTH];
   scanf("%s", fileName);
   inputFile = fopen(fileName, "r");
   
   //string variable to take in each line
   //int variable to keep track of / count each line for the column arrays
   char fileLine[MAX_LINE_LENGTH];
   int countLine = 0;
   
   //when skipping the '\t' character, remember that there is also a null character
   //being skipped over in the array being copied too.
   
   while(!feof(inputFile)){
      fgets(fileLine, MAX_LINE_LENGTH, inputFile);
      
      if(feof(inputFile)){ //I have found that if I don't check for end-of-file again, 
         break;            //it would duplicate the last line.
      }
      
      int i = 0;
      int k = 0;
      int tabCount = 0; //tabCount to account for skipping over \t characters
      //while loop to take file input and put it in respective columns
      while( i < strlen(fileLine) ){
         if( fileLine[i] == '\t' ){
            k = 0;
            i++;
            tabCount++;
         }
         ///////////////////////////////////////////////
         if( tabCount == 0 ){
            
            column1[countLine][k-tabCount] = fileLine[i];
            
         } else if( tabCount == 1 ){
            
            if( !(k >= 1) ){
               k = 1;
            }
            column2[countLine][k-tabCount] = fileLine[i];
            
         } else if( tabCount == 2 ){
            
            if( !(k >= 2) ){
               k = 2;
            }
            column3[countLine][k-tabCount] = fileLine[i];
            
         } else if( tabCount == 3 ){
            
            if( !(k >= 3) ){
               k = 3;
            }
            column4[countLine][k-tabCount] = fileLine[i];
            
         }
         ///////////////////////////////////////////////
         
         i++;
         k++;
      }
      
      countLine++;
   }
   
   /* Attempt at adding NULL character to strings */
   /////////////////////////////////////////////////
   /*for(int i = 0; i < countLine; i++){
      
      if( ( column1[i][strlen(column1[i])]) != '\0'){
         column1[i][strlen(column1[i])] = '\0';
      }
      
      if( ( column2[i][strlen(column2[i])]) != '\0'){
         column2[i][strlen(column2[i])] = '\0';
      }
      
      if( ( column3[i][strlen(column3[i])]) != '\0'){
         column3[i][strlen(column3[i])] = '\0';
      }
      
   }*/
   /////////////////////////////////////////////////
   
   /* Second attempt at adding NULL character to strings */
   /////////////////////////////////////////////////
   /*for(int i = 0; i < countLine; i++){
      for(int k = 0; k < strlen(column1[i]); k++){
         if(column1[i][k] == '\0'){
            column1[i][k] = ' ';
         }
      }
      
      for(int k = 0; k < strlen(column2[i]); k++){
         if(column2[i][k] == '\0'){
            column2[i][k] = ' ';
         }
      }
      
      for(int k = 0; k < strlen(column3[i]); k++){
         if(column3[i][k] == '\0'){
            column3[i][k] = ' ';
         }
      }
      
   }*/
   /////////////////////////////////////////////////
   
   //output
   int b = 0;
   while( b < countLine ){
      if( strcmp(column4[b], "Available\n") == 0 ){
         printf("%s (%s) -- %s\n", column2[b], column1[b], column3[b]);
      }
      
      b++;
   }
   
   fclose(inputFile);

   return 0;
}

This is the initial txt file:

Sandwiches  Ham sandwich    Classic ham sandwich    Available
Sandwiches  Chicken salad sandwich  Chicken salad sandwich  Not available
Sandwiches  Cheeseburger    Classic cheeseburger    Not available
Salads  Caesar salad    Chunks of romaine heart lettuce dressed with lemon juice    Available
Salads  Asian salad Mixed greens with ginger dressing, sprinkled with sesame    Not available
Beverages   Water   16oz bottled water  Available
Beverages   Coca-Cola   16oz Coca-Cola  Not available
Mexican food    Chicken tacos   Grilled chicken breast in freshly made tortillas    Not available
Mexican food    Beef tacos  Ground beef in freshly made tortillas   Available
Vegetarian  Avocado sandwich    Sliced avocado with fruity spread   Not available

I would greatly appreciate any help.

Rob
  • 14,746
  • 28
  • 47
  • 65
Phoinexx
  • 11
  • 4
  • Have you tried running your code line-by-line in a debugger while monitoring the control flow and the values of all variables, in order to determine in which line your program stops behaving as intended? If you did not try this, then you may want to read this: [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173/12149471) When running the program line by line using a debugger, you can also inspect the values of all array elements, to determine whether the strings are properly terminated by a null character. – Andreas Wenzel Mar 03 '23 at 22:07
  • _Side note:_ You've got "parallel" arrays: `column1, column2, column3, column4`. Whenever I see that I want to combine them: `char columns[MAX_COLUMNS][MAX_LINES][MAX_STRING_LENGTH];` This would be easier to see if there weren't just 4 columns, but (e.g.) 50. With the combined array, you can reduce the number of separate `if` statements and `for` loops. – Craig Estey Mar 03 '23 at 22:07
  • 1
    Side note: [Why is “while( !feof(file) )” always wrong?](https://stackoverflow.com/questions/5431941/why-is-while-feoffile-always-wrong) I suggest that you move the `fgets` line into the while loop condition, like this: `while ( fgets(fileLine, MAX_LINE_LENGTH, inputFile) != NULL )`. That way, you can remove both calls to `feof`. – Andreas Wenzel Mar 03 '23 at 22:13
  • C has a built-in function for splitting a string using a delimiter character: `strtok()`. – Barmar Mar 03 '23 at 22:23
  • There are numerous improved ways to parse those tab-delimited strings. `strcspn()` or `strtok()` would find the boundaries more cleanly and clearly. `strncat()`, `strncpy()`, or `strcpy()` would all, under various different circumstances, serve to copy the data from the buffer. Or you could write your own function to copy a tab-terminated field. – John Bollinger Mar 03 '23 at 22:24
  • 1
    But the basic answer is that whenever you get to `\t` or `\n` you need to append `'\0'` to whichever column you were just copying to. – Barmar Mar 03 '23 at 22:26
  • Also, you appear to be outputting only the items marked as available. Do you then need to store the data in those arrays at all? What about checking whether each item is available as you read it in, and writing the available ones back out (in appropriate format) *then and there*, without storing anything for later? – John Bollinger Mar 03 '23 at 22:28
  • @Barmar I tried out the 'strtok()' function that you recommended. Did the trick almost immediately. Thank you. Wish I had learned of this sooner in class. For everyone else who left a comment, I will be sure to implement your suggestions in my future code. – Phoinexx Mar 03 '23 at 23:16
  • @Barmar OP has indicated your comment solved their problem. For future users who come through, and to be awarded your deserved reputation, please make an answer with your solution and an explanation. After which Phoinexx, if you could so kindly accept it. – Edward Severinsen Mar 04 '23 at 12:57

1 Answers1

0

This was my adjusted loop, which solved my problem:

while( fgets(fileLine, MAX_LINE_LENGTH, inputFile) != NULL){
  
  token = strtok(fileLine, delimiter);
  strcpy(column1[countLine], token);
  
  token = strtok(NULL, delimiter);
  strcpy(column2[countLine], token);
  
  token = strtok(NULL, delimiter);
  strcpy(column3[countLine], token);
  
  token = strtok(NULL, delimiter);
  strcpy(column4[countLine], token);
  
  if( strcmp(column4[countLine], "Available\n") == 0){
     printf("%s (%s) -- %s\n", column2[countLine], column1[countLine], column3[countLine]);
  }
  
  countLine++;
}

The delimiter variable is a constant and contains the '\t' character. The token variable is a character pointer. Both were declared before entering the while loop.

For the purposes of the assignment, the four column arrays were kept, instead of being condensed down into a single array as suggested by Craig Estey.

Phoinexx
  • 11
  • 4