Я пытаюсь реализовать программу, которая считывает данные из файла с именем grad.dat. Каждая запись (т. е. каждая строка) в файле данных содержит статистику для данной страны, года, степени, пола и т. д. Записи имеют следующий формат:
Примечание: существует только 3 различных типа степеней — L6, L7 и L8.
Общий формат град.дат:
AUS Australia F L6 2010 1276
AUS Australia M L6 2010 5779
AUS Australia F L6 2011 1255
AUS Australia M L6 2011 5739
BEL Belgium F L6 2017 157
BEL Belgium M L6 2017 1665
BEL Belgium F L7 2010 61
BEL Belgium F L8 2016 0
BEL Belgium F L8 2017 1
BEL Belgium M L8 2017 13
BRA Brazil F L6 2010 7187
BRA Brazil M L6 2010 32173
BRA Brazil F L6 2011 6240
BRA Brazil M L6 2011 30527
BRA Brazil M L6 2014 30156
BRA Brazil M L6 2016 32443
CAN Canada F L6 2010 561
CAN Canada F L6 2012 599
CAN Canada M L6 2012 3018
Я пытаюсь использовать эти данные для создания серии отчетов. Я застрял на первом отчете, где я должен включить:
Процент выпускников для каждой страны по степени, для всех лет и всех полов
а. каждая страна будет строкой, и будет столбец для каждого типа степени, плюс один столбец для все степени вместе взятые
б. каждая ячейка будет показывать долю выпускников в этой конкретной стране для этой конкретной степени, по сравнению с общим количеством выпускников во всех странах вместе взятых для этой степени
В общем, я хочу, чтобы первый отчет был напечатан так:
Country L6 L7 L8 Total
----------------------------------------------------
Country#1 % % %
Country#2 .....
Country#3
Моя программа компилируется. Мне нужно хранить данные о выпускниках в связанном списке, что, я думаю, я сделал правильно, но у меня возникли проблемы с выяснением того, как найти общее количество выпускников для всех степеней в конкретной стране, а также общее количество выпускников во всех странах вместе взятых для этой степени. Моя попытка сделать это находится в report.c, и я думаю, что у меня есть правильная идея объявить переменные для каждой степени (L6, L7, L8), а затем добавить количество градаций к переменной, если степень записи в файле соответствует переменной . Я также думаю, что у меня есть правильное представление о том, как найти общее количество выпускников в конкретной стране, выполнив totalAllDegreesForCountry = L6+L7+L8.
Я сталкиваюсь с проблемами, когда он продолжает печатать одну и ту же страну несколько раз, поскольку в файле есть несколько записей. Я пытаюсь понять, как печатать каждую страну только один раз. Моя программа также, кажется, печатает все записи, а затем в самом конце seg fault, и я не уверен, почему.
Я думаю, что статистика тоже может быть отключена, так как она печатает значения 1,00 и 0,00.
Я был бы очень признателен за помощь в этом или толчок в правильном направлении, чтобы мой первый отчет печатался правильно.
defs.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STR 32
typedef struct {
char code[MAX_STR];
char country[MAX_STR];
char gender[MAX_STR];
char degree[MAX_STR];
int year;
int numberOfGrads;
} DataType;
typedef struct Node {
DataType *data;
struct Node *next;
} NodeType;
typedef struct {
int size;
NodeType *head;
NodeType *tail;
} ListType;
void initData(DataType**, char*, char*, char*, char*, int, int);
void printData(const DataType*);
void addDataToList(ListType *list, DataType *m);
void printList(ListType*);
void reportOne(ListType*);
main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "defs.h"
int main()
{
FILE *infile;
DataType *data;
ListType *list ;
char code[MAX_STR];
char country[MAX_STR];
char gender[MAX_STR];
char degree[MAX_STR];
int year;
int numberOfGrads;
char input[MAX_STR];
list->size = 0;
list->head = NULL;
list->tail = NULL;
infile = fopen("grad.dat", "r");
if (!infile) {
printf("Error: could not open file\n");
exit(1);
}
while (1) {
fscanf(infile, "%s %s %s %s %d %d ", code, country, gender, degree, &year, &numberOfGrads);
initData(&data, code, country, gender, degree, year, numberOfGrads);
addDataToList(list, data);
if (feof(infile))
break;
}
fclose(infile);
while (1){
printf(" 0. Quit \n");
printf(" 1 = Top 5 and bottom 5 countries of female graduates \n");
printf("Enter a selection: ");
scanf("%s", input);
if (strcmp(input, "1")==0){
reportOne(list);
}else if (strcmp(input, "0")==0){
break;
}
}
}
void initData(DataType **r, char *co, char *c, char *g, char *d, int y, int n){
*r = malloc(sizeof(DataType));
strcpy((*r)->code, co);
strcpy((*r)->country, c);
strcpy((*r)->gender, g);
strcpy((*r)->degree, d);
(*r)->year = y;
(*r)->numberOfGrads = n;
}
void printData(const DataType *data){
printf("%s %s %s %s %d %d\n", data->code, data->country, data->gender, data->degree, data->year, data->numberOfGrads);
}
void addDataToList(ListType *list, DataType *m){
NodeType *currNode;
NodeType *prevNode;
NodeType *newNode;
prevNode=NULL;
int currPos = 0;
currNode = list->head;
while (currNode != NULL) {
prevNode = currNode;
currNode = currNode->next;
}
newNode = malloc(sizeof(NodeType));
newNode->data = m;
newNode->prev = NULL;
newNode->next = NULL;
if (prevNode == NULL)
list->head = newNode;
else
prevNode->next = newNode;
if (currNode == NULL)
list->tail = newNode;
newNode->next = currNode;
newNode->prev = prevNode;
if (currNode != NULL)
currNode->prev = newNode;
list->size++;
}
}
void printList(ListType* list){
NodeType *currNode = list->head;
while(currNode != NULL){
printData(currNode->data);
currNode = currNode->next;
}
}
отчеты.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "defs.h"
void reportOne(ListType* list){
NodeType *currNode = list->head;
float L6 = 0;
float L7 = 0;
float L8 = 0;
float totalGradsCountry = 0; //specific country
float totalGradsAll = 0; //all degrees all countries
printf("%15s %10s %10s %10s %10s \n", "Country", "L6","L7","L8", "Total");
printf("-------------------------------------------------------- \n");
while (currNode != NULL) {
if (strcmp(currNode->data->country, currNode->next->data->country) == 0){
if (strcmp(currNode->data->degree, "L6")==0){
L6 += currNode->data->numberOfGrads;
}else if (strcmp(currNode->data->degree,"L7")==0){
L7 += currNode->data->numberOfGrads;
}else if (strcmp(currNode->data->degree,"L8")==0){
L8 += currNode->data->numberOfGrads;
}
totalGradsCountry = L6 + L7 + L8;
totalGradsAll += totalGradsCountry;
printf("%-15s %6.2f %6.2f %6.2f %7.2f \n", currNode->data->country, L6/totalGradsCountry, L7/totalGradsCountry, L8/totalGradsCountry, totalGradsCountry/totalGradsAll);
}
currNode = currNode->next;
}
}
Я прикрепил 2 изображения ниже моего вывода, чтобы показать, что я имею в виду с проблемами, с которыми я сталкиваюсь.
Ах, извините, это была старая версия, которую я забыл отредактировать. Я обновил код с правильными параметрами в defs.h
Также list->size = 0;
, где list
— неинициализированный указатель, который может привести к SegFault при разыменовании. main.c
~ строка 19. Вы также можете просто объявить ListType list;
(или выделить для него - на ваш выбор)
Вам захочется посмотреть Почему while ( !feof (file) ) всегда неправильно?
Хорошо, ваша основная проблема заключается в разыгрывании NULL
в reports.c
по адресу:
while (currNode != NULL) {
if (strcmp (currNode->data->country, currNode->next->data->country) ==
на вашем последнем узле указатель node->next
равен NULL
, но вы пытаетесь получить доступ к currNode->next->...
Boom! Ошибка Сегмента!
Вам просто нужно использовать currNode->next
в своем условном выражении, например.
while (currNode->next != NULL) {
if (strcmp (currNode->data->country, currNode->next->data->country) ==
Далее ссылка Почему while ( !feof (file) ) всегда неправильный? объясняет, почему ваш цикл чтения вызовет проблемы. Всегда контролируйте свой цикл чтения с возвратом функции ввода, например.
while (fscanf (infile, "%s %s %s %s %d %d ", code, country, gender, degree,
&year, &numberOfGrads) == 6) {
if (!(data = initData (code, country, gender, degree, year, numberOfGrads)))
return 1;
addDataToList (list, data);
}
Если вы заметили, объявление для initData
изменилось. У вас нет действительного способа указать успех или неудачу mailloc
в initData
, прежде чем вы начнете слепо использовать *r
. Вместо этого в любой функции, которая выделяет память, принимает пользовательский ввод и т. д. (любой функции, которая имеет решающее значение для непрерывной работы вашего кода), вам нужно выбрать осмысленный тип возвращаемого значения, который вы можете использовать для обозначения успеха или неудачи вашей функции.
Нет необходимости передавать адрес указателя data
в качестве параметра. Вместо этого объявите r
локальным для вашей функции, а затем верните NULL
в случае ошибки или выделенный блок для назначения обратно в вызывающем объекте. Вы можете изменить прототип на:
DataType *initData (char*, char*, char*, char*, int, int);
а затем просто вернуть функцию NULL
в случае ошибки или r
в случае успеха, например.
DataType *initData (char *co, char *c, char *g, char *d, int y, int n)
{
DataType *r = malloc (sizeof (DataType));
if (!r) {
perror ("malloc-initData");
return NULL;
}
strcpy (r->code, co);
strcpy (r->country, c);
strcpy (r->gender, g);
strcpy (r->degree, d);
r->year = y;
r->numberOfGrads = n;
return r;
}
Наконец, ваша функция addDataToList()
страдает тем же типом void
и не может указывать успех/неудача malloc()
(я позволю вам переписать это). В то же время вы должны защитить от ошибки нехватки памяти. Вы можете еще больше сократить процесс добавления узла в свой список. Выполняя оба действия и используя указатель tail
для того, для чего должен использоваться указатель tail
, вы можете сделать:
void addDataToList (ListType * list, DataType * m)
{
NodeType *newNode = malloc (sizeof (NodeType));
if (!newNode) {
perror ("malloc-newNode");
exit (EXIT_FAILURE);
}
newNode->data = m;
newNode->prev = NULL;
newNode->next = NULL;
if (!list->head)
list->head = list->tail = newNode;
else {
newNode->prev = list->tail;
list->tail->next = newNode;
list->tail = newNode;
}
list->size++;
}
Используйте Header-Guards для предотвращения многократного включения заголовка
При написании файла заголовка вы хотите, чтобы он был включен в компиляцию только один раз (это может вызвать проблемы при включении несколько раз, например, переопределение X
и т. д.). Чтобы гарантировать, что заголовок включен только один раз, вы заключаете содержимое в
#infdef SOME_HEADER_LABEL_H
#define SOME_HEADER_LABEL_H 1
...
your header content
...
#endif
Таким образом, при первом включении компилятор проверяет, определен ли SOME_HEADER_LABEL_H
, и если нет, он определяет его с помощью #define SOME_HEADER_LABEL_H 1
, так что при следующем включении он просто пропустит содержимое. (1
не является строго обязательным — его можно опустить, но если вы собираетесь определить константу, вы также можете присвоить ей значение). Для вашего заголовка это может быть:
#ifndef DEFS_H
#define DEFS_H 1
#define MAX_STR 32
typedef struct {
char code[MAX_STR];
char country[MAX_STR];
char gender[MAX_STR];
char degree[MAX_STR];
int year;
int numberOfGrads;
} DataType;
typedef struct Node {
DataType *data;
struct Node *prev;
struct Node *next;
} NodeType;
typedef struct {
int size;
NodeType *head;
NodeType *tail;
} ListType;
// void initData(DataType**, char*, char*, char*, char*, int, int);
DataType *initData (char*, char*, char*, char*, int, int);
void printData(const DataType*);
void addDataToList (ListType*, DataType*);
void reportOne(ListType*);
#endif
Не нужно выделять для data
В main()
На второй взгляд, нет необходимости выделять DataType *data;
в main()
вообще. Это временно, поэтому просто объявите его с автоматическим типом хранения и повторно используйте его для всех входных данных, но вам нужно будет выделить newNode->data
в addDataToList
и memcpy
, а не назначать m
. Так что любой способ в порядке. (этот код показан с комментариями в обновлении, связанном с выводом отчета, показанным в редактировании ниже)
Избегайте MagicNumbers и жестко закодированных имен файлов
В вашем коде вы жестко задаете "grad.dat"
в качестве имени файла для чтения. Это означает, что вы должны перекомпилировать свой код каждый раз, когда хотите прочитать из другого файла. Вместо этого передайте имя файла для чтения в качестве аргумента вашей программе, используя int main (int argc, char **argv)
— для этого и нужны параметры main()
. Вы можете использовать простой троичный код для чтения из имени файла, указанного в качестве аргумента, или, если аргумент не указан, читать из "grad.dat"
по умолчанию. Это нормально, но определите константу для имени файла, например.
#define DATAFILE "grad.dat"
...
/* do not hardcode filenames, you can use it as a default */
infile = fopen (argc > 1 ? argv[1] : DATAFILE, "r");
if (!infile) { /* good job validating fopen */
printf ("Error: could not open file\n");
exit (1);
}
Изготовление reportOne()
Обобщить по странам
Это потребовало немного больше размышлений, чтобы прийти к этому. Есть несколько способов сделать это. Вы либо используете немного более сложную структуру данных для хранения сводных данных по каждой стране вместе со значением totalGradsCountry
, либо сохраняете только сводку данных по стране, а затем должны дважды просмотреть свой список в reportOne()
, один раз для суммирования totalGradsCountry
и затем еще раз, чтобы вывести результаты, используя ваш totalGradsCountry
.
По сути, для сбора информации при просмотре списка за один раз требуется построить массив уникальных стран вместе с суммами L6
, L7
и L8
, захваченными вместе с country
(имя) и, возможно, code
(уменьшает strcmp
для определения страна в узле). Таким образом, вы можете использовать что-то вроде:
#define NSUM 2 /* initial no. of countrytype to allocate */
typedef struct { /* struct to hold summary of country data */
char code[MAX_STR],
country[MAX_STR];
double L6,
L7,
L8;
} countrytype;
typedef struct { /* struct to hold array of countries and totalgrads */
countrytype *countries;
double totalGradsAll;
} totaltype;
Затем нужно просмотреть весь список и для каждой уникальной страны выделить хранилище для countrytype
и на первом узле для этой страны скопировать информацию code
и country
, а для всех оставшихся записей для этой страны добавить в L6
, L7
и L8
значений и к сумме totalGradsAll
. При выделении (и перераспределении) для countrytype
обнуление памяти позволяет вам проверить, является ли code
пустой строкой, чтобы узнать, есть ли у вас первое вхождение этой страны.
Функция reportOne()
становится:
void reportOne (ListType *list)
{
size_t used = 0, avail = NSUM; /* number used, number allocated */
totaltype total = { .countries = calloc (avail, sizeof(countrytype)) };
NodeType *node = list->head;
if (!total.countries) { /* validate initial allocation */
perror ("calloc-.countries");
return;
}
while (node) { /* loop over each node in list */
/* check if country code exists in summary list of countries */
size_t ndx = codeexists (total.countries, node->data->code, &used);
if (used == avail) /* check if realloc needed */
total.countries = addcountry (total.countries, &avail);
addtosum (&total, ndx, node->data); /* add current node info to summary */
node = node->next; /* advance to next node */
}
puts ( "\nCountry L6 L7 L8 Total\n"
"-------------------------------------------------------");
for (size_t i = 0; i < used; i++) { /* loop over countries in summary */
/* sum L6 + L7 + L8 for country */
double totalGradsCountry = total.countries[i].L6 + total.countries[i].L7 +
total.countries[i].L8;
/* output results */
printf ("%-15s %6.2f %6.2f %6.2f %7.2f\n",
total.countries[i].country,
total.countries[i].L6 / totalGradsCountry,
total.countries[i].L7 / totalGradsCountry,
total.countries[i].L8 / totalGradsCountry,
totalGradsCountry / total.totalGradsAll);
}
free (total.countries); /* free all memory allocated for summary */
}
Теперь вы видите несколько вспомогательных функций, которые, помещая их в функции, помогают сделать вашу reportOne
логику читабельной. Помощниками являются codeexists()
(например, этот код страны уже существует в блоке countrytype
). Если он возвращает index
стране в выделенном блоке countrytype
, вы можете просто обновить L6
, L7
и L8
в этом индексе. Если индекс не существует, это новая страна, которую вы добавляете в свой счет used
, чтобы отслеживать, когда требуется перераспределение. Это простая функция:
/* returns index of country matching code or next available index,
* incrementing the value at n (used) for each new index returned.
*/
size_t codeexists (const countrytype *c, const char *code, size_t *n)
{
size_t i;
for (i = 0; i < *n; i++) /* loop over countries */
if (strcmp (c[i].code, code) == 0) /* if codes match, break to return index */
break;
if (i == *n) /* if code not found in countries */
*n += 1; /* new country found, increment n (used) */
return i; /* return index */
}
Следующим помощником является addcountry()
, который, когда ваш used == avail
(используемые блоки для стран равны выделенному количеству), требуется перераспределение, и это то, что делает addcountry()
, возвращая указатель на вновь перераспределенный блок для назначения (или выхода в случае ошибки), например
/* reallocate countries when used == available,
* new memory is zeroed for 1st char check in addtosum,
* program exits on memory failure.
*/
countrytype *addcountry (countrytype *c, size_t *avail)
{
/* always realloc using a temporary pointer */
void *tmp = realloc (c, 2 * *avail * sizeof *c);
if (!tmp) { /* validate reallocation */
perror ("realloc-countrytype");
exit (EXIT_FAILURE);
}
c = tmp; /* assing realloc'ed block to ptr */
memset (c + *avail, 0, *avail * sizeof *c); /* zero newly allocated memory */
*avail *= 2; /* update no. of countries avail */
return c; /* return pointer for assignment in caller */
}
Последний помощник — это addtosum()
, который просто копирует имена code
и country
при необходимости и обновляет L6
, L7
и L8
для заданного индекса,
/* add current nodes information to the country and total sums */
void addtosum (totaltype *t, size_t ndx, DataType *d)
{
if (!*t->countries[ndx].code) { /* if new country, copy info */
strcpy (t->countries[ndx].code, d->code);
strcpy (t->countries[ndx].country, d->country);
}
switch (d->degree[1]) { /* switch on 2nd character */
case '6': t->countries[ndx].L6 += d->numberOfGrads; break;
case '7': t->countries[ndx].L7 += d->numberOfGrads; break;
case '8': t->countries[ndx].L8 += d->numberOfGrads; break;
default : fputs ("error: addtosum switch -- we shouldn't be here\n", stderr);
return;
}
t->totalGradsAll += d->numberOfGrads; /* add to total for all */
}
switch()
используется выше на втором символе degree
, например. 6
, 7
или 8
для обновления до соответствующего значения без необходимости каждый раз вызывать strcmp()
.
Собираем все вместе
Это необходимые изменения. Таким образом, ваш defs.h
становится:
#ifndef DEFS_H
#define DEFS_H 1
#define MAX_STR 32
typedef struct {
char code[MAX_STR];
char country[MAX_STR];
char gender[MAX_STR];
char degree[MAX_STR];
int year;
int numberOfGrads;
} DataType;
typedef struct Node {
DataType *data;
struct Node *prev;
struct Node *next;
} NodeType;
typedef struct {
int size;
NodeType *head;
NodeType *tail;
} ListType;
// void initData (DataType*, char*, char*, char*, char*, int, int);
DataType *initData (char*, char*, char*, char*, int, int);
void printData (const DataType*);
void addDataToList (ListType*, DataType*);
void reportOne (ListType*);
// void reportTwo (ListType*);
#endif
Ваш main.c
(с закомментированным кодом, показывающим использование data
с автоматическим сроком хранения) становится:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "defs.h"
#define DATAFILE "grad.dat"
int main (int argc, char **argv)
{
FILE *infile;
DataType *data;
ListType *list = malloc (sizeof *list);
char code[MAX_STR];
char country[MAX_STR];
char gender[MAX_STR];
char degree[MAX_STR];
int year;
int numberOfGrads;
char input[MAX_STR];
if (!list) { /* validate EVERY allocation */
perror ("malloc-list");
return 1;
}
list->size = 0;
list->head = NULL;
list->tail = NULL;
/* do not hardcode filenames, you can use it as a default */
infile = fopen (argc > 1 ? argv[1] : DATAFILE, "r");
if (!infile) { /* good job validating fopen */
printf ("Error: could not open file\n");
exit (1);
}
/* always control read-loop with return of the input function itself */
while (fscanf (infile, "%s %s %s %s %d %d ", code, country, gender, degree,
&year, &numberOfGrads) == 6) {
if (!(data = initData (code, country, gender, degree, year, numberOfGrads)))
return 1;
addDataToList (list, data);
}
fclose (infile);
while (1) {
fputs ( "\n 0. Quit \n"
" 1 = Top 5 and bottom 5 countries of female graduates\n"
"Enter a selection: ", stdout);
if (scanf ("%s", input) != 1) {
puts ("(user canceled input)");
return 1;
}
if (strcmp (input, "1") == 0) {
reportOne (list);
}
else if (strcmp (input, "0") == 0) {
break;
}
}
}
DataType *initData (char *co, char *c, char *g, char *d, int y, int n)
{
DataType *r = malloc (sizeof (DataType));
if (!r) {
perror ("malloc-initData");
return NULL;
}
strcpy (r->code, co);
strcpy (r->country, c);
strcpy (r->gender, g);
strcpy (r->degree, d);
r->year = y;
r->numberOfGrads = n;
return r;
}
/* initData for use of data with automatic storage in main */
// void initData (DataType *r, char *co, char *c, char *g, char *d, int y, int n)
// {
// strcpy (r->code, co);
// strcpy (r->country, c);
// strcpy (r->gender, g);
// strcpy (r->degree, d);
// r->year = y;
// r->numberOfGrads = n;
// }
void printData (const DataType * data)
{
printf ("%s %s %s %s %d %d\n", data->code, data->country, data->gender,
data->degree, data->year, data->numberOfGrads);
}
void addDataToList (ListType * list, DataType * m)
{
NodeType *newNode = malloc (sizeof (NodeType));
if (!newNode) {
perror ("malloc-newNode");
exit (EXIT_FAILURE);
}
/* allocation and copy if using data with automatic storage in main */
// if (!(newNode->data = malloc (sizeof *newNode->data))) {
// perror ("malloc-newNode->data");
// exit (EXIT_FAILURE);
// }
// memcpy (newNode->data, m, sizeof *m);
newNode->data = m;
newNode->prev = NULL;
newNode->next = NULL;
if (!list->head)
list->head = list->tail = newNode;
else {
newNode->prev = list->tail;
list->tail->next = newNode;
list->tail = newNode;
}
list->size++;
}
Наконец обновленный reports.c
становится:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "defs.h"
#define NSUM 2 /* initial no. of countrytype to allocate */
typedef struct { /* struct to hold summary of country data */
char code[MAX_STR],
country[MAX_STR];
double L6,
L7,
L8;
} countrytype;
typedef struct { /* struct to hold array of countries and totalgrads */
countrytype *countries;
double totalGradsAll;
} totaltype;
/* returns index of country matching code or next available index,
* incrementing the value at n (used) for each new index returned.
*/
size_t codeexists (const countrytype *c, const char *code, size_t *n)
{
size_t i;
for (i = 0; i < *n; i++) /* loop over countries */
if (strcmp (c[i].code, code) == 0) /* if codes match, break to return index */
break;
if (i == *n) /* if code not found in countries */
*n += 1; /* new country found, increment n (used) */
return i; /* return index */
}
/* reallocate countries when used == available,
* new memory is zeroed for 1st char check in addtosum,
* program exits on memory failure.
*/
countrytype *addcountry (countrytype *c, size_t *avail)
{
/* always realloc using a temporary pointer */
void *tmp = realloc (c, 2 * *avail * sizeof *c);
if (!tmp) { /* validate reallocation */
perror ("realloc-countrytype");
exit (EXIT_FAILURE);
}
c = tmp; /* assing realloc'ed block to ptr */
memset (c + *avail, 0, *avail * sizeof *c); /* zero newly allocated memory */
*avail *= 2; /* update no. of countries avail */
return c; /* return pointer for assignment in caller */
}
/* add current nodes information to the country and total sums */
void addtosum (totaltype *t, size_t ndx, DataType *d)
{
if (!*t->countries[ndx].code) { /* if new country, copy info */
strcpy (t->countries[ndx].code, d->code);
strcpy (t->countries[ndx].country, d->country);
}
switch (d->degree[1]) { /* switch on 2nd character */
case '6': t->countries[ndx].L6 += d->numberOfGrads; break;
case '7': t->countries[ndx].L7 += d->numberOfGrads; break;
case '8': t->countries[ndx].L8 += d->numberOfGrads; break;
default : fputs ("error: addtosum switch -- we shouldn't be here\n", stderr);
return;
}
t->totalGradsAll += d->numberOfGrads; /* add to total for all */
}
void reportOne (ListType *list)
{
size_t used = 0, avail = NSUM; /* number used, number allocated */
totaltype total = { .countries = calloc (avail, sizeof(countrytype)) };
NodeType *node = list->head;
if (!total.countries) { /* validate initial allocation */
perror ("calloc-.countries");
return;
}
while (node) { /* loop over each node in list */
/* check if country code exists in summary list of countries */
size_t ndx = codeexists (total.countries, node->data->code, &used);
if (used == avail) /* check if realloc needed */
total.countries = addcountry (total.countries, &avail);
addtosum (&total, ndx, node->data); /* add current node info to summary */
node = node->next; /* advance to next node */
}
puts ( "\nCountry L6 L7 L8 Total\n"
"-------------------------------------------------------");
for (size_t i = 0; i < used; i++) { /* loop over countries in summary */
/* sum L6 + L7 + L8 for country */
double totalGradsCountry = total.countries[i].L6 + total.countries[i].L7 +
total.countries[i].L8;
/* output results */
printf ("%-15s %6.2f %6.2f %6.2f %7.2f\n",
total.countries[i].country,
total.countries[i].L6 / totalGradsCountry,
total.countries[i].L7 / totalGradsCountry,
total.countries[i].L8 / totalGradsCountry,
totalGradsCountry / total.totalGradsAll);
}
free (total.countries); /* free all memory allocated for summary */
}
Примечание. Вся память, используемая в reportOne()
, перед возвратом освобождается. Вы должны убедиться, что вы очищаете оставшуюся часть кода.
Не торопитесь, чтобы разобраться, как структуры countrytype
и totaltype
используются для получения окончательного результата. Там много всего, так что просто притормози, пройди построчно и следуй до конца.
Пример использования/вывода
С предоставленными ограниченными примерными данными вы получите:
$ ./bin/main
0. Quit
1 = Top 5 and bottom 5 countries of female graduates
Enter a selection: 1
Country L6 L7 L8 Total
-------------------------------------------------------
Australia 1.00 0.00 0.00 0.09
Belgium 0.96 0.03 0.01 0.01
Brazil 1.00 0.00 0.00 0.87
Canada 1.00 0.00 0.00 0.03
0. Quit
1 = Top 5 and bottom 5 countries of female graduates
Enter a selection:
Что, учитывая значения в выборке данных, выглядит так, как и следовало ожидать. Подтверждение остается за вами.
Ваш код должен нормально работать с этими изменениями. Если это не так, оставьте комментарий, возможно, я забыл одно или два изменения. Дайте мне знать, если у вас есть дополнительные вопросы.
Привет! Большое вам спасибо, это избавило от ошибки seg :) Моя единственная другая проблема, которую я пытаюсь понять, это как печатать каждую страну только один раз с их статистикой вместо того, чтобы печатать одну и ту же страну 100 раз, как в моих выходных изображениях на основе по количеству записей, связанных со страной. Я думаю, что вся моя математическая логика для reportOne() отключена. Значения для каждой степени L6, L7 и L8 в сумме составляют 100%, что имеет смысл, но в некоторых столбцах L6 печатается 100%, а в других столбцах печатается 0%, хотя в файле есть тысячи выпускников для каждой степени.
У меня впереди еще одно редактирование — ждите его :)
Я рассмотрю печать каждой страны только один раз.
Объясните
void addDataToList(NodeType**, DataType*);
вdefs.h
иvoid addDataToList (ListType * list, DataType * m)
вmain.c
? (обратите внимание на разницу в типах параметров) Вы должны включить защиту заголовков вdefs.h
, чтобы предотвратить многократное включение. Вы можете удалить заголовки изdefs.h