To my understanding, there is a way to parse input such that:
A million $ exit $$$ 16
The Cheit and its Punishment $$$ 8
War and Remembrance $$$ 12
Winds of War $$$ 12
How to Play Football $$$ 12
Ultrashort Pulses $$$ 8
Nonlinear Optics $$$ 8
etc..
Where the "$$$" separates between fields of data.
I'm looking to upgrade the phrase:
sscanf(line, " %200[^$][^$][^$]$$$%ld", name, &copies);
so it would fit line no. 1 in the example.
EDIT:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NAME_LENGTH 200
#define ERROR -1
typedef int BOOL;
#define TRUE 1
#define FALSE 0
typedef struct book{
char name[NAME_LENGTH];
long copies;
struct book *next;
} Book;
Book* create_book(char name[], long copies){
Book *new_book = (Book*) malloc(sizeof(Book));
if (new_book != NULL) {
strcpy(new_book->name, name);
new_book->next = NULL;
new_book->copies = copies;
}
return new_book;
}
Book* add_first(Book *head, char name[], long copies){
Book *new_book = create_book(name, copies);
if (new_book == NULL)
return NULL;
new_book->next = head;
return new_book;
}
Book* add_last(Book *head, char name[], long copies){
Book *tail;
Book *new_book = create_book(name, copies);
if (new_book == NULL)
return NULL;
if (head == NULL)
return new_book;
tail = head;
while (tail->next != NULL)
tail = tail->next;
tail->next = new_book;
return head;
}
Book* add_sorted(Book *head, char name[], long copies){
Book* iter, *prev = NULL;
Book* new_book = create_book(name, copies);
if(new_book == NULL)
return head;
if (head == NULL)
return new_book;
if (!strcmp(new_book->name, head->name)){
new_book->next = head;
return new_book;
}
iter = head;
while ((iter != NULL) && (strcmp(new_book->name, head->name))){
prev = iter;
iter = iter->next;
}
prev->next = new_book;
new_book->next = iter;
return head;
}
int length(const Book *head){
if (head == NULL)
return 0;
return 1 + length(head->next);
}
void free_library(Book *head_book){
if (head_book == NULL)
return;
free_library(head_book->next);
free(head_book);
}
Book* find_book(Book *head, char name[]){
if (head == NULL)
return NULL;
if (strcmp(head->name, name) == 0)
return head;
find_book(head->next, name);
return NULL;
}
Book* delete_book(Book *head, char name[]){
Book *iter = head, *prev = NULL;
if (head == NULL)
return head;
if ((!strcmp(head->name, name)) == 1){
iter = head->next;
free(head);
return iter;
}
while (iter->next != NULL){
if ((!strcmp(head->name, name)) == 1){
prev->next = iter->next;
free(iter);
break;
}
prev = iter;
iter = iter->next;
}
return head;
}
Book* initBooksList(FILE *input){
Book *head_book = NULL, *existing_book = NULL;
long copies = 0;
char line[256] = {0}, name[NAME_LENGTH];
if (input == NULL){
printf("File did not open. Exit..\n");
return NULL;
}
while(!feof(input)){
if((fgets(line, 256, input) != NULL) && (head_book == NULL)){
sscanf(line, " %200[^$][^$][^$]$$$%ld", name, &copies);
printf("%s\n%ld\n", name, copies);
head_book = create_book(name, copies);
strcpy(line, "");
strcpy(name, "");
copies = 0;
}
else{
sscanf(line, " %200[^$][^$][^$]$$$%ld", name, &copies);
existing_book = find_book(head_book, name);
if(existing_book != NULL){
existing_book->copies += copies;
printf("%s\n%ld\n", name, existing_book->copies);
}
else{
add_sorted(head_book, name, copies);
printf("%s\n%ld\n", name, copies);
strcpy(line, "");
strcpy(name, "");
copies = 0;
}
}
}
return head_book;
}
void storeBooks(Book *head_book){
}
void returnBook(Book *head_book){
}
void borrowBook(Book *head_book){
}
int main(int argc, char *argv[]){
int i = 0;
FILE *ptr;
printf("%d\n", argc);
for(i = 0; i < argc; i++)
printf("argv[%d] = %s\n", i, argv[i]);
ptr = fopen(argv[1], "r");
initBooksList(ptr);
return 0;
}
If you know that the longest title is 200 characters, as indicated in your comment, you can allocate an array for this (including space for the null-terminator).
You can use fscanf()
to parse the lines of your file with the format string " %200[^$]$$$%d"
. The first space tells fscanf()
to skip over leading whitespaces, which may be left behind from previous I/O operations. The next conversion specifer is %200[^$]
, which tells fscanf()
to read any characters into a string, until a $
is encountered. The $
is left in the input stream. Note that a maximum width of 200 is specified here to prevent buffer overflow. The next three characters in the format string, $$$
, must be present in the input, and are matched before the final conversion specifier, %d
, is reached.
#include <stdio.h>
#include <stdlib.h>
#define MAX_TITLE 201
int main(void)
{
/* Open file, and check for success */
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Unable to open file");
exit(EXIT_FAILURE);
}
char title[MAX_TITLE];
int price;
while (fscanf(fp, " %200[^$]$$$%d", title, &price) == 2) {
printf("Title: %s --- Price: $%d\n", title, price);
}
fclose(fp);
return 0;
}
Here is the program output when run against your input file:
Title: The Cheit and its Punishment --- Price: $8
Title: War and Remembrance --- Price: $12
Title: Winds of War --- Price: $12
Title: How to Play Football --- Price: $12
Title: Ultrashort Pulses --- Price: $8
Title: Nonlinear Optics --- Price: $8
The calls to fscanf()
in the above code leave the whitespace character following the last number on each line in the input stream; this is why the leading whitespace in the format string was needed. A better solution would be to use fgets()
to fetch a line of input, and sscanf()
to parse the line. A buffer
should be allocated to hold the contents of each line as it is read; a generous allocation here is good, as it reduces the chance of long inputs leaving characters behind in the input stream. If there is a possibility of longer input, code should be added to clear the input stream before the next call to fgets()
.
One advantage to this approach is that, since an entire line is read including the \n
, there is no need to skip leading whitespace characters as before. Another advantage is that spurious characters after the final number can be ignored, or handled by the code; since the line is stored it can be inspected and scanned as many times as needed. Characters following the final number would have caused problems for the first version, which was only equipped to skip leading whitespace.
#include <stdio.h>
#include <stdlib.h>
#define BUF_SZ 1000
#define MAX_TITLE 201
int main(void)
{
/* Open file, and check for success */
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Unable to open file");
exit(EXIT_FAILURE);
}
char buffer[BUF_SZ];
char title[MAX_TITLE];
int price;
size_t lnum = 0;
while (fgets(buffer, BUF_SZ, fp) != NULL) {
++lnum;
if (sscanf(buffer, "%200[^$]$$$%d", title, &price) == 2) {
printf("Title: %s --- Price: $%d\n", title, price);
} else {
fprintf(stderr, "Format error in line %zu\n", lnum);
}
}
fclose(fp);
return 0;
}
The use of fgets()
here allows more flexibility in inspecting the input. To handle cases where $
is part of the title, you can use strstr()
to first find the delimiter " $$$"
, and then copy characters up to the delimiter into the title[]
array in a loop. Since strstr()
returns a pointer to the found string, this pointer can be given to sscanf()
to pick out the final number. The strstr()
function returns a null pointer if the string is not found, and this can be used to identify lines with formatting problems. Note that strstr()
is in string.h
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SZ 1000
#define MAX_TITLE 201
int main(void)
{
/* Open file, and check for success */
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Unable to open file");
exit(EXIT_FAILURE);
}
char buffer[BUF_SZ];
char title[MAX_TITLE];
int copies;
size_t lnum = 0;
while (fgets(buffer, BUF_SZ, fp) != NULL) {
++lnum;
/* Find delimiter string in buffer */
char *title_end = strstr(buffer, " $$$");
if (title_end == NULL) {
fprintf(stderr, "Format error in line %zu\n", lnum);
continue;
} else {
/* Copy characters into title until space before delimiter */
char *curr = buffer;
size_t i = 0;
while (curr < title_end && i < MAX_TITLE) {
title[i] = buffer[i];
++curr;
++i;
}
title[i] = '\0';
}
if (sscanf(title_end, " $$$%d", &copies) == 1) {
printf("Title: %s --- Copies: %d\n", title, copies);
} else {
fprintf(stderr, "Format error in line %zu\n", lnum);
}
}
fclose(fp);
return 0;
}
Here is a modified input file:
The Cheit and its Punishment $$$ 8
War and Remembrance $$$ 12
Winds of War $$$ 12
A million $ exit $$$ 16
How to Play Football $$$ 12
Ultrashort Pulses $$$ 8
Nonlinear Optics $$$ 8
and the resulting output:
Title: The Cheit and its Punishment --- Copies: 8
Title: War and Remembrance --- Copies: 12
Title: Winds of War --- Copies: 12
Title: A million $ exit --- Copies: 16
Title: How to Play Football --- Copies: 12
Title: Ultrashort Pulses --- Copies: 8
Title: Nonlinear Optics --- Copies: 8