I am currently learning about to how to data structures in C and I need a little help. I am supposed to take information about classes from a .txt file and store the information in a data structure; but I am having trouble doing so. I am also sure that I am also screwing up a lot of other things in my program, so feel free to bash on my program and tell me what I am doing wrong so I can learn from my mistakes.
Here is one line of information that I am trying to store:
M273 Multivariable Calculus :MWF 0900-0950 2
where the first part is the course number, the second part is the course name, the third part is the days and time the course is available and the last number represents what year you should be in to take the course (2 translates to sophomore).
Below is my code:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define EMAX 250
typedef struct class{
char *classNumber[EMAX];
char *className[EMAX];
char *classTime[EMAX];
char *classStanding[EMAX];
}class;
void menu(class*info, char buffer[], FILE*file);
void setInformation(class*info, char buffer[], FILE*file);
int main(void)
{
class info[EMAX];
char buffer[EMAX];
File *file;
setInformation(info, buffer, file);
menu(info, buffer, file);
return(0);
}
void menu(class*info, char buffer[], FILE*file)
{
int user_input=0;
do {
printf("\nSelect one of the following options: \n");
printf("1) Print all information about all classes in order of the class number\n");
printf("5) Quit\n");
scanf("%d", &user_input);
if(user_input==1)
{
//getInformation(info, buffer, file);
}
}while(user_input!=5);
}
void setInformation(class*info, char buffer[], FILE*file)
{
size_t count = 0;
char line[50];
char *token;
file = fopen("classes.txt", "r");
while(fgets(line, sizeof(line), file)!=NULL)
{
token=strtok(line, " "); //Only gets the course number
strncpy(info[count].classNumber, token, strlen(token));
count++;
}
fclose(file);
}
As you can tell, I can only extract the course number with this code. I would prefer to store all the data in one while loop and I have tried to extract more information by adding another token to stop as soon as it reaches the ":" before the day and time but I can't figure out how to get it to work. I also get a lot of warnings when I compile this, so I welcome any advice to help my trash code. I appreciate any help
*scanf() is great:
#include <stdlib.h>
#include <stdio.h>
#define EMAX 250
#define STRING(X) #X
#define STRINGIFY(X) STRING(X)
typedef struct class_tag {
char classNumber[EMAX + 1];
char className[EMAX + 1];
char classTime[EMAX + 1];
char classStanding[EMAX + 1];
char foo[EMAX + 1];
} class;
int main(void)
{
char const *input_filename = "test.txt";
FILE *input = fopen(input_filename, "r");
if (!input) {
fprintf(stderr, "Couldn't open \"%s\" for reading :(\n\n", input_filename);
return EXIT_FAILURE;
}
class c;
class *classes = NULL;
size_t classes_size = 0;
while (fscanf(input, "%"STRINGIFY(EMAX)"s %"STRINGIFY(EMAX)"[^:] %"STRINGIFY(EMAX)"s "
"%"STRINGIFY(EMAX)"s %"STRINGIFY(EMAX)"s",
c.classNumber, c.className, c.classTime, c.classStanding, c.foo) == 5)
{
class *tmp = realloc(classes, ++classes_size * sizeof(*classes));
if (!tmp) {
fputs("Not enough memory :(\n\n", stderr);
fclose(input);
free(classes);
return EXIT_FAILURE;
}
classes = tmp;
classes[classes_size - 1] = c;
}
fclose(input);
for (size_t i = 0; i < classes_size; ++i)
printf("%s %s %s %s\n", classes[i].classNumber, classes[i].className, classes[i].classTime, classes[i].classStanding);
free(classes);
}
Related
Suppose I have a text file such as:
Adam: Tall Handsome Kind Athlete
He enjoys playing basketball
Sabrina: Short Pretty Funny Adorable Gymnast
She loves gymnastics
Sinclair: Blonde
He is blonde
Assume the file has several more people, each with a Name and then 0 or more characteristics about them
and then a new line with a tab following a sentence underneath.
For example,
Adam: would be the name
Tall Handsome Kind Athlete would be 4 individual characteristics
He enjoys playing basketball would be the sentence.
I want to store this information in a structure like so:
typedef struct People {
char *name;
char **characteristics;
char *sentence;
} People;
typedef struct List {
People **list;
int total_ppl;
} List;
int main (void) {
List *ppl_list = malloc(sizeof(List));
ppl_list->list = malloc(sizeof(People));
int i = 0;
FILE *pf = fopen("input.txt", "r");
if (pf == NULL) {
printf("Unable to open the file");
} else {
/* I'm not sure how to go about reading the information from here. I was thinking about using
fscanf but I don't know how to separate and store the Name, each Characteristic, and
the sentence separately. I know I will need to realloc ppl_list as more people are read from the
file. If I should change my structs to organize it better, please let me know.
*/
}
}
This answer is maybe not complete but it will help you, at least, with the processing of the lines in the text file.
Assuming a file.txt as the input file, and with the following format
Adam: Tall Handsome Kind Athlete
He enjoys playing basketball
Sabrina: Short Pretty Funny Adorable Gymnast
She loves gymnastics
Sinclair: Blonde
He is blonde
We can process this file as follows
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/*process_info_line: process the line with the name and attributes*/
int process_info_line(char *line)
{
char *next = NULL;
char *part = strtok_r(line, ":", &next);
if (part)
printf("NAME: %s\n", part);
else
return 0;
while (part != NULL) {
part = strtok_r(NULL, " ", &next);
if (part)
printf("ATTRIBUTE: %s\n", part);
}
return 0;
}
/*process_sentence: process the line with the sentence*/
char *process_sentence(char *line)
{
line = line + 4;
return line;
}
/*is_sentence: checks if the line is a sentence, given your definition
* with a tab(or 4 spaces) at the begining*/
int is_sentence(char *line)
{
if (strlen(line) == 0)
return 0;
char *ptr = line;
int space_count = 0;
while (ptr != NULL) {
if (strncasecmp(ptr, " ", 1) != 0) {
break;
}
space_count++;
ptr++;
}
if (space_count == 4)
return 1;
return 0;
}
/*scan_file: read each of the lines of the file and use
* the previous functions to process it.*/
int scan_file(char *filename)
{
char *line_buf = NULL;
size_t line_buf_size = 0;
ssize_t line_size;
int line_count = 0;
FILE *fp = fopen(filename, "r");
if (!fp) {
fprintf(stderr, "Error opening file '%s'\n", filename);
return 1;
}
/*Get the first line of the file*/
line_size = getline(&line_buf, &line_buf_size, fp);
while (line_size >= 0)
{
line_count++;
line_buf[line_size-1] = '\0'; /*removing '\n' */
if (is_sentence(line_buf)) {
printf("SENTENCE: %s\n", process_sentence(line_buf));
} else {
process_info_line(line_buf);
}
line_size = getline(&line_buf, &line_buf_size,fp);
}
// don't forget to free the line_buf used by getline
free(line_buf);
line_buf = NULL;
fclose(fp);
return 0;
}
int main(void)
{
scan_file("file.txt");
return 0;
}
This will generate the following output
NAME: Adam
ATTRIBUTE: Tall
ATTRIBUTE: Handsome
ATTRIBUTE: Kind
ATTRIBUTE: Athlete
SENTENCE: He enjoys playing basketball
NAME: Sabrina
ATTRIBUTE: Short
ATTRIBUTE: Pretty
ATTRIBUTE: Funny
ATTRIBUTE: Adorable
ATTRIBUTE: Gymnast
SENTENCE: She loves gymnastics
NAME: Sinclair
ATTRIBUTE: Blonde
SENTENCE: He is blonde
There is a function called strtok(): https://www.tutorialspoint.com/c_standard_library/c_function_strtok.htm
Though I've never used it, the one time I had to do this I implemented a function that would separate a string into an array of pointers to pointers of chars and dynamically allocated memory for the whole block, it would look for commas and generate a new string each time it found one, my code wouldn't work in your case because it was written to specifically ignore withe spaces, though if you're not ignoring them but using them as delimitators the code gets a lot simpler.
Edit: as for getting the info out of the file I would create a buffer of an absurd size like say 32768 Bytes and use fgets(buffer, 32768, pf), though you may wanna add a check to see if even 32K chars weren't enough for the read and to deal with that, though I imagine it wouldn't be necessary.
Also this were the prototypes of the functions i implemented once, to give you a better idea of what you'd have to code:
char **separete_string (char *string, char delim);
void free_string_list (char **list);
#define LINES 4
#define LENGHT 30
typedef struct
{
char *name;
char *phoneNumber;
char *location;
char *traveltype;
} Client;
int main(int argc, char *argv[])
{
char *filename = argv[1];
char clientData[LINES][LENGHT];
readClientData(filename, clientData);
/* How to do this client struct */
Client *client = createClient(clientData[0], clientData[1], clientData[2], clientData[3]);
return 0;
}
void readClientData(char *filename, char clientData[LINES][LENGHT])
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
perror("Error while opening file!");
exit(1);
}
char line[LENGHT];
int i = 0;
while (fgets(line, LENGHT, file) != NULL)
clientData[i] = line; /* How to do this */
i++;
fclose(file);
}
Coming from a pythonista world I'm a bit confused how things are working here. It is clear that I can't just add lines to the list, nor can acces the lines the way I do to initialize the client struct.
Maybe I should just use a char *clientData[], but don't know how.
To copy C strings, you need to use:
char * strcpy ( char * destination, const char * source );
from the Standard C String Library, string.h.
However, notice, than unlike Python (your background), indentation does not define the body of the loop, you need to use curly brackets!
So, you need to do this:
while (fgets(line, LENGHT, file) != NULL)
{
strcpy(clientData[i], line);
i++;
}
Without the curly brackets, only the first immediate line of code will be considered to be the body of the loop. So, in the example above, the body of the loop - if we did not use curly brackets - would be only the call to the method for copying strings, and the increment of the counter would not be part of the loop!
You need to define, or just declare a method before using it, which you didn't do in your example.
Since you only need one client, you do not need a pointer, you can simply create one, by doing Client client;.
In order to keep it simple, use the knowledge you gathered from reading this answer so far, and do define the maximum length of the strings (that is the size of the arrays of characters every field of your struct gets to have). Remember, that C strings have to be NULL terminated, so, the maximum length of them is actually LENGTH - 1.
If you keep the fields of the struct as pointers to char, then you'd need to dynamically allocate memory, or set the pointer point to corresponding string of the clientData array (similarly to what you did for the file name). I suggest you get some experience in C first, and then try implementing these two approaches.
Now, you are ready to do:
strcpy(client.name, clientData[0]);
...
strcpy(client.traveltype, clientData[3]);
Complete working example:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LINES 4
#define LENGTH 30
typedef struct
{
char name[LENGTH];
char phoneNumber[LENGTH];
char location[LENGTH];
char traveltype[LENGTH];
} Client;
void readClientData(char *filename, char clientData[LINES][LENGTH]);
void printClient(Client client);
int main(int argc, char *argv[])
{
char *filename = NULL;
if(argc > 1)
filename = argv[1];
else
{
printf("Usage: %s <filename>\n", argv[0]);
}
char clientData[LINES][LENGTH];
readClientData(filename, clientData);
Client client;
strcpy(client.name, clientData[0]);
strcpy(client.phoneNumber, clientData[1]);
strcpy(client.location, clientData[2]);
strcpy(client.traveltype, clientData[3]);
printClient(client);
return 0;
}
void readClientData(char *filename, char clientData[LINES][LENGTH])
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
perror("Error while opening file!");
exit(1);
}
char line[LENGTH];
int i = 0;
while (fgets(line, LENGTH, file) != NULL)
{
line[strcspn(line, "\n")] = 0;
strcpy(clientData[i], line);
i++;
}
fclose(file);
}
void printClient(Client client)
{
printf("%s, %s, %s, %s\n", client.name, client.phoneNumber, client.location, client.traveltype);
}
Output:
Georgioss-MBP:Desktop gsamaras$ cat test.txt
MegasAlexandros
3335632320
Greece
Cosmos
Georgioss-MBP:Desktop gsamaras$ gcc main.c
Georgioss-MBP:Desktop gsamaras$ ./a.out test.txt
MegasAlexandros, 3335632320, Greece, Cosmos
PS: I used this in my example: Removing trailing newline character from fgets() input
I am a beginner in c so I have a problem with get the user to input last name, a comma & then first name. However it will pass to the function call
int get_name(FILE *fp)
in my main function. I have a problem either if I have to use the arguments parameters.
Example, main (int argc, char *argv[])) or just main (void))
and from what I have been searching so far, FILE*fp cannot get the user to enter from stdin it only use to open the file(?) BUT I am required to get the user to input from keyboard and pass to the function. I have written some codes. but they don't seem to work but I am going to put down on here the one I am sure that I need a few changes most.
#define LINESIZE1024
int main(void){
FILE *fp;
char line[LINESIZE];
char first;
char last;
char comma;
while(1){
if(!fgets(line,LINESIZE,stdin)){
clearerr(stdin);
break;
}
if(fp = (sscanf(line,"%s %s %s",&last,&comma,&first)==3))
get_name(fp);
if(get_last_first(fp)== -1)
break;
printf("Please enter first name a comma and then last name");
}
BUT I got an error saying I can't use pass it from pointer to an integer. and many MORE but I accidentally closed my concolse and all the errors that appeared while I was trying to fix are gone. So please give me some ideas.
What about seconde code
while(1){
if(!fgets(line,LINESIZE,fp)){
clearerr(stdin);
break;
}
if(sscanf(line,"%s %s %s",last,comma,first)==3)
get_last_first(fp);
return 0;
}
It gave me errors too. fp,last,first,comma used uninitialized in this function
OK so I think I have fixed the previous problem now. However it doesn't print the name back if the name is given correctly. Here is my fixed main code.
int main(void){
FILE *fp = stdin;
char line[LINESIZE];
char first[16];
char last[16];
while(1){
if(!fgets(line,LINESIZE,stdin)){
clearerr(stdin);
break;
}
if(sscanf(line,"%s ,%s",last,first)==2)
if(get_name(fp)==2)
printf("Your name is: %s %s\n", first, last);
}
return 0;
}
here is my function.
int get_name(FILE *fp){
char line[LINESIZE];
char last[16], first[16];
int n;
/* returns -1 if the input is not in the correct format
or the name is not valid */
if(fgets(line, LINESIZE, fp) == NULL) {
return -1;
}
/* returns 0 on EOF */
if((n = sscanf(line, " %[a-zA-Z-] , %[a-zA-Z-]", last, first)) == EOF) {
return 0;
}
/* prints the name if it's valid */
if((n = sscanf(line, " %[a-zA-Z-] , %[a-zA-Z-]", last, first)) == 2) {
return 2;
}
return 1;
}
I thank you people so much for taking time to read and help me. Please don't be mean :)
Seems that you are making it more complicated than needed. Don't call fgets and scanf in main. Only do that in the function get_name.
It can be something like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LINESIZE 1024
int get_name(FILE *fp)
{
char line[LINESIZE];
char* t;
if(!fgets(line, LINESIZE,fp))
{
printf("Error reading input\n");
return 0;
}
t = strstr(line, ",");
if (t)
{
*t = '\0';
++t;
printf("First: %s - Last: %s\n", line, t);
return 2;
}
printf("Illegal input\n");
return 0;
}
int main(int argc, char **argv)
{
get_name(stdin);
return 0;
}
If you later decide that you want to read from a file, you can reuse the function get_name without changing it at all. All you need is to change main. Like:
int main(int argc, char **argv)
{
FILE* f = fopen("test.txt", "r");
if (f)
{
get_name(f);
fclose(f);
}
else
{
printf("Open file failed\n");
}
return 0;
}
If you want to read from the keyboard, read from stdin or use scanf, which internally reads from stdin. If you want to read from a file instead, use FILE *fp, but don't forget to open the file and check if it was successful (you'll find lots of tutorials for this).
Further, when reading in strings, you need an array of characters, not a single one. Note further, that scanf can already deal with formats like "everything that is not a ',' then a ',' then a string. Note that format "[^,]" means "any character except a ',':
So you could adapt the code as follows:
#define LINESIZE 1024
int main(void){
char line[LINESIZE];
char first[LINESIZE];
char last[LINESIZE];
while(fgets(line,LINESIZE,stdin)) {
if(sscanf(line,"%[^,],%s",last,first)==2) {
printf("Read in %s ... %s\n",last,first);
}
else {
printf("Please enter first name a comma and then last name");
}
}
return 0;
}
And if your professor is picky concerning the "use FILE*", you could write:
FILE *fp = stdin;
...
while(fgets(line,LINESIZE,fp)) {
...
The purpose of the program is to read a text file that contains a list of 55 authors and titles of books. The format of the list goes (author name, booktitle). I can use malloc, strlen, strtok, and strcopy. So far I got the program to read out the names of the authors but I am stuck on how to get the program to read the titles of the books.How would I get the program to read the titles of the books from the text file? I know that there are errors in this code so please be kind .
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void loadBookName(char* filename, char* authorName[55], char* bookName[55]);
int main(int argc, char* argv[])
{
//Create two arrays each with length 55
char* authorName[55];
char* bookName[55];
//Ask the user for the name of the file
char fileName[30];
//Insert your code here
printf("Please enter the name of the file\n");
scanf("%s", fileName);
//Call the method loadBookName
loadBookName(fileName, authorName, bookName);
return 0;
//Print the two arrays to test if the two arrays were correctly loaded with the data
int i = 0;
printf("%-30s%-40s\n", "Author", "Book");
for (i = 0; i < 55; i++) {
printf("%-30s%-40s\n", authorName[i], bookName[i]);
}
}
/*
loadBookName method
This method is responsible for:
1. Take a file containing a book name and the author name as input
2. Open the file
3. Read the information in the file and store it in two arrays: authorName, bookName
4. Return the two arrays to the main method.
*/
void loadBookName(char* filename, char* authorName[55], char* bookName[55])
{
int i;
char string_array[80];
const char comma[2] = ",";
//Open the file
FILE *fp;
fp = fopen(filename, "r");
if (fp == NULL)
{
printf("Failed to open file\n");
exit(1);
}
for (i=0; i<55; i++)
{
fgets(string_array, 80, fp);
authorName[i] = strtok(string_array, comma);
printf("%s\n", *authorName);
}
//Close the file
fclose(fp);
}
when I run the program in terminal it asks me to enter the filename (books.txt). Then when I enter the file name, the program prints a list of 55 authors.
I don't have a compiler in front of me, so excuse the compilation error if it has any. But I think you can try something as below, within your existing code:
UPDATED:
After comments, I've updated one line. This is compiled and working.
Assumptions: User need to take care of error-handling e.g. file not present, file unable to open, buffer overflows etc.
char *token;
for (i=0; i<55; i++)
{
//fgets(string_array, 80, fp);
//This will take care in case if lines are less than 55
if(!fgets(string_array, 80, fp))
break;
//Get the author
token = strtokstring_array, comma);
authorName[i] = token; // or use string copy functions
//Get book name
while( token != NULL )
{
printf( " %s\n", token ); //this shall print author name
token = strtok(NULL, comma);
bookName[i] = token;
printf( " %s\n", token ); //this shall print book name
//EDIT: This is additional line after suggestions
token = strtok(NULL, comma);
}
}
Simple way of separating strings with strlcpy:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main(void )
{
size_t i = 1;
char *authorName, *bookName;
const char *a_line_in_a_file =
"Lewis Carroll,The Hunting of the Snark";
const char *title = a_line_in_a_file;
while ( *title != ','){
title++;
i++;}
authorName = malloc(i);
bookName = malloc(strlen(title));
title++;
#if __BSD_VISIBLE
strlcpy(bookName, title, strlen(title) + 1);
strlcpy(authorName, a_line_in_a_file, i);
#else
snprintf(bookName, strlen(title) + 1, "%s", title);
snprintf(authorName, i, "%s", a_line_in_a_file);
#endif
printf("%-30s%-40s\n", authorName, bookName);
free(authorName);
free(bookName);
return 0;
}
I have an array in a struct. I'm reading from a file into a string. I use strtok to get the first few characters, and i want to pass the rest of the line into the struct, to eventually be passed into a thread. I'm getting the following error:
incompatible types when assigning to type char[1024] from type char *
Referring to the line indicated below with the comments. It probably has to do with how i'm trying to copy character arrays, but i'm not sure on a better way.
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <linux/input.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
typedef struct
{
int period; //stores the total period of the thread
int priority; // stores the priority
char pline[1024]; // stores entire line of text to be sorted in function.
}PeriodicThreadContents;
int main(int argc, char* argv[])
{
//opening file, and testing for success
//file must be in test folder
FILE *fp;
fp = fopen("../test/Input.txt", "r");
if (fp == NULL)
{
fprintf(stderr, "Can't open input file in.list!\n");
exit(1);
}
char line[1024];
fgets(line, sizeof(line), fp);
//getting first line of text, containing
char *task_count_read = strtok(line," /n");
char *duration_read = strtok(NULL, " /n");
//converting char's to integers
int task_count = atoi(task_count_read);
int i = 0;
PeriodicThreadContents pcontents;
printf("started threads \n");
while (i < task_count)
{
fgets(line, sizeof (line), fp);
strtok(line," ");
if (line[0] == 'P')
{
char *period_read = strtok(NULL, " ");
pcontents.period = atoi(period_read);
printf("%d",pcontents.period);
printf("\n");
char *priority_read = strtok(NULL, " ");
pcontents.priority = atoi(priority_read);
printf("%d",pcontents.priority);
printf("\n");
printf("\n%s",line);
memcpy(&(pcontents.pline[0]),&line,1024);
printf("%s",pcontents.pline);
}
}
return 0;
}
C cannot handle strings as other languages do. C doesn't have string assignments or comparisons without using auxiliary functions.
In order to copy a string in a buffer you can use:
strcpy(pcontents.pline, line);
Or even (to have a warranty that your string is not longer than 1024 bytes):
memcpy(pcontents.pline, line, 1024);
pcontents.pline[1023] = '\0';
For other string operations check: http://www.gnu.org/software/libc/manual/html_node/String-and-Array-Utilities.html#String-and-Array-Utilities
You need to copy the chars from the buffer into pcontents.pline (assuming pcontents is a PeriodicThreadContents).
strcpy(pcontents.pline, strtok(NULL, " "));