C Primer 5th - Task 14-6 - c

A text file holds information about a softball team. Each line has data arranged as follows:
4 Jessie Joybat 5 2 1 1
The first item is the player's number, conveniently in the range 0–18. The second item is the player's first name, and the third is the player's last name. Each name is a single word. The next item is the player's official times at bat, followed by the number of hits, walks, and runs batted in (RBIs). The file may contain data for more than one game, so the same player may have more than one line of data, and there may be data for other players between those lines. Write a program that stores the data into an array of structures. The structure should have members to represent the first and last names, the at bats, hits, walks, and RBIs (runs batted in), and the batting average (to be calculated later). You can use the player number as an array index. The program should read to end-of-file, and it should keep cumulative totals for each player.
The world of baseball statistics is an involved one. For example, a walk or reaching base on an error doesn't count as an at-bat but could possibly produce an RBI. But all this program has to do is read and process the data file, as described next, without worrying about how realistic the data is.
The simplest way for the program to proceed is to initialize the structure contents to zeros, read the file data into temporary variables, and then add them to the contents of the corresponding structure. After the program has finished reading the file, it should then calculate the batting average for each player and store it in the corresponding structure member. The batting average is calculated by dividing the cumulative number of hits for a player by the cumulative number of at-bats; it should be a floating-point calculation. The program should then display the cumulative data for each player along with a line showing the combined statistics for the entire team.
team.txt (text file I'm working with):
4 Jessie Joybat 5 2 1 1
4 Jessie Joybat 7 3 5 3
7 Jack Donner 6 3 1 2
11 Martin Garder 4 3 2 1
15 Jaime Curtis 7 4 1 2
2 Curtis Michel 3 2 2 3
9 Gillan Morthim 9 6 6 7
12 Brett Tyler 8 7 4 3
8 Hans Gunner 7 7 2 3
14 Jessie James 11 2 3 4
12 Brett Tyler 4 3 1 3
Since I'm a beginner in C, either I misinterpreted the task from what was asked originally or it's unfairly complex (I believe the former is the case). I'm so lost that I can't think of the way how could I fill in by the criteria of index (player number) every piece of data, keep track of whether he has more than one game, calculate and fetch bat average and then print.
What I have so far is:
#define LGT 30
struct profile {
int pl_num;
char name[LGT];
char lname[LGT];
int atbat[LGT/3];
int hits[LGT/3];
int walks[LGT/3];
int runs[LGT/3];
float batavg;
};
//It's wrong obviously but it's a starting point
int main(void)
{
FILE *flx;
int i,jc,flow=0;
struct profile stat[LGT]={{0}};
if((flx=fopen("team.txt","r"))==NULL) {
fprintf(stderr,"Can't read file team!\n");
exit(1);
}
for( jc = 0; jc < 11; jc++) {
fscanf(flx,"%d",&i);
stat[i].pl_num=i;
fscanf(flx,"%s",&stat[i].name);
fscanf(flx,"%s",&stat[i].lname);
fscanf(flx,"%d",&stat[i].atbat[flow]);
fscanf(flx,"%d",&stat[i].hits[flow]);
fscanf(flx,"%d",&stat[i].walks[flow]);
fscanf(flx,"%d",&stat[i].runs[flow]);
flow++;
}
}

Advice 1: don't declare arrays like atbat[LGT/3].
Advice 2: Instead of multiple fscanf you could read the whole line in a shot.
Advice 3: Since the number of players is limited and the player number has a good range (0-18), using that player number as an index into the struct array is a good idea.
Advice 4: Since you need cumulative data for each player (no need to store his history points), then you don't need arrays of integers, just an integer to represent the total.
So:
#include <stdio.h>
#define PLAYERS_NO 19
typedef struct
{
char name[20+1];
char lastName[25+1];
int atbat;
int hits;
int walks;
int runs;
float batavg;
} Profile;
int main(int argc, char** argv)
{
Profile stats[PLAYERS_NO];
int i;
FILE* dataFile;
int playerNo;
Profile tmpProfile;
int games = 0;
for(i=0; i<PLAYERS_NO; ++i)
{
stats[i].name[0] = '\0';
stats[i].lastName[0] = '\0';
stats[i].atbat = 0;
stats[i].hits = 0;
stats[i].walks = 0;
stats[i].runs = 0;
}
dataFile = fopen("team.txt", "r");
if ( dataFile == NULL )
{
fprintf(stderr, "Can't read file team!\n");
exit(1);
}
for(i=0; i<PLAYERS_NO && !feof(dataFile); ++i, ++games)
{
fscanf(dataFile, "%d", &playerNo);
if ( playerNo <0 || playerNo > PLAYERS_NO )
{
fprintf(stderr, "Player number out of range\n");
continue;
}
fscanf(dataFile, "%s %s %d %d %d %d",
&tmpProfile.name,
&tmpProfile.lastName,
&tmpProfile.atbat,
&tmpProfile.hits,
&tmpProfile.walks,
&tmpProfile.runs);
printf("READ: %d %s %s %d %d %d %d\n",
playerNo,
tmpProfile.name,
tmpProfile.lastName,
tmpProfile.atbat,
tmpProfile.hits,
tmpProfile.walks,
tmpProfile.runs);
strcpy(stats[playerNo].name, tmpProfile.name);
strcpy(stats[playerNo].lastName, tmpProfile.lastName);
stats[playerNo].atbat += tmpProfile.atbat;
stats[playerNo].hits += tmpProfile.hits;
stats[playerNo].walks += tmpProfile.walks;
stats[playerNo].runs += tmpProfile.runs;
}
/* exercise: compute the average */
fclose(dataFile);
for(i=0; i<PLAYERS_NO; ++i)
{
if ( stats[i].name[0] == '\0' )
continue;
printf("%d %s %s %d %d %d %d\n",
i,
stats[i].name,
stats[i].lastName,
stats[i].atbat,
stats[i].hits,
stats[i].walks,
stats[i].runs);
}
return 0;
}

The first rule of programming: Divide and conquer.
So you need to identify individual operations. One such operation is "load one row of input", another is "look up a player". If you have some of those operations (more will come up as you go), you can start building your program:
while( more_input ) {
row = load_one_row()
player = find_player( row.name )
if( !player ) {
player = create_player( row.name )
add_player( player )
}
... do something with row and player ...
}
when you have that, you can start to write all the functions.
An important point here is to write test cases. Start with a simple input and test the code to read a row. Do you get the correct results?
If so, test the code to find/create players.
The test cases make sure that you can forget about code that already works.
Use a framework like Check for this.

If I were doing this, I'd start with a structure that only held one "set" of data, then create an array of those structs:
struct profile {
char name[NAMELEN];
char lname[NAMELEN];
int atbat;
int hits;
int walks;
int runs;
float batavg;
};
Since you're using the player's number as the index into an array, you don't need to store it into the structure too.
I think that will simplify the problem a little bit. You don't need to store multiple data items for a single player -- when you get a duplicate, you just ignore some of the new data (like the names, which should be identical) and sum up the others (e.g., at-bats, hits).

Related

Issues Reading a CSV file into a C Struct

I've just started learning C, and I'm currently trying to create a program to automate a fishing minigame I made for my D&D campaign. In order to do so, I created a CSV containing all the tables I had originally written up. I've been trying a variety of things for about a week now to just simply read the CSV into C so that I can call specific elements at will. I've finally gotten something to very nearly work, but I'm getting a strange problem. Firstly, the CSV is formatted as such:
bluefish,str,2,5
bluefish,str,2,5
bluefish,str,2,5
bluefish,str,2,5
kahawai,dex,2,1000
narrow barred mackerel,str,3,5
It goes on for another 400 lines, but you get the point. My current code looks like the following:
typedef struct{
char name[50];
char contest[5];
int modifier;
int value;
} fts_t;
/*Checking for the file and opening it*/
FILE *tpointer;
tpointer = fopen("fishing_tables.csv", "r");
printf("Accessing Fishing Tables...\n");
if(tpointer == NULL){
printf("Error: Missing fishing_tables.csv");
return 0;
} else{
printf("Success! Importing Data...\n");
}
/*Creating a struct array and initializing variables*/
fts_t ft[400];
char buffer[1024];
int count = 0;
char name[100];
char contest[3];
int modifier;
int value;
while(fgets(buffer, 1024, tpointer)){
sscanf(buffer, " %[^,],%[^,],%d,%d", name, contest, &modifier, &value);
strcpy(ft[count].name, name);
strcpy(ft[count].contest, contest);
ft[count].modifier = modifier;
ft[count].value = value;
printf("%s\t%s\t%d\t%d\n", ft[count].name, ft[count].contest, ft[count].modifier, ft[count].value);
count++;
}
So, following above, I'm having it print out the elements of the struct at each count as it loops, just to check that it's creating the struct correctly. The problem comes in that the output looks like this:
dex 2 15
dex 2 15
dex 2 15
dex 2 15
dex 2 15
next 0 0 0
next 0 0 0
next 0 0 0
next 0 0 0
next 0 0 0
Now, hilariously, the rows beginning with "next" are the only rows printing correctly. I have these added in for some extra shenanigans in the context of the minigame, but what's important is that those are correct. However, in every other row, the name variable is not being read, and I'm really lost as to why, when the "next" rows are functioning fine. I've checked the actual variable itself, and it appears there's some sort of problem in my sscanf statement.
Any help is appreciated.

Print one number after the last word

while(!feof(fp)) {
printf("\n %s %s %s", post.name, post.lastnamn, post.clubb);
for(i = 1; i <= x; i++;)
{
printf(" %d", j.number[i]);
}
fread(&post, sizeof(postTyp), 1, fp);
}
I have created two typedef struct and I am referring to them with post and j.
I have created a program that asks for a users name, last-name and clubb and stores it in a text file. I have also created a program that creates x amount of numbers in the array number[i] and save it in a text file. x counts how many times you have run a program to create an name, last-name and clubb. So if you run the program 3 times 3 numbers will be created like example down below
So when i print it i get this
Hanna Svensson FCB 1 2 3
Fabian Svensson FCB 1 2 3
Patrik Svensson FCB 1 2 3
What i want is to print it like this
Hanna Svensson FCB 1
Fabian Svensson FCB 2
Patrik Svensson FCB 3
Any tips for how I may accomplish this?
The solution is to only use the outer loop and not the inner one
i=1;
while(!feof(fp))
{
printf("\n %s %s %s", post.name, post.lastnamn, post.clubb);
printf(" %d", j.number[i]);
i++;
/* ...*/
}
Note aside: your while loop construct is very quetionable.

Recursive function printing in reverse order

(I'll start by noting this lab is low level 1st year programing, so high level stuff isn't usable right now for us)
I was given a lab to write a program in C that would accept a number between 1 and 36, six times, then print out those numbers as a bar graph, where the 'bar graph' is a number of # equal to the input number.
e.g. 5 would be:
So far I have this:
#include <stdio.h>
void graphCreate();
int main(void)
{
graphCreate();
}
void graphCreate()
{
static int chartLoop = 1;
int graphLength = 0;
int graphNumber = chartLoop;
while(graphLength > 36 || graphLength < 1)
{
printf("How long is chart %d?\t", graphNumber);
scanf("%d", &graphLength);
}
if(chartLoop < 6)
{
chartLoop++;
graphCreate();
}
printf("\n%d.\t%d|", graphNumber, graphLength);
while(graphLength > 0)
{
printf("#");
graphLength--;
}
}
And it does the output as expected... mostly:
How long is chart 1? 5
How long is chart 2? 10
How long is chart 3? 15
How long is chart 4? 20
How long is chart 5? 25
How long is chart 6? 30
6. 30|##############################
5. 25|#########################
4. 20|####################
3. 15|###############
2. 10|##########
1. 5|#####
However, I need the final outputs (the bars) in 1 -> 6 order, and it's reversed. What am I doing wrong that it's in reverse?
If using the recursive way to solve the problem is a requirement, you need to store the outputs and then print them in reverse order.
If recursive is not a requirement, you can loop 6 times to get and store the input numbers and print the bars in sequence.

while loop for scanning user input table into array

I am a super unintuitive beginner programming student working on a homework assignment. The program is to take an input table of hockey teams and game stats, calculate "points", and then output the table organized in a different way, with additional columns for points and games played.
So far, I haven't made it past scanning the input. With my while loop, I'm trying to first determine if the string is a conference name or a team name and then scan and store the subsequent table values accordingly. I'm just trying to print the same table back out at this point, but when i copy/paste the input input table which was given, I get no output, and when I manually type it in, it comes out suuper weird.
The input table looks something like this:
Conference1_Name 3 (teams in conference)
Team_1_Name 31 15 2 2 (wins, losses, ot losses, shootout losses)
Team_2_Name 24 21 1 0
Team_3_Name 27 19 0 2
Conference2_Name 4
Team_4_Name 30 15 1 1
...
aaand this is my code so far...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
int main(void){
int n=0, N=25, nE=0, nW=0, i, w[n], l[n], otl[n], sol[n], gp[n], pts[n];
char input[30], team[n][30];
printf("Enter conference data\n");
setvbuf(stdout,NULL,_IONBF,0);
while(n<=N){
scanf("%s", input);
if (0==strcmp(input, "Eastern_Conference")) {
scanf("%d", &nE);
continue; //does not count eastern conference as a team, but stores number of teams, and does not increment n
}
if (0==strcmp(input, "Western_Conference")) {
scanf("%d", &nW);
continue; //does not count western conference as a team, but stores number of teams, and does not increment n
}
if (0==strcmp(input, "EOD")) break;
//originally i had a statement like: if (nE>0 && nW>0){N=nE+nW;) so that the loop condition n<N will break the loop on it's own but thought maybe EOD would simplify it and fix my problem//
strcpy(team[n], input);
scanf("%d%d%d%d", &w[n], &l[n], &otl[n], &sol[n]);
gp[n]=w[n]+l[n];
pts[n]=(2*w[n])+otl[n]+sol[n];
n++;
} //so we have input the data and calculated points
printf("\n%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "WHL", "GP", "W", "L", "OTL", "SL", "PTS");
for(i=0; i<n; i++){
printf("%s\t%d\t%d\t%d\t%d\t%d\t%d\n", team[i], gp[i], w[i], l[i], otl[i], sol[i], pts[i]);
}
return EXIT_SUCCESS;
}

Reading specific phrases from input file in C

Last question for the night. I try not to post more than once per struggle haha...
This one's a bit simpler.
I have a txt file with a series of arranged numbers in the first 8 lines. Every line after is a certain phrase like "BUY ITEM" or "AWARD ITEM" followed by an integer (there are several phrases but I'm only concerned with one). Basically I'm trying to have a for or while loop where I can detect the phrase in the document, set the pointer to the end of the phrase and then fscanf the integer to the right of the phrase. The only trouble I'm having is getting the pointer to the end of the specific phrase and then reading the number. As well as the fact that the phrase is repeated on different lines and I don't want the values to be taken all at once.
I'm sure I can do a simple
while (!feof(//function for reading phrase)) {
fscanf("%d", &value);
//rest of function
And that would be that. But I've tried fseek and fget and nothing has really been able to help get the pointer to the location I need it to without having a preset location of where to go. The input file will be different each time so I can't just tell it to go 1024 spaces down or something like that. Just not sure how you would even go about this...
Also below is an example of an input file.
75 75 908
10 10
18 23.10 10.09
70 5 15
8 100 20 28.99
30 40 50 60
4 6 8 8 5 5 5 6 7 10
10
BUY ITEM 8
BUY ITEM 10
AWARD ITEM 7
BUY ITEM 1
BUY ITEM 3
AWARD ITEM 9
BUY ITEM 7
RETURN ITEM 8
Much appreciation for anyone's help.
Here's an easy way to do it, using the fact that on the lines in question, if your file follows the same format then the numbers will always appear in the same character of the line. This is a little bit fragile, and it'd be better to make your program more robust, to cope with arbitrary amounts of whitespace, for instance, but I'll leave that as an exercise to you:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LEN 100
#define BUY_LEN 9
#define AWARD_LEN 11
int main(void) {
FILE * infile = fopen("file.dat", "r");
if ( !infile ) {
perror("couldn't open file");
return EXIT_FAILURE;
}
char buffer[MAX_LEN];
char * endptr;
while ( fgets(buffer, MAX_LEN, infile) ) {
if ( !strncmp(buffer, "BUY ITEM ", BUY_LEN ) ) {
char * num_start = buffer + BUY_LEN;
long item = strtol(num_start, &endptr, 0);
if ( endptr == num_start ) {
fprintf(stderr, "Badly formed input line: %s\n", buffer);
return EXIT_FAILURE;
}
printf("Bought item %ld\n", item);
}
else if ( !strncmp(buffer, "AWARD ITEM ", AWARD_LEN) ) {
char * num_start = buffer + AWARD_LEN;
long item = strtol(num_start, &endptr, 0);
if ( endptr == num_start ) {
fprintf(stderr, "Badly formed input line: %s\n", buffer);
return EXIT_FAILURE;
}
printf("Awarded item %ld\n", item);
}
}
fclose(infile);
return 0;
}
Running this with the sample data file in your question, you get:
paul#local:~/src/sandbox$ ./extr
Bought item 8
Bought item 10
Awarded item 7
Bought item 1
Bought item 3
Awarded item 9
Bought item 7
paul#local:~/src/sandbox$
Incidentally, based on one of the suggestions in your question, you might want to check out the answers to the question "while( !feof( file ) )" is always wrong.

Resources