I am trying to read a text file I made into a linked list, the text file looks like this:
around 1 2 1
bread 2 4 3 5 1
four 1 3 2
head 3 1 2 2 1 5 1
has 2 3 1 5 2
Where the first string of each line are just words from a paragraph. The first number after the word is the number of lines the word was found in, in the paragraph. Then the following numbers are pairs of (line, occurrences) in the paragraph.
For example, for the word bread:
It was found in 2 lines in the paragraph. In the first line, line 4, it was found 3 times. Then in the second line, line 5, it was found 1 time.
I am trying to create a linked list from this text file, my program looks like this so far:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#define MAXWORD 999
typedef struct node node_t;
struct node {
char *word;
int num_lines;
int paragraph;
int freq;
node_t *next;
};
int
main(int argc, char *argv[]) {
FILE *fp;
char word[MAXWORD+1];
int ch, line_count = 0, len = 0;
node_t *node = (node_t*)malloc(sizeof(*node));
node_t *curr, *prev;
fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "Error reading file\n");
exit(EXIT_FAILURE);
}
/* Just trying to store the string so far */
while ((ch = getc(fp)) != EOF) {
if (ch == '\n') {
line_count++;
strcpy(node->word, word);
}
if (isalpha(ch)) {
word[len] = ch;
len++;
word[len] = '\0';
}
if (isdigit(ch)) {
len = 0;
}
}
printf("line count = %d", line_count);
free(node)
fclose(fp);
return 0;
}
In this snippet, I have been trying to store the string in the linked list data structure, but I have not yet used dynamic arrays to store the numbers after the word which occur in the text file. I know I will need to build this data structure using malloc() and realloc(), but I am unsure of how to do this.
How should I do this?
My desired output would look like this:
There are five words in the text file,
and 9 pairs of (line, occurences)
Word: pairs
"around": 2,1
"bread": 4,3; 5,1
"four": 3,2
"head": 1,2; 2,1; 5,1
"has": 3,1; 5,2
UPDATE
I have been researching this and it seems to be very similar to the inverted index problem, where I have seen that using a binary search tree would be best.
Could I implement my binary search tree like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#define MAXWORD 999
typedef char word_t[MAXWORD+1];
typedef struct node node_t;
struct node {
void *data;
int *ints;
node_t *rght;
node_t *left;
};
typedef struct {
node_t *root;
int (*cmp)(void*, void*);
} tree_t;
int
main(int argc, char *argv[]) {
FILE *fp;
fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "Error reading file\n");
exit(EXIT_FAILURE);
}
while ((ch = getc(fp)) != EOF) {
if (ch == '\n') {
line_count++;
}
}
fclose(fp);
return 0;
}
You could do something like this:
typedef struct {
int paragraph;
int freq;
} stats_t;
struct node {
char *word;
int num_lines;
stats_t *stats;
node_t *next;
};
Then after you parse the string you can do:
ps = calloc(line_count, sizeof(stats_t));
to get a pointer to an array of stats_t structs, which you can fill with line locations and frequencies. Then you can store the pointer ps in your node struct.
I wrote a program that does what I think you are looking for. I modified the structs I was thinking about before:
typedef node node_t;
struct node {
char *word;
int num_lines;
int *location;
int *frequency;
node_t *next;
};
This way the nodes contain pointers to arrays of int to store the location and frequency information. Nodes and storage for the word strings, location arrays, and frequency arrays are all dynamically allocated. Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAXLINE 1000
#define MAXWORD 30
typedef struct node node_t;
struct node {
char *word;
int num_lines;
int *location;
int *frequency;
node_t *next;
};
void strip(char *pln);
void normalize_word(char *pstr);
struct node * update_word(char *pwd, int lnum, struct node *phead);
struct node * find_in_list(char *pwd, struct node *phead);
int find_line_pair(int lnum, struct node *pwn);
int list_len(struct node *phead);
int num_pairs(struct node *phead);
int main(int argc, char *argv[])
{
FILE *fp;
struct node *head, *current;
char *pline, *pword;
char line[MAXLINE + 1];
char word[MAXWORD + 1];
int i, n, line_count = 0;
head = NULL;
if (argc < 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
exit(EXIT_FAILURE);
} else {
if ((fp = fopen(argv[1], "r")) == NULL) {
fprintf(stderr, "Unable to open file %s\n", argv[1]);
exit(EXIT_FAILURE);
}
}
/* Read in lines and process words */
pline = line;
pword = word;
while (fgets(pline, MAXLINE, fp) != NULL) {
++line_count;
strip(pline);
while ((pword = strtok(pline, " ")) != NULL) {
normalize_word(pword);
if (*pword != '\0') // don't add empty words
head = update_word(pword, line_count, head);
pline = NULL;
}
pline = line;
}
/* Display list contents */
printf("There are %d words in the text file,\n",
list_len(head));
printf("and %d pairs of (line, occurrences)\n",
num_pairs(head));
printf("Word: pairs\n");
current = head;
while (current != NULL) {
n = current->num_lines;
printf("%s:", current->word);
for (i = 0; i < n; i++) {
printf(" %d, %d;",
current->location[i], current->frequency[i]);
}
putchar('\n');
current = current->next;
}
/* Cleanup */
// close file
if (fclose(fp) != 0)
fprintf(stderr, "Error closing file %s\n", argv[1]);
// free all allocated memory
current = head;
while (current != NULL) {
free(current->word);
free(current->location);
free(current->frequency);
current = current->next;
free(head);
head = current;
}
return 0;
}
/* Remove trailing newlines */
void strip(char *pln)
{
while (*pln != '\0') {
if (*pln == '\n')
*pln = '\0';
++pln;
}
}
/* Convert word to lowercase and remove trailing
* non-alphanumeric characters */
void normalize_word(char *pstr)
{
int i = 0;
char ch;
while ((ch = pstr[i]) != '\0') {
pstr[i] = tolower(ch);
++i;
}
while ((--i >= 0) && !isalnum(pstr[i])) {
pstr[i] = '\0';
continue;
}
}
/* Update existing word node or create a new one, and return
* a pointer to the head of the list */
struct node * update_word(char *pwd, int lnum, struct node *phead)
{
struct node *found, *newnode;
char *pword;
int *ploc, *pfreq;
int index;
/* Modify existing node if word is in list */
if ((found = find_in_list(pwd, phead)) != NULL) {
// add new (location, freq) pair if word not in found line
if ((index = find_line_pair(lnum, found)) == -1) {
index = found->num_lines; // index for new pair
found->num_lines += 1; // increment number of lines
ploc = realloc(found->location, (index + 1) * sizeof(int));
pfreq = realloc(found->frequency, (index + 1) * sizeof(int));
ploc[index] = lnum; // new location
pfreq[index] = 1; // found once in this line so far
found->location = ploc; // point to new location array
found->frequency = pfreq; // point to new frequency array
}
else { // update frequency in existing line
found->frequency[index] += 1;
}
/* Set up a new node */
} else {
// allocate memory for new node
newnode = malloc(sizeof(struct node));
// allocate memory for string pointed to from node
pword = malloc((strlen (pwd) + 1) * sizeof(char));
strcpy(pword, pwd);
newnode->word = pword; // set word pointer
newnode->num_lines = 1; // only one line so far
ploc = malloc(sizeof(int));
pfreq = malloc(sizeof(int));
*ploc = lnum; // location was passed by caller
*pfreq = 1; // only one occurrence so far
newnode->location = ploc;
newnode->frequency = pfreq;
if (phead == NULL) { // if wordlist is empty
newnode->next = NULL; // only/last link in the list
phead = newnode; // newnode is the head
} else {
newnode->next = phead; // insert newnode at front of list
phead = newnode;
}
}
return phead;
}
/* Return pointer to node containing word, or NULL */
struct node * find_in_list(char *pwd, struct node *phead)
{
struct node *current = phead;
while (current != NULL) {
if (strcmp(current->word, pwd) == 0)
return current; // word already in list
current = current->next;
}
return NULL; // word not found
}
/* Return index of existing line location, or -1 */
int find_line_pair(int lnum, struct node *pwn)
{
int n = pwn->num_lines;
int index = 0;
while (index < n) {
if (pwn->location[index] == lnum)
return index; // word already found in this line
++index;
}
return -1; // word not yet found in this line
}
/* Find number of nodes in linked list */
int list_len(struct node *phead)
{
int length = 0;
struct node *current = phead;
while (current != NULL) {
++length;
current = current->next;
}
return length;
}
/* Find number of (line, occurrence) pairs */
int num_pairs(struct node *phead)
{
int num = 0;
struct node *current = phead;
while (current != NULL) {
num += current->num_lines;
current = current->next;
}
return num;
}
Note: I modified this from the previous version in the update_word() function. The original code inserted a new node at the end of the list, so the resulting list contained words in order of their first appearance in the input text. This version inserts a new node at the beginning of the list, so the resulting list contains words in reverse order of their first appearance. This speeds up node insertion and simplifies the node-insertion code from:
current = phead;
while (current->next != NULL) // find tail
current = current->next;
current->next = newnode; // add newnode to end
to:
newnode->next = phead; // insert newnode at front of list
I have no doubt that the code can be improved, but this does seem to work. I wouldn't say that this is exactly simple, but relatively straightforward. I ran it against this text file:
Three blind mice. Three blind mice.
See how they run. See how they run.
They all ran after the farmer's wife,
Who cut off their tails with a carving knife,
Did you ever see such a sight in your life,
As three blind mice?
Here are the results:
There are 31 words in the text file,
and 37 pairs of (line, occurrences)
Word: pairs
as: 6, 1;
life: 5, 1;
your: 5, 1;
in: 5, 1;
sight: 5, 1;
such: 5, 1;
ever: 5, 1;
you: 5, 1;
did: 5, 1;
knife: 4, 1;
carving: 4, 1;
a: 4, 1; 5, 1;
with: 4, 1;
tails: 4, 1;
their: 4, 1;
off: 4, 1;
cut: 4, 1;
who: 4, 1;
wife: 3, 1;
farmer's: 3, 1;
the: 3, 1;
after: 3, 1;
ran: 3, 1;
all: 3, 1;
run: 2, 2;
they: 2, 2; 3, 1;
how: 2, 2;
see: 2, 2; 5, 1;
mice: 1, 2; 6, 1;
blind: 1, 2; 6, 1;
three: 1, 2; 6, 1;
Here is my version using Binary Search Tree (BST):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
typedef struct internal_node in_node;
struct internal_node{
int line;
int freq;
in_node* next;
};
struct tree{
char *word;
int num_lines;
in_node* in_nodeptr;
in_node* current;
struct tree* right;
struct tree* left;
};
typedef struct tree* treeptr;
void free_list(in_node* in_nodeptr){
if(in_nodeptr!=NULL) {
free(in_nodeptr);
}
}
void free_bst(treeptr head){
if (head!=NULL) {
free_bst(head->right);
free_bst(head->left);
free_list(head->in_nodeptr);
free(head->word);
free(head);
}
}
void print_list(in_node* in_nodeptr){
while(in_nodeptr!=NULL){
printf("%d %d; ",in_nodeptr->line,in_nodeptr->freq);
in_nodeptr=in_nodeptr->next;
}
}
void print_bst(treeptr head){
if(head!=NULL){
printf("%s: ",head->word);
print_list(head->in_nodeptr);
printf("\n");
print_bst(head->right);
print_bst(head->left);
}
}
void input_to_bst(treeptr* head,char* word,int line){
if((*head)==NULL){
(*head)=(treeptr)malloc(sizeof(struct tree));
(*head)->word=(char*)malloc(50*sizeof(char));
strcpy(((*head)->word),word);
(*head)->num_lines=1;
(*head)->right=NULL;
(*head)->left=NULL;
(*head)->in_nodeptr=(in_node*)malloc(sizeof(in_node));
(*head)->in_nodeptr->line=line;
(*head)->in_nodeptr->freq=1;
(*head)->in_nodeptr->next=NULL;
(*head)->current=(*head)->in_nodeptr;
}
else{
int check=strcmp(((*head)->word),word);
if(check>0) input_to_bst(&((*head)->left),word,line);
else if(check<0) input_to_bst(&((*head)->right),word,line);
else{
if( (*head)->current->line==line) (*head)->current->freq++;
else {
(*head)->current->next=(in_node*)malloc(sizeof(in_node));
(*head)->current->next->line=line;
(*head)->current->next->freq=1;
(*head)->current->next->next=NULL;
}
}
}
}
int main(int argc, char *argv[]) {
treeptr head=NULL;
FILE *fp=fopen(argv[1], "r");
char word[50],ch;
int len=0,lines=1;
if (fp == NULL) {
fprintf(stderr, "Error reading file\n");
exit(1);
}
while ((ch = getc(fp)) != EOF) {
if (ch == '\n') {
word[len]='\0';
if(len>0) input_to_bst(&head,word,lines);
len=0;
lines++;
}
else if (ch==' '){
word[len]='\0';
if(len>0) input_to_bst(&head,word,lines);
len=0;
}
else if (isalpha(ch)){
word[len]=ch;
len++;
}
}
if(len>0) {
word[len]='\0';
input_to_bst(&head,word,lines);
}
print_bst(head);
fclose(fp);
free_bst(head);
return 0;
}
Every word is held as a node of the BST and also each node of BST except from the word, holds a list with all the appearances (lines and frequency ) of the word. In order to be as most efficient as possible we hold a pointer (in_node* current) to the last element of list of appearance so that we don't need to traverses every time we need to add an appearance.
As an example:
Text:
C is an imperative procedural language. It was designed to be compiled
using a relatively straightforward compiler and to require minimal
runtime support.
Output:
C: 1 1;
is: 1 1;
procedural: 1 1;
was: 1 1;
to: 1 1; 2 1;
using: 2 1;
relatively: 2 1;
straightforward: 2 1;
support: 3 1;
require: 2 1;
runtime: 3 1;
language: 1 1;
minimal: 2 1;
an: 1 1;
imperative: 1 1;
designed: 1 1;
be: 1 1;
compiled: 1 1;
compiler: 2 1;
and: 2 1;
It: 1 1;
a: 2 1;
Note that the above implementation is case sensitive for example "And" is different from "and".
If you don't wish to be case sensitive just replace the line word[len]=ch; with word[len]=tolower(ch); and works fine.
The complexity of the above algorithm is O(n^2) which would be the same if you used only linked lists but in the average case BST is O(nlogn) which is much better than linked lists and this is the reason that it is considered to be the better.
Also note that since we must keep a list for appearances of each word the complexity would be worst if we didn't keep the in_node* current pointer which gives us access to the end of each appearance list in constant time (O(1)). So I think that as terms of complexity you can't go better than O(nlogn).
Related
I have a project about linked lists but I'm having a hard time doing it. The teacher wants me to read a .txt file and create singly linked list from it. After that, I need to reverse odd numbers of every line. Then print it. Here is the code which I used for printing the linked list. But I need help to reverse the odd numbers of each line.
This is the code which I used to print the list:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct list {
char *string;
struct list *next;
};
typedef struct list LIST;
int main(void) {
FILE *fp;
char line[10];
LIST *current, *head;
head = current = NULL;
fp = fopen("data.txt", "r");
while(fgets(line, sizeof(line), fp)){
LIST *node = malloc(sizeof(LIST));
node->string = strdup(line);
node->next =NULL;
if(head == NULL){
current = head = node;
} else {
current = current->next = node;
}
}
fclose(fp);
for(current = head; current ; current=current->next){
printf("%s", current->string);
}
return 0;
}
Here is the content of the .txt file:
10
9,6,11,7,12,18,19,14,15,13
13,14,9,12,15,3,18,20,1,2
4,11,8,17,12,15,20,10,3,16
19,4,11,1,13,17,12,16,20,18
1,6,20,11,13,9,7,16,10,2
12,4,11,16,3,20,9,19,17,15
20,3,10,12,18,2,5,14,15,16
18,19,15,2,6,9,1,3,17,4
7,6,20,1,11,4,3,5,8,16
1,2,16,13,17,10,12,9,4,15
"But I need help to reverse the odd numbers of each line."
There are several other parts that need to be considered before this step can be developed.
Following are suggestions for a functions approach implementation using your problem description. A few items are simply suggestions to simplify the existing code. And a few other steps, are not mentioned as necessary, but should be considered:
Since you are not mandated to use char *string; in your problem description, choose to use a reasonable string length variable that does not require an additional layer of dynamic allocation, such as char string[260]; (or even smaller to fit your input file.) This will greatly simplify the code.
Because the input file is sized with lines ~30 char long, declare the variable line to be at least large enough to contain one line, eg 80 would allow larger values, and still allow enough space, but since memory is cheap, go with the same size as is used in the string member of your linked list.
Move the work of populating each new node to a function. It also will greatly simplify the program, and provide greater readability. Eg: void insert(LIST **head_ref, char *str);
Always test the return of fopen() before attempting to use the file descriptor.
To manipulate the contents of each odd row (eg 1, 3, 5, 7, 9), as numbers, the contents of each line read in from a file as a string, needs to first be converted to a collection of numbers. This suggests an additional member be added to the struct. For example int num[10].
The previous observation implicitly suggests the need of an additional function to parse and convert each comma delimited string into discrete integer values. Perhaps with the prototype: void parseIntArray(LIST **list);
The next and final task also suggests an additional function to reverse the contents of selected array member integer arrays. This one might use a prototype such as: void reverse_odd(LIST **list, size_t size);
Finally, because each node of LIST created required dynamically allocated memory, once finished using LIST, the memory must be given back to the OS to prevent memory leaks. An additional function to do this could be prototyped: void freeList(LIST **head);
Following are the main() function and preceding support declarations etc. It is intended here to illustrate the above suggested steps, and the benefits of breaking down a bigger problem into smaller problems, then implementing each smaller solution to support the whole. Benefits include for example readability and maintainability and potential re-use of code-base, (Note the similarity of argument lists in each supporting function.):
#define MAX_STRLEN 260 //use mnemonic values to avoid magic numbers in code
struct list {
char string[MAX_STRLEN];
int arr[10];
struct list *next;
};
typedef struct list LIST;
//Prototypes of 'smaller' solutions
void insert(LIST **head_ref, char *str);
void parseIntArray(LIST **list);
void reverse_odd(LIST **list, size_t size);
void freeList(LIST **head);
int main(void)
{
FILE *fp;
char line[MAX_STRLEN];
LIST *current, *head;
char *convPtr = NULL;
head = current = NULL;
fp = fopen("data.txt", "r");
if(fp)
{
//consume 1st line
if(fgets(line, sizeof(line), fp));//10
{
sizeArray = strtol(line, &convPtr, 10);
if(errno != ERANGE)
{
while(fgets(line, sizeof(line), fp))
{
//(see implementations of each below)
//create new node, insert num string
insert(¤t, line);
//convert new->string to integers, place in new->array
parseIntArray(¤t);
//reverse 'odd' contents of each array
reverse_odd(¤t, sizeArray);
}
}else{//handle error and leave}
}
fclose(fp);
}else{//handle error and leave}
//At this point in code, entire file is captured into nodes of list.
//use list as needed
//When finished using list, memory must be freed to prevent memory leaks
head = current;
freeList(&head);
return 0;
}
The remaining code segments are the function implementations used above:
void freeList(LIST **head)
{
LIST *tmp;
while (*head != NULL)
{
tmp = (*head);
(*head) = (*head)->next;
free(tmp);
}
}
//create new node, insert num string
void insert(LIST **head_ref, char *str)
{
int *arr = malloc(numNodes * sizeof(*arr));
//allocate node
LIST* new = calloc(1, sizeof(*new));
//put in the data
strcpy(new->string, str);
//Make next of new node as head
new->next = (*head_ref);
//Move the head to point to the new node
(*head_ref) = new;
}
//convert new->string to integers, place in list->array
void parseIntArray(LIST **list)
{
char *tok = NULL;
int i = 0;
int tmp = 0;
char *sArray = strdup((*list)->string);
tok = strtok(sArray, ",\n ");
while(tok)
{
errno = 0;
tmp = atoi(tok);
if(errno == ERANGE)
{
printf("Error converting string to number\nExiting.");
return;
}
(*list)->arr[i] = tmp;
i++;
tok = strtok(NULL, ",\n ");
}
}
//reverse 'odd' contents of list->array
void reverse_odd(LIST **list, size_t size)
{
int *ptr = &((*list)->arr[0]);
int *tmp = malloc(size * sizeof(*tmp));
memset(tmp, -1, size*sizeof(*tmp));
for(int i=0;i<size;i++)
{
if(ptr[i]%2 != 0)
tmp[size-1-i] = ptr[i];
}
for(int i=0;i<size;i++)
{
if(tmp[i] < 0)
{
while((*ptr)%2 != 0 ) ptr++;
tmp[i] = *ptr;
ptr++;
}
}
memcpy((*list)->arr, tmp, size*sizeof(int));
}
This hope this code will do the job.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct line {
struct num *first;
struct line *next;
} LineNode;
typedef struct num {
int num;
int order;
struct num *next;
} NumNode;
int main() {
FILE *fp;
char ch;
int counter = 0;
NumNode *curr_num, *even_ptr, *odd_ptr, *odd_head, *even_head;
LineNode *curr_line, *line_head;
curr_num = even_head = odd_head = even_ptr = odd_ptr = NULL;
line_head = curr_line = NULL;
fp = fopen("data.txt", "r");
if (fp == NULL)
{
return 1;
}
ch = fgetc(fp);
while(ch != EOF){
if (ch >= 48 && ch <= 57)
{
int n = 0;
while (ch != EOF && ch != '\n' && ch >= 48 && ch <= 57)
{
int x = ch - 48;
n = n * 10 + x;
ch = fgetc(fp);
}
NumNode *node = malloc(sizeof(NumNode));
node->num = n;
node->order = counter;
node->next =NULL;
if (n % 2 == 0){
if(even_head == NULL){
even_head = even_ptr = node;
} else {
even_ptr = even_ptr->next = node;
}
}else{
if(odd_head == NULL){
odd_head = node;
} else {
node->next = odd_head;
odd_head = node;
}
}
counter++;
}
if (ch == '\n' || ch == EOF)
{
NumNode *num_node, *head;
num_node = head = NULL;
even_ptr = even_head;
odd_ptr = odd_head;
counter = 0;
if (even_head != NULL && even_head->order == counter){
head = num_node = even_ptr;
even_ptr = even_ptr->next;
} else {
head = num_node = odd_ptr;
odd_ptr = odd_ptr->next;
}
counter++;
while (even_ptr != NULL)
{
if (even_ptr->order == counter) {
num_node = num_node->next = even_ptr;
even_ptr = even_ptr->next;
}
else if (odd_ptr != NULL) {
num_node = num_node->next = odd_ptr;
odd_ptr = odd_ptr->next;
}
counter++;
}
while (odd_ptr != NULL)
{
num_node = num_node->next = odd_ptr;
odd_ptr = odd_ptr->next;
}
LineNode *node = malloc(sizeof(LineNode));
node->next =NULL;
node->first = head;
if (line_head == NULL)
line_head = curr_line = node;
else
curr_line = curr_line->next = node;
odd_head = even_head = NULL;
counter = 0;
}
ch = fgetc(fp);
}
fclose(fp);
for(curr_line = line_head; curr_line != NULL ; curr_line=curr_line->next) {
for(curr_num = curr_line->first; curr_num != NULL ; curr_num=curr_num->next) {
printf("%d", curr_num->num);
if (curr_num->next != NULL)
printf(",");
}
printf("\n");
}
return 0;
}
This code is reading a text file and inserting each word into the linked list.
I am new to linked lists and have been working on this for four hours now and cannot for the life of me figure this out.
So what is going on here? I have checked every way I know how to, and for the life of me cannot get the linked list to print correctly. I believe it has something to do with the push/append functions. Somehow it is overwriting everything previously in the linked list. Maybe the printlist function is overwriting everything but I don't see how it could possibly be doing that.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
// A complete working C program to demonstrate all insertion methods
// on Linked List
// A linked list node
struct Node;
void push(struct Node **head_ref, char *new_data);
void insertAfter(struct Node *prev_node, char *new_data);
void append(struct Node **head_ref, char *new_data);
void printList(struct Node *node);
int LINECOUNT(FILE *(*stream), char *filename);
struct Node {
char *data;
struct Node *next;
};
/* Given a reference (pointer to pointer) to the head of a list and
an int, inserts a new node on the front of the list. */
void push(struct Node **head_ref, char *new_data) {
/* 1. allocate node */
struct Node* new_node = (struct Node *)malloc(sizeof(struct Node));
/* 2. put in the data */
new_node->data = new_data;
printf("push data:%s ", new_data);
/* 3. Make next of new node as head */
new_node->next = (*head_ref);
/* 4. move the head to point to the new node */
(*head_ref) = new_node;
}
/* Given a reference (pointer to pointer) to the head
of a list and an int, appends a new node at the end */
void append(struct Node **head_ref, char *new_data) {
/* 1. allocate node */
struct Node* new_node = (struct Node *)malloc(sizeof(struct Node));
struct Node *last = *head_ref; /* used in step 5*/
/* 2. put in the data */
new_node->data = new_data;
printf("push data:%s ", new_data);
/* 3. This new node is going to be the last node, so make next of
it as NULL*/
new_node->next = NULL;
/* 4. If the Linked List is empty, then make the new node as head */
if (*head_ref == NULL) {
*head_ref = new_node;
return;
}
/* 5. Else traverse till the last node */
while (last->next != NULL)
last = last->next;
/* 6. Change the next of last node */
last->next = new_node;
return;
}
// This function prints contents of linked list starting from head
void printList(struct Node *node) {
while (node != NULL) {
printf(" %s ", node->data);
node = node->next;
}
}
int LINECOUNT(FILE *(*stream), char *filename) {
int size = 0;
size_t chrCount;
char *text;
if ((*stream = fopen(filename, "r")) == NULL) {
printf("LC Could not open hw8 data file.\n");
exit(0);
}
while (1) {
text = NULL;
getline(&text, &chrCount, *stream);
free(text); /*free text*/
if (feof(*stream))
break;
size++;
}
rewind(*stream);
return size;
}
/*int wordCount(FILE *(*stream), char *filename, int lineCount) {
char ch;
int wordcount = 0;
int charcount = 0;
*stream = fopen(filename, "r");
int x = 0;
int lineArr[lineCount];
for (int i = 0; i < lineCount; i++) {
lineArr[i] = 0;
}
if (*stream) {
while ((ch = getc(*stream)) != EOF) {
if (ch != ' ' && ch != '\n') {
charcount++;
}
if (ch == ' ' || ch == '\n') {
wordcount++;
lineArr[x] = lineArr[x] + 1;
}
if (ch == '\n') {
x++;
}
}
if (charcount > 0) {
wordcount++;
charcount++;
}
} else {
printf("Failed to open the file\n");
}
// rewind(*stream);
return lineArr;
}*/
int main(void) {
char *fn = "hw8data.txt";
int lineCount;
FILE *stream;
lineCount = LINECOUNT(&stream, fn);
//int lineArr[lineCount];
//int lineArr[];//lineArr[0] = 4 would say the first line has 4 words. using this data for strtok
//lineArr = wordCount(&stream, fn, lineCount);
//-------------------------------------
char ch;
int wordcount = 0;
int charcount = 0;
stream = fopen("./hw8data.txt", "r");
int x = 0;
int lineArr[lineCount];
for (int i = 0; i < lineCount; i++) {
lineArr[i] = 0;
}
if (stream) {
while ((ch = getc(stream)) != EOF) {
if (ch != ' ' && ch != '\n') {
charcount++;
}
if (ch == ' ' || ch == '\n') {
wordcount++;
lineArr[x] = lineArr[x] + 1;
}
if (ch == '\n') {
x++;
}
}
//if (charcount > 0) { wordcount++; charcount++; }
} else {
printf("Failed to open the file\n");
}
/* Start with the empty list */
struct Node *head = NULL;
rewind(stream);
char *sArr = malloc(42 * sizeof(char));
fscanf(stream, "%s ", sArr);
printf("%s ", sArr);
push(&head, sArr);
fscanf(stream, "%s ", sArr);
printf("%s ",sArr);
append(&head, sArr);
printList(head);
return 0;
}
char* sArr=malloc(42*sizeof(char));
fscanf(stream,"%s ",sArr);
printf("%s ",sArr);
push(&head,sArr);
fscanf(stream,"%s ",sArr);
printf("%s ",sArr);
append(&head,sArr);
You add the same value to the list twice, the value you got back from your one and only call to malloc. If you want two nodes to hold different values, don't add the same value twice. One ugly fix is if after push(&head,sArr) you add another sArr = malloc(42*sizeof(char));. That way, your call to append will add a different value to the list.
If you don't see this, add code to output the value of node->data as you print the list. You'll see that both nodes have pointers to the same chunk of memory, the value you got back from that call to malloc.
But it would be much more elegant if your list entries owned their contents. That would require functions like push and append to allocate their own pointers, copy the strings into them, and use those new pointers. Your code to destroy a list could call free on the data pointed to as well as the nodes.
I would suggest a completely different approach.
I would use a C99 flexible array member for storing each word. Also, because I don't want my code to be submittable as a homework answer, I'll show how to do it with wide-character input. (On basically all OSes except possibly Windows, it treats non-ASCII characters like Ö and Ø as letters, if your locale says they are.)
struct word {
struct word *next;
wchar_t data[]; /* Flexible array member */
};
I would use a helper function that reads the next word from a wide stream, skipping any non-word characters (which I assume to be alphanumeric characters, i.e. letters and digits):
struct word *wide_word(FILE *input)
{
struct word *w = NULL, *tempw;
size_t max = 0; /* No characters allocated in w yet */
size_t len = 0; /* No characters in w yet */
wint_t c;
/* NULL input is not allowed. */
if (!input) {
errno = EINVAL;
return NULL;
}
/* Also fail if the stream is already in an error state. */
if (ferror(input)) {
errno = EIO;
return NULL;
}
c = getwc(input);
/* Skip leading non-word characters. */
while (c != WEOF && !iswalnum(c))
c = getwc(input);
/* End of input? */
if (c == WEOF) {
errno = 0;
return NULL;
}
/* Append each wide word character. */
while (c != WEOF && iswalnum(c)) {
/* Need to reallocate? */
if (len >= max) {
/* Since words are usually short, let's allocate
in chunks of 64 wide characters. */
max = (len | 63) + 65;
tempw = realloc(w, sizeof (struct word) + max * sizeof (wchar_t));
if (!tempw) {
/* Out of memory. */
free(w);
errno = ENOMEM;
return NULL;
}
w = tempw;
}
/* Append. */
w->data[len++] = c;
c = getwc(input);
}
/* Although not useful for this particular case,
we normally want to keep the separator intact. */
if (c != WEOF)
ungetwc(c, input);
/* Optimize the memory allocated to the word. */
if (max != len + 1) {
max = len + 1;
tempw = realloc(w, sizeof (struct word) + max * sizeof (wchar_t));
if (!tempw) {
free(w);
errno = ENOMEM;
return NULL;
}
w = tempw;
}
/* Terminate the wide string in w. */
w->data[len] = L'\0';
/* Success! */
return w;
}
I personally prefer to prepend new nodes to the list, then reverse the entire list afterwards:
struct word *reverse_list(struct word *oldlist)
{
struct word *newlist = NULL;
struct word *w;
while (oldlist) {
w = oldlist;
oldlist = oldlist->next;
w->next = newlist;
newlist = w;
}
return newlist;
}
With the above, a program to read wide words from standard input is basically
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <wchar.h>
#include <errno.h>
/* Functions listed above */
int main(void)
{
struct word *list, *node;
if (!setlocale(LC_ALL, ""))
fprintf(stderr, "Warning: Your C library does not support your current locale.\n");
if (fwide(stdin, 1) < 1)
fprintf(stderr, "Warning: Your C library does not support wide standard input.\n");
if (fwide(stdout, 1) < 1)
fprintf(stderr, "Warning: Your C library does not support wide standard output.\n");
/* Read words from standard input into reversed list. */
while (1) {
node = wide_word(stdin);
if (!node) {
if (errno) {
fprintf(stderr, "Error reading standard input: %s.\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* No error, just end of input. */
break;
}
/* Prepend to list. */
node->next = list;
list = node;
}
/* Reverse the list so first word is first in list. */
list = reverse_list(list);
/* Print each word in the list to standard output, in order. */
for (node = list; node != NULL; node = node->next)
wprintf(L"%ls\n", node->data);
/* We could free() each word in 'list' here. */
return EXIT_SUCCESS;
}
I'm trying to setup a graph in C. I tried the graph with user input and it works perfectly. However, i am trying to implement a read from file. The last else statement is where the error is coming from because when i commented it out it compiles without any problems. I have included a comment over the block i think that has the problem. Please let me know if there is anything else needed for this question.
#include <stdio.h>
#include <stdlib.h>
struct node{
int data;
struct node* next;
};
//int counter and mainVertex would be used to determine if graph is connected.
// void graphConnection(){
//
//
//
//
//
//
// }
char* deblank(char* input)
{
int i,j;
char *output=input;
for (i = 0, j = 0; i<strlen(input); i++,j++)
{
if (input[i]!=' ')
output[j]=input[i];
else
j--;
}
output[j]=0;
return output;
}
struct node *G[1000];
int counter = 0;
char *mainVertex;
void readingEachLine(){
FILE * fp;
char * line = NULL;
size_t len = 0;
ssize_t read;
//Read file and exit if fail
fp = fopen("test.txt", "r");
if (fp == NULL)
exit(EXIT_FAILURE);
while ((read = getline(&line, &len, fp)) != -1) {
line = deblank(line);
int i = 0;
struct node* cursor = malloc(sizeof(struct node));
struct node* secondcursor = malloc(sizeof(struct node));
struct node* tempitem;
while(line[i] != '\n'){
//If its the first of the line look into the array and set struct cursor to the corresponding
//array position
if (i == 0){
mainVertex[counter] = line[0];
int convertor = line[i] - '0';
cursor = G[convertor];
counter++;
}
//if its not the first, then set a struct with that number as data
else{
tempitem = malloc(sizeof(struct node));
int convertor = line[i] - '0';
tempitem->data = convertor;
tempitem->next = NULL;
}
//if there is no element connected to the struct in array, connect the tempitem
if (cursor->next == NULL){
cursor->next = tempitem;
}
//If there are already connected elements, loop until the end of the linked list
//and append the tempitem
//ERROR: I GET SEGMENTATION FAULT FROM HERE. TRIED AFTER COMMENTING IT OUT
else{
secondcursor = cursor;
while(secondcursor->next != NULL){
secondcursor = secondcursor->next;
}
secondcursor->next = tempitem;
}
i++;
}
printf("\n");
}
}
int main(void){
for (int i = 1; i < 1000; i++)
{
G[i]= malloc(sizeof(struct node));
G[i]->data = i;
G[i]->next = NULL;
}
readingEachLine();
}
EDIT: This is how the text file looks like:
1 3 4
2 4
3 1 4
4 2 1 3
Your code has several misconceoptions:
Apparently, you can have a maximum of 1,000 nodes. You have an array G of 1,000 head pointers to linked lists. Don't allocate memory for all 1,000 nodes at the beginning. At the beginning, all lists are empty and an empty linked list is one that has no node and whose head is NULL.
In your example, cursor is used to iterate oer already existing pointers, so don't allocate memory for it. If you have code like this:
struct node *p = malloc(...);
// next use of p:
p = other_node;
you shouldn't allocate. You would overwrite p and lose the handle to the allocated memory. Not all pointers have to be initialised with malloc; allocate only if you create a node.
Your idea to strip all spaces from a line and then parse single digits will fail if you ever have more then 9 nodes. (But you cater for 1,000 node.) Don't try to parse the numbers yourself. There are library functions for that, for example strtol.
It is not clear what mainVertex is supposed to be. You use it only once, when you assign to it. You treat it like an array, but it is a global pointer, initialised to NULL. When you dereference it, you get undefined behaviour, which is where your segmentation fault probably comes from.
Here's a program that does what you want to do. (It always inserts nodes at the head for simplicity and it should have more allocation checks.)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum {
maxNodes = 1000
};
struct node{
int data;
struct node* next;
};
struct node *G[maxNodes];
size_t nnode = 0;
int read_graph(const char *fn)
{
FILE * fp;
char * line = NULL;
size_t len = 0;
fp = fopen(fn, "r");
if (fp == NULL) return -1;
while (getline(&line, &len, fp) != -1) {
char *p;
char *end;
int id;
int n;
id = strtol(line, &end, 10);
if (end == line) continue;
if (id < 1 || id > maxNodes) break;
if (id > nnode) nnode = id;
id--;
p = end;
n = strtol(p, &end, 10);
while (p != end) {
struct node *nnew = malloc(sizeof(*nnew));
nnew->data = n - 1;
nnew->next = G[id];
G[id] = nnew;
p = end;
n = strtol(p, &end, 10);
}
}
fclose(fp);
free(line);
return 0;
}
int main(void)
{
if (read_graph("test.txt") < 0) {
fprintf(stderr, "Couldn't gread raph.\n");
exit(1);
}
for (int i = 0; i < nnode; i++) {
struct node *p = G[i];
if (p) {
printf("%d:", i + 1);
for (; p; p = p->next) {
printf(" %d", p->data + 1);
}
puts("");
}
}
for (int i = 0; i < nnode; i++) {
struct node *p = G[i];
while (p) {
struct node *old = p;
p = p->next;
free(old);
}
}
return 0;
}
I am creating a program that will read a word from a text file in main.c, and send it over to list.c file to create a new node to store that word. The node will also store three ints: first (number of times this word appears in txt file 1), second(number of times this word appears in txt file 2), and dif (abs(first-second)). After adding all the new words to the file and counting the number of times each word exists in each txt file, the main.c will call a method that will calculate the difference between first and second for each node. This is difference (stored in dif for each node) will be used to sort the linked nodes in decreasing order.
EX. word: the, first: 2888, second: 2466, dif: 422.
red, 39 12 27
.....
However, when main calls the sort method, a infinite loop occurs. This infinite loop comes from the inner loop of the sorting algorithm, where the current node is assigned the node from the curr->next pointer. Somewhere during the sort method, the current node's next pointer points to the current node, not the actual next node in the linkedlist. If the sort method is dactivated, then all other functions work fine, including printAll which goes through the entire list and prints the data in each node (see my example above).
My issue is that I cannot find where in my sort method how current->next started to point to the current node. Any help is appreciated!
/*
* list.h
*/
#ifndef LIST_H_
#define LIST_H_
typedef struct node Node;
void findWord(char *word, int book);
void addWord(char *word, int book);
void editWord(Node **endPtr, int book);
void sort();
void swap(Node **a, Node **b);
void calculateDiff();
void printAll();
#endif /* LIST_H_ */
/*
* list.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
typedef struct node{
int first;
int second;
int dif;
char name[20];
struct node *next;
}Node;
Node *front = NULL;
/*
* Sees if the current word exists in the
* linkedlist.
*/
void findWord(char *word, int book) {
Node *curr = front;
int boolean = 0;
while (curr != NULL) {
if(strcmp(curr->name, word) == 0) {
boolean = 1;
editWord(&curr, book);
break;
}
curr = curr->next;
}
if(!boolean) { //Add word if it does not exist.
addWord(word, book);
}
}
/*
* Creates a new node for the added word. Adds to front.
*/
void addWord(char *word, int book) {
Node *newNode = malloc (sizeof(Node));
/*
* Since this word is being added
* to the linkedlist with a newly
* created node, either the
* first or second int must be to 1
* while the other is set to 0. Based
* off of book int.
*/
if(book == 1) {
newNode->first = 1;
newNode->second = 0;
} else {
newNode->first = 0;
newNode->second = 1;
}
newNode->dif = 0;
strcpy(newNode->name, word);
newNode->next = front;
front = newNode;
}
/*
* Edits the data for an existing word.
* Only called if current word exists in
* the linkedlist.
*/
void editWord(Node **endPtr, int book) {
if (book == 1) {
(*endPtr)->first++;
} else {
(*endPtr)->second++;
}
}
/*
* Sorts the list in descending order based on
* difference value.
*/
void sort() {
Node *curr, *last = NULL;
curr = front;
while (curr != last) {
while (curr->next != last) {
if(curr->dif < curr->next->dif ) {
swap(&curr, &curr->next);
}
curr = curr->next;
}
last = curr;
curr = front;
}
}
/*
* Swaps the data in the current and next node in the list.
*/
void swap(Node **a, Node **b) {
int temp;
char nameTemp[20];
//Swap first
temp = (*a)->first;
(*a)->first = (*b)->first;
(*b)->first = temp;
//Swap second
temp = (*a)->second;
(*a)->second = (*b)->second;
(*b)->second = temp;
//Swap dif
temp = (*a)->dif;
(*a)->dif = (*b)->dif;
(*b)->dif = temp;
//Swap name
strcpy(nameTemp, (*a)->name);
strcpy((*a)->name, (*b)->name);
strcpy((*b)->name, nameTemp);
}
/*
* Calculates the difference between first and second
*/
void calculateDiff() {
Node *curr = front;
while(curr != NULL) {
curr->dif = abs((curr->first - curr->second));
curr = curr->next;
}
}
/*
* Prints all the data from the nodes.
*/
void printAll() {
printf("|| Word || RedBadge || LittleRegiment || Diff\n");
Node *curr = front;
while ( curr != NULL ) {
printf("%s, %d, %d, %d\n", curr->name, curr->first, curr->second, curr->dif);
curr = curr->next;
}
}
/*
* main.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "list.h"
void readBook(int book, FILE *infile);
void readLine(char *line, int book);
int main (void) {
setvbuf(stdout, NULL, _IONBF,0);
FILE *infile = fopen("RedBadge.txt", "r");
FILE *infile2 = fopen("LittleRegiment.txt", "r");
readBook(1, infile);
readBook(2, infile2);
fclose(infile);
fclose(infile2);
calculateDiff();
sort();
printAll();
return 0;
}
void readBook(int book, FILE *infile) {
char line[70];
//Read in each line
while (!feof(infile)) {
fgets(line, 70, infile);
readLine(line, book);
}
}
void readLine(char *line, int book) {
int i = 0, j = 0;
char word[20];
while (line[i]) {
line[i] = tolower(line[i]); //Convert line to lowercase
if((line[i] <= 'z' && line[i] >= 'a') || line[i] == 39 || line[i] == '-') {
word[j] = line[i];
j++;
} else if (j != 0) {
word[j] = '\0';
findWord(word, book);
j = 0;
}
i++;
}
}
I believe your error is actually a buffer overflow. There are words in those books that are longer than 19 characters (the max that will fit in your word variable). When your readline function tries to read those words it will write outside the boundaries of the word array, which is undefined behavior. It will then also use strcpy to copy the word into the node, which will also overflow the node's word array.
A quick fix is to just throw away the extra characters past 19 that won't fit in your word array. In readline add a test for how big j is:
if (j < sizeof word - 1) {
word[j] = line[i];
j++;
}
One of the words in question is "ain't--plundering----" (at least in the copy of the text i downloaded), which leads me to think maybe you also should split words on punctuation.
I would like to read from a file, line by line. Each line has 3 arguments guaranteed. First 2 are first and last name and third is age.
I want to make a linked list, in which, each node represents a person (line) in the file.
I don't know the size of the names so I made it dynamic. I also don't know the number of lines in the file, so I would like that to be dynamic too.
My approach was to use fscanf, but then I wouldn't know how much memory needs to be allocated prior to reading it.
The function convertToList is supposed to receive a file path of the file we wanna read, convert it to a linked list, then return the head node. (Open to improvements)
Check out my code and see where I got stuck:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum
{
FALSE,
TRUE
}bool;
struct Node{
char firstName[50];
char lastName[50];
int age;
struct Node *next;
};
typedef struct {
struct Node *head;
}LinkedList;
struct Node * convertToList(char *inputFilePath);
int main(int argc, char* argv[]) {
if(argc != 4) {
printf("Invalid arguments.\n");
exit(0);
}
if (strlen(argv[3])!=1) {
printf("Invalid sorting type.\n");
exit(0);
}
char *inputFilePath = (char*) malloc(sizeof(char*) * strlen(argv[1]) +1);
memcpy(inputFilePath, argv[1], strlen(argv[1]));
char *outputFilePath = (char*) malloc(sizeof(char*) * strlen(argv[2]) +1);
memcpy(outputFilePath, argv[2], strlen(argv[2]) +1);
char *sortType = argv[3];
//LinkedList* inputList = (LinkedList*)malloc(sizeof(struct Node));
struct Node* head = malloc(sizeof(struct Node));
head = convertToList(inputFilePath);
printf("\n%s %s %d\n", head->firstName, head->lastName, head->age);
// printf("\nsaaap\n");
getchar();
}
struct Node * convertToList(char *inputFilePath) {
FILE* ifp;
ifp = fopen(inputFilePath, "r");
if (!ifp) { perror("fopen"); exit(0); }
struct Node *head = NULL;
struct Node *prev = NULL;
bool isHead = TRUE;
while(!feof(ifp)) {
struct Node *tmp = (struct Node*)malloc(sizeof(struct Node));
if (prev != NULL)
prev->next = tmp;
if (head==NULL)
head = tmp;
fscanf(ifp, "%s %s %d\n", tmp->firstName, tmp->lastName, &tmp->age);
prev = tmp;
//Need to link to next node as well
}
fclose(ifp);
return head;
}
I know that the fscanf is wrong, but I'm not sure how to fix it.
Also, how do I return the root? Is my approach gonna work?
And lastly, how do can I set the next node in the list? I don't see it happening with the current while loop.
Thanks.
If you need to link the nodes this is how you can do it and use dynamic storage, here you go, I didn't think this very much but it is Ok.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct Node
{
char *firstName;
char *lastName;
int age;
struct Node *next;
};
struct Node *convertToList(const char *const inputFilePath);
void freeList(struct Node *);
int main(int argc, char* argv[])
{
struct Node *head;
if (argc != 2)
{
printf("Invalid arguments.\n");
return 1;
}
head = convertToList(argv[1]);
if (head != NULL)
{
struct Node *current;
current = head;
while (current != NULL)
{
fprintf(stderr, "%s %s %d\n", current->firstName, current->lastName, current->age);
current = current->next;
}
/* do manupulations with the list, example above, print the values */
freeList(head);
}
return 0;
}
void freeList(struct Node *node)
{
struct Node *current;
current = node;
while (current != NULL)
{
struct Node *next;
next = current->next;
if (current->firstName != NULL)
free(current->firstName);
if (current->lastName != NULL)
free(current->lastName);
free(current);
current = next;
}
}
size_t appendChar(char **buffer, char character, size_t length)
{
char *temporary;
if (buffer == NULL)
return length;
temporary = realloc(*buffer, 1 + length);
if (temporary == NULL)
return length;
temporary[length] = character;
*buffer = temporary;
return 1 + length;
}
struct Node *parseFileLine(char *line)
{
char *word;
struct Node *node;
char *endptr;
if (line == NULL)
return NULL;
node = malloc(sizeof(struct Node));
if (node == NULL)
return NULL;
node->firstName = NULL;
node->lastName = NULL;
node->age = -1; // an invalid value;
node->next = NULL;
word = strtok(line, " ");
if (word == NULL)
return node;
node->firstName = strdup(word);
word = strtok(NULL, " ");
if (word == NULL)
return node;
node->lastName = strdup(word);
word = strtok(NULL, " ");
if (word == NULL)
return node;
node->age = strtol(word, &endptr, 10);
if (*endptr != '\0')
node->age = -1;
return node;
}
struct Node *getNode(FILE *file)
{
char *line;
int character;
size_t length;
line = NULL;
length = 0;
while ((character = fgetc(file)) != EOF)
{
if (((char)character == '\n') && (line != NULL))
{
struct Node *node;
length = appendChar(&line, '\0', length);
node = parseFileLine(line);
free(line);
return node;
}
length = appendChar(&line, (char)character, length);
}
if (line != NULL)
free(line);
return NULL;
}
struct Node *convertToList(const char *const inputFilePath)
{
FILE *ifp;
struct Node *head;
struct Node *current;
struct Node *last;
ifp = fopen(inputFilePath, "r");
if (ifp == NULL)
{
perror("fopen");
return NULL;
}
head = NULL;
last = NULL;
while ((current = getNode(ifp)) != NULL)
{
if (current == NULL)
return head;
if (head == NULL)
head = current;
if (last != NULL)
last->next = current;
last = current;
}
fclose(ifp);
return head;
}
Here you can also print the nodes to see that the data is correctly there.
I think you don't understand what malloc is for and you don't know much about pointers too, in your fscanf you are storing data in firstName and lastName without allocating memory for it, they are not even initialized so you would get a segmentation fault.
A somewhat different approach.
argv copying
First off, as mentioned, you do not need to copy argv values. Main reason for doing do is if you manipulate the values. There are also cases where one want to erase argv values as they can be read by ps and other tools, read from /proc/ etc. For example some programs take passwords as argument, to prevent password to be readable by anyone having access to the system one typically copy the argument then overwrite the argv value.
It is however usually good practice to use variables for the arguments. It usually makes the code clearer, but also makes it easier to maintain if one do changes. E.g. implement flag arguments like -f <filename>.
exit() and return from main()
You also exit() with zero on error. You would want to exit with zero on success, and other value on error or other. This is the norm. 0 == success. Some applications implement numeric exit codes that can mean different things. E.g. 0 is normal exit, 1 is not an error but some special case, 2 likewise 3 might be an error etc. For example grep:
EXIT STATUS
The exit status is 0 if selected lines are found, and 1 if not found. If an
error occurred the exit status is 2. (Note: POSIX error handling code should
check for '2' or greater.)
scanf
When you use scanf to read strings there are some tricks that can be used to make it better. First off always use the size parameter.
char name[16]
sscanf(buf, "%15s", name);
Do also check items read:
if (sscanf(buf, "%15s %d", name, &age) != 2)
... error ...
Third you can also save number of bytes read by %n:
sscanf(buf, "%n%15s%n %n%d%n", &of1, name, &of2, &age, &of3)
Usage
A very simple, but also quick and user-friendly thing, is to add a usage function.
Typically:
int usage(const char *self, const char *err_str)
{
fprintf(stderr,
"Usage: %s <in-file> <out-file> <sort-type>\n"
" Sort types:\n"
" f Sort by First Name\n"
" l Sort by Last Name\n"
" a Sort by Age\n"
,
self
);
if (err_str) {
fprintf(stderr,
"\nError: %s\n",
err_str
);
}
return ERR_ARG;
}
Then in main() you can quickly and clean add something like:
if (argc < 4)
return usage(argv[0], "Missing arguments.");
A note on you validation of the sort argument. Instead of using strlen() you can check if byte 2 is 0.
if (argv[3][1] != '\0')
... error ...
Finally main could be something like:
int main(int argc, char *argv[])
{
char *in_file, *out_file, sort;
struct Node *head = NULL;
int err = 0;
if (argc < 4)
return usage(argv[0], "Missing arguments.");
if (argc > 4)
return usage(argv[0], "Unknown arguments.");
if (argv[3][1] != '\0')
return usage(argv[0], "Invalid sorting type.");
in_file = argv[1];
out_file = argv[2];
sort = argv[3][0];
if (sort != 'f' && sort != 'l' && sort != 'a')
return usage(argv[0], "Invalid sorting type.");
if ((err = file_to_llist(in_file, &head)) != 0)
return err;
prnt_llist(stdout, head);
free_ll(head);
return err;
}
malloc helpers
When dealing with a lot of mallocing and similar it can be useful to add some helper functions. If you get a memory error you normally would exit right away.
void *alloc(size_t size)
{
void *buf;
if ((buf = malloc(size)) == NULL) {
fprintf(stderr, "Memory error.\n");
exit(ERR_MEM);
}
return buf;
}
void *re_alloc(void *old, size_t size)
{
void *buf;
if ((buf = realloc(old, size)) == NULL) {
fprintf(stderr, "Memory error.\n");
exit(ERR_MEM);
}
return buf;
}
Parsing of the file
As you want to have everything dynamically allocated and no limits (beyond system memory) one solution is to implement some sort of tokenizer. It can be helpful to use a struct to hold it together. Something like:
struct file_toker {
FILE *fh; /* File handle */
char *buf; /* Dynamic Read buffer */
size_t size; /* Size of buffer */
size_t len; /* Length of actual data in buffer. */
};
One point here is to keep length of tokens read. By this one do not need to keep using strlen etc.
If you can afford it it would usually be better to read whole file in one go, then parse the buffer. Optionally one can read file in chunks of say 4096*16 bytes, but then one get some complexity when it comes to overlapping lines between reads etc.
Anyhow in this example one byte is read at a time.
Start code
Finally a starting ground could be something like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* memcpy/strncpy */
#include <errno.h> /* errno for fopen() */
#include <ctype.h> /* isspace() */
#define ERR_ARG 1
#define ERR_FILE_FMT 2
#define ERR_MEM 3
struct Node {
char *name_first;
char *name_last;
int age;
struct Node *next;
};
struct file_toker {
FILE *fh;
char *buf;
size_t size;
size_t len;
};
/* ===============----- GEN HELPERS ------=================== */
int usage(const char *self, const char *err_str)
{
fprintf(stderr,
"Usage: %s <in-file> <out-file> <sort-type>\n"
" Sort types:\n"
" f Sort by First Name\n"
" l Sort by Last Name\n"
" a Sort by Age\n"
,
self
);
if (err_str) {
fprintf(stderr,
"\nError: %s\n",
err_str
);
}
return ERR_ARG;
}
void *alloc(size_t size)
{
void *buf;
if ((buf = malloc(size)) == NULL) {
fprintf(stderr, "Memory error.\n");
exit(ERR_MEM);
}
return buf;
}
void *re_alloc(void *old, size_t size)
{
void *buf;
if ((buf = realloc(old, size)) == NULL) {
fprintf(stderr, "Memory error.\n");
exit(ERR_MEM);
}
return buf;
}
/* ===============----- LINKED LIST ------=================== */
void free_node(struct Node *n)
{
if (!n)
return;
if (n->name_first)
free(n->name_first);
if (n->name_last)
free(n->name_last);
free(n);
}
void free_ll(struct Node *n)
{
struct Node *p;
if (!n)
return;
for ( ; n ; ) {
p = n;
n = n->next;
free_node(p);
}
}
void prnt_llist(FILE *fd, struct Node *n)
{
int i = 0;
fprintf(fd, "NODELIST:\n");
for ( ; n != NULL ; n = n->next) {
fprintf(fd,
"Entry %d {\n"
" Name: %s, %s\n"
" Age : %d\n"
"}\n",
++i,
n->name_last,
n->name_first,
n->age
);
}
}
/* ================--------- FILE TOKER ------------==================== */
/* Free / close reader. */
void free_ft(struct file_toker *ft)
{
if (!ft)
return;
if (ft->fh)
fclose(ft->fh);
free(ft->buf);
ft->fh = NULL;
ft->buf = NULL;
}
/* Initiate reader. */
int ft_init(struct file_toker *ft, const char *fn, size_t buf_sz)
{
ft->size = buf_sz;
ft->len = 0;
ft->buf = alloc(ft->size);
ft->fh = fopen(fn, "r");
if (!ft->fh) {
perror("Unable to open file");
return errno;
}
return 0;
}
/* Increase buffer size. */
size_t ft_increase(struct file_toker *ft)
{
if (ft->size < 1)
ft->size = 1;
ft->size *= 2;
ft->buf = re_alloc(ft->buf, ft->size);
return ft->size;
}
/* Read and skip spaces (\n, \r, ' ', \t etc.). Return first non-space. */
char ft_skip_space(struct file_toker *ft)
{
int c;
while ((c = fgetc(ft->fh)) != EOF && isspace(c))
;
return c == EOF ? 0 : (char)c;
}
/* Read next token */
size_t file_tok(struct file_toker *ft)
{
size_t i = 1;
size_t max;
int c;
if (ft->size < 2)
ft_increase(ft);
ft->len = 0;
max = ft->size - 1;
/* Skip any leading spaces. Function return first non-space. */
if ((ft->buf[0] = ft_skip_space(ft)) == 0)
return 0;
while ((c = fgetc(ft->fh)) != EOF) {
/* If space, break. */
if (isspace(c))
break;
/* Save char to buffer. */
ft->buf[i++] = (char)c;
/* If entire buffer used, increase it's size. */
if (i > max)
max = ft_increase(ft) - 1;
}
/* Null terminate. */
ft->buf[i] = 0x00;
/* Length without terminating null */
ft->len = i;
return i;
}
/* Read next space separated token and save it as new allocated string. */
int file_tok_str(struct file_toker *ft, char **out)
{
if (file_tok(ft) == 0)
return 1;
*out = alloc(ft->len + 1);
memcpy(*out, ft->buf, ft->len + 1);
return 0;
}
/* Read next space separated token and scan it as int. */
int file_tok_int(struct file_toker *ft, int *out)
{
if (file_tok(ft) == 0)
return 1;
if ((sscanf(ft->buf, "%d", out)) != 1)
return 1;
return 0;
}
/* ===============----- FILE PARSER ------=================== */
int file_to_llist(const char *fn, struct Node **head)
{
struct Node *node = NULL, *cur = *head;
struct file_toker ft;
/* Initiate new file token reader, initial buffer size 4096 bytes. */
if (ft_init(&ft, fn, 4096))
return 1;
while (1) {
/* Allocate next node */
node = alloc(sizeof(struct Node));
node->name_first = NULL;
node->name_last = NULL;
/* Read and copy first name. */
if (file_tok_str(&ft, &node->name_first))
break;
/* Read and copy last name. */
if (file_tok_str(&ft, &node->name_last))
break;
/* Read and copy age. */
if (file_tok_int(&ft, &node->age))
break;
/* Link and save current for next iteration. */
node->next = NULL;
if (cur) {
cur->next = node;
}
cur = node;
if (*head == NULL)
*head = node;
}
/* Free last unused node. */
free_node(node);
free_ft(&ft);
return 0;
}
/* ===============----- MAIN ROUTINE ------=================== */
int main(int argc, char *argv[])
{
char *in_file, *out_file, sort;
struct Node *head = NULL;
int err = 0;
if (argc < 4)
return usage(argv[0], "Missing arguments.");
if (argc > 4)
return usage(argv[0], "Unknown arguments.");
if (argv[3][1] != '\0')
return usage(argv[0], "Invalid sorting type.");
in_file = argv[1];
out_file = argv[2];
sort = argv[3][0];
if (sort != 'f' && sort != 'l' && sort != 'a')
return usage(argv[0], "Invalid sorting type.");
if ((err = file_to_llist(in_file, &head)) != 0)
return err;
prnt_llist(stdout, head);
free_ll(head);
return err;
}