It appears much of the difficulty you are having in trying to figure out how to use the "TN"
stems from your attempt to store all data read in each line in a separate struct. As mentioned in the comments, this may be fine for reading data into a database where the database provides the ability to query all records by state abbreviation, but makes handling the data a bit more awkward. Why?
When you store all records as individual struct, there is no relationship between the state the data belongs to and the information stored other than a code
member of the struct. This means that if you wish to search or print the information for, e.g. "TN"
you must traverse over every single stuct checking whether the code
member matches "TN"
. Think about printing. You have to loop for each state, and then loop over every struct each time picking out information for a single state to print.
Instead of storing every record of information as an element in an array of records, why not have an array of states where each state contains a pointer to the data for that state. That would make your num_records
member make more sense. You would then simply have to loop over your array of states, check whether (num_records > 0)
and then printing the num_records
worth of information for that state while skipping all states where no data has been stored. This provides a much more efficient approach.
For example it takes little effort to rearrange your structs slightly to provide a relationship between the state and the data associated with that state, e.g.:
#include <stdio.h>
#include <stdlib.h>
/* if you need constants, either #define them or use an enum */
enum { ABRV = 2, NDATA = 8, LOC = 13, NAME = 15, MAXC = 1024 };
...
typedef struct { /* struct holding only climate data */
long long millitime;
char location[LOC];
double humidity;
double snow;
double cloud;
double lightning;
long double pressure;
double temperature;
} climate_t;
typedef struct {
size_t num_allocated, /* track of how many data are allocated */
num_records;
climate_t *data; /* a pointer to allocated block for data */
} statedata_t;
But how to relate reading "TN"
from the file to get the data stored with the correct state? This is where the lookup table comes in. If you had another simple struct containing the state name and abbreviation, you could create a simple array of struct that holds the abbreviation information and when you read, e.g. "TN"
from the file, you could simply "look-up" the index where "TN"
lives in your array holding abbreviations, and then use that index to store the information from that line at the corresponding index in your statedata_t
array.
Since your "lookup-array" will be constant, it can simply be a global that is declared as const
. If you are using multiple source files, you can simply define the array in one file and declare it a extern
in the remaining files that need it. So how would you define it? First declare a sturct with the information you want in your lookup (the state name and abbreviation) and then declare a constant array of them initializing the name and abbreviation for each, e.g.
typedef struct {
char name[NAME+1],
abrv[ABRV+1];
} stateabrv_t;
...
const stateabrv_t state[] = { { "Alabama", "AL" },
{ "Alaska", "AK" },
{ "Arizona", "AZ" },
{ "Arkansas", "AR" },
{ "California", "CA" },
{ "Colorado", "CO" },
{ "Connecticut", "CT" },
{ "Delaware", "DE" },
{ "Florida", "FL" },
{ "Georgia", "GA" },
{ "Hawaii", "HI" },
{ "Idaho", "ID" },
{ "Illinois", "IL" },
{ "Indiana", "IN" },
{ "Iowa", "IA" },
{ "Kansas", "KS" },
{ "Kentucky", "KY" },
{ "Louisiana", "LA" },
{ "Maine", "ME" },
{ "Maryland", "MD" },
{ "Massachusetts", "MA" },
{ "Michigan", "MI" },
{ "Minnesota", "MN" },
{ "Mississippi", "MS" },
{ "Missouri", "MO" },
{ "Montana", "MT" },
{ "Nebraska", "NE" },
{ "Nevada", "NV" },
{ "New Hampshire", "NH" },
{ "New Jersey", "NJ" },
{ "New Mexico", "NM" },
{ "New York", "NY" },
{ "North Carolina", "NC" },
{ "North Dakota", "ND" },
{ "Ohio", "OH" },
{ "Oklahoma", "OK" },
{ "Oregon", "OR" },
{ "Pennsylvania", "PA" },
{ "Rhode Island", "RI" },
{ "South Carolina", "SC" },
{ "South Dakota", "SD" },
{ "Tennessee", "TN" },
{ "Texas", "TX" },
{ "Utah", "UT" },
{ "Vermont", "VT" },
{ "Virginia", "VA" },
{ "Washington", "WA" },
{ "West Virginia", "WV" },
{ "Wisconsin", "WI" },
{ "Wyoming", "WY" } };
const int nstates = sizeof state / sizeof *state;
Now you have a simple 2-way lookup. Given the state name or abbreviation, you can return the index where it lives in the array. Further, given the name you can lookup the abbreviation, or given the abbreviation, you can lookup the name.
A simple lookup function returning the index could be:
/* simple lookup function, given a code s, return index for state
* in array of statedata_t on success, -1 otherwise.
*/
int lookupabrv (const char *s)
{
int i = 0;
for (; i < nstates; i++)
if (state[i].abrv[0] == s[0] && state[i].abrv[1] == s[1])
return i;
return -1;
}
Now that you can find the index given the abbreviation using your global lookup table, you can put the remainder of your data handling together in main()
by declaring an array of 50 statedata_t
, e.g.
int main (int argc, char **argv) {
char buf[MAXC]; /* line buffer */
/* array of 50 statedata_t (one for each state) */
statedata_t stdata[sizeof state / sizeof *state] = {{.num_records = 0}};
Now you are ready to begin reading from your file, and insert_data
for the proper state based on the abbreviation read from the file. A simple way to approach the read is to read the "TN"
into a separate array, and then read the climate data into a temporary stuct of type climate_t
that you can pass to your insert_data
function. In your insert_data
function, you simply lookup the index, (allocate or reallocate for data
as needed) and then assign your temporary struct of data to the memory block for your state.data. For example, your insert_data
function could be something like the following:
/* insert data for state given code and climate_t containing data */
int insert_data (statedata_t *st, const char *code, climate_t *data)
{
int index = lookupabrv (code); /* lookup array index */
if (index == -1) /* handle error */
return 0;
if (!st[index].num_allocated) { /* allocate data if not allocated */
st[index].data = malloc (NDATA * sizeof *st[index].data);
if (!st[index].data) {
perror ("malloc-st[index].data");
return 0;
}
st[index].num_allocated = NDATA;
}
/* check if realloc needed */
if (st[index].num_records == st[index].num_allocated) {
/* realloc here, update num_allocated */
}
/* add data for proper state index */
st[index].data[st[index].num_records++] = *data;
return 1; /* return success */
}
That's basically it. How you parse the information from each line is up to you, but for purposes of my example, given your sample data, I simply used sscanf
for simplicity. Putting it altogether, you could do something like the following:
#include <stdio.h>
#include <stdlib.h>
/* if you need constants, either #define them or use an enum */
enum { ABRV = 2, NDATA = 8, LOC = 13, NAME = 15, MAXC = 1024 };
typedef struct {
char name[NAME+1],
abrv[ABRV+1];
} stateabrv_t;
typedef struct { /* struct holding only climate data */
long long millitime;
char location[LOC];
double humidity;
double snow;
double cloud;
double lightning;
long double pressure;
double temperature;
} climate_t;
typedef struct {
size_t num_allocated, /* track of how many data are allocated */
num_records;
climate_t *data; /* a pointer to allocated block for data */
} statedata_t;
const stateabrv_t state[] = { { "Alabama", "AL" },
{ "Alaska", "AK" },
{ "Arizona", "AZ" },
{ "Arkansas", "AR" },
{ "California", "CA" },
{ "Colorado", "CO" },
{ "Connecticut", "CT" },
{ "Delaware", "DE" },
{ "Florida", "FL" },
{ "Georgia", "GA" },
{ "Hawaii", "HI" },
{ "Idaho", "ID" },
{ "Illinois", "IL" },
{ "Indiana", "IN" },
{ "Iowa", "IA" },
{ "Kansas", "KS" },
{ "Kentucky", "KY" },
{ "Louisiana", "LA" },
{ "Maine", "ME" },
{ "Maryland", "MD" },
{ "Massachusetts", "MA" },
{ "Michigan", "MI" },
{ "Minnesota", "MN" },
{ "Mississippi", "MS" },
{ "Missouri", "MO" },
{ "Montana", "MT" },
{ "Nebraska", "NE" },
{ "Nevada", "NV" },
{ "New Hampshire", "NH" },
{ "New Jersey", "NJ" },
{ "New Mexico", "NM" },
{ "New York", "NY" },
{ "North Carolina", "NC" },
{ "North Dakota", "ND" },
{ "Ohio", "OH" },
{ "Oklahoma", "OK" },
{ "Oregon", "OR" },
{ "Pennsylvania", "PA" },
{ "Rhode Island", "RI" },
{ "South Carolina", "SC" },
{ "South Dakota", "SD" },
{ "Tennessee", "TN" },
{ "Texas", "TX" },
{ "Utah", "UT" },
{ "Vermont", "VT" },
{ "Virginia", "VA" },
{ "Washington", "WA" },
{ "West Virginia", "WV" },
{ "Wisconsin", "WI" },
{ "Wyoming", "WY" } };
const int nstates = sizeof state / sizeof *state;
/* simple lookup function, given a code s, return index for state
* in array of statedata_t on success, -1 otherwise.
*/
int lookupabrv (const char *s)
{
int i = 0;
for (; i < nstates; i++)
if (state[i].abrv[0] == s[0] && state[i].abrv[1] == s[1])
return i;
return -1;
}
/* insert data for state given code and climate_t containing data */
int insert_data (statedata_t *st, const char *code, climate_t *data)
{
int index = lookupabrv (code); /* lookup array index */
if (index == -1) /* handle error */
return 0;
if (!st[index].num_allocated) { /* allocate data if not allocated */
st[index].data = malloc (NDATA * sizeof *st[index].data);
if (!st[index].data) {
perror ("malloc-st[index].data");
return 0;
}
st[index].num_allocated = NDATA;
}
/* check if realloc needed */
if (st[index].num_records == st[index].num_allocated) {
/* realloc here, update num_allocated */
}
/* add data for proper state index */
st[index].data[st[index].num_records++] = *data;
return 1; /* return success */
}
/* print states with data collected */
void print_data (statedata_t *st)
{
int i = 0;
for (; i < nstates; i++) {
if (st[i].num_records) {
size_t j = 0;
printf ("\n%s\n", state[i].name);
for (; j < st[i].num_records; j++)
printf (" %13lld %-12s %5.1f %5.1f %5.1f %5.1f %8.1Lf "
"%8.4f\n",
st[i].data[j].millitime, st[i].data[j].location,
st[i].data[j].humidity, st[i].data[j].snow,
st[i].data[j].cloud, st[i].data[j].lightning,
st[i].data[j].pressure, st[i].data[j].temperature);
}
}
}
/* free allocated memory */
void free_data (statedata_t *st)
{
int i = 0;
for (; i < nstates; i++)
if (st[i].num_records)
free (st[i].data);
}
int main (int argc, char **argv) {
char buf[MAXC]; /* line buffer */
/* array of 50 statedata_t (one for each state) */
statedata_t stdata[sizeof state / sizeof *state] = {{.num_records = 0}};
/* read from file given as argument (or stdin if none given) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line of data */
char code[ABRV+1] = ""; /* declare storage for abriviation */
climate_t tmp = { .millitime = 0 }; /* declare temp stuct for data */
/* simple parse of data with sscanf */
if (sscanf (buf, "%2s %lld %12s %lf %lf %lf %lf %Lf %lf", code,
&tmp.millitime, tmp.location, &tmp.humidity, &tmp.snow,
&tmp.cloud, &tmp.lightning, &tmp.pressure, &tmp.temperature)
== 9) {
if (!insert_data (stdata, code, &tmp)) /* insert data/validate */
fprintf (stderr, "error: insert_data failed (%s).\n", code);
}
else /* handle error */
fprintf (stderr, "error: invalid format:\n%s\n", buf);
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
print_data (stdata); /* print data */
free_data (stdata); /* free allocated memory */
return 0;
}
Example Input File
$ cat dat/state_climate.txt
TN 1424325600000 dn20t1kz0xrz 67.0 0.0 0.0 0.0 101872.0 262.5665
TN 1422770400000 dn2dcstxsf5b 23.0 0.0 100.0 0.0 100576.0 277.8087
TN 1422792000000 dn2sdp6pbb5b 96.0 0.0 100.0 0.0 100117.0 278.49207
TN 1422748800000 dn2fjteh8e80 6.0 0.0 100.0 0.0 100661.0 278.28485
TN 1423396800000 dn2k0y7ffcup 14.0 0.0 100.0 0.0 100176.0 282.02142
Example Use/Output
$ ./bin/state_climate <dat/state_climate.txt
Tennessee
1424325600000 dn20t1kz0xrz 67.0 0.0 0.0 0.0 101872.0 262.5665
1422770400000 dn2dcstxsf5b 23.0 0.0 100.0 0.0 100576.0 277.8087
1422792000000 dn2sdp6pbb5b 96.0 0.0 100.0 0.0 100117.0 278.4921
1422748800000 dn2fjteh8e80 6.0 0.0 100.0 0.0 100661.0 278.2849
1423396800000 dn2k0y7ffcup 14.0 0.0 100.0 0.0 100176.0 282.0214
Memory Use/Error Check
In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind
is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/state_climate <dat/state_climate.txt
==6157== Memcheck, a memory error detector
==6157== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==6157== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==6157== Command: ./bin/state_climate
==6157==
Tennessee
1424325600000 dn20t1kz0xrz 67.0 0.0 0.0 0.0 101872.0 262.5665
1422770400000 dn2dcstxsf5b 23.0 0.0 100.0 0.0 100576.0 277.8087
1422792000000 dn2sdp6pbb5b 96.0 0.0 100.0 0.0 100117.0 278.4921
1422748800000 dn2fjteh8e80 6.0 0.0 100.0 0.0 100661.0 278.2849
1423396800000 dn2k0y7ffcup 14.0 0.0 100.0 0.0 100176.0 282.0214
==6157==
==6157== HEAP SUMMARY:
==6157== in use at exit: 0 bytes in 0 blocks
==6157== total heap usage: 1 allocs, 1 frees, 768 bytes allocated
==6157==
==6157== All heap blocks were freed -- no leaks are possible
==6157==
==6157== For counts of detected and suppressed errors, rerun with: -v
==6157== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
Look things over and consider why the change in the structs makes sense. Let me know if you have any questions.