From df7aa5d5200b5d8ccc6d94e811bc8890ccd69c49 Mon Sep 17 00:00:00 2001 From: Vinicius Gabriel Date: Wed, 21 Sep 2022 17:21:02 -0300 Subject: [PATCH 1/2] =?UTF-8?q?Adiciona=20quest=C3=A3o=20"Registro=20em=20?= =?UTF-8?q?C"=20do=20TP2=20(2022-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gabarito da questão "Registro em C" do TP2 (2022-2) 100% testado no verde (código 217136). --- tps/fonte/game.c | 721 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 721 insertions(+) create mode 100644 tps/fonte/game.c diff --git a/tps/fonte/game.c b/tps/fonte/game.c new file mode 100644 index 0000000..484b24a --- /dev/null +++ b/tps/fonte/game.c @@ -0,0 +1,721 @@ +// ------------------------------------------------------------------------------ // +// Author: Vinicius Gabriel dos Santos - https://github.com/ravixr +// Sep/2022 +// +// DESCRIPTION: +// In addition to the game data structure and +// the solution to the second question of the assignment, +// this file contains a basic implementation of a string type +// which was extensively used in the game data structure +// as well as in the solution algorithm. +// ------------------------------------------------------------------------------ // + +#include +#include +#include +#include +#include +#include + +#define GAME_FIELDS_SIZE 17 +#define MAX_FIELD_SIZE 500 +#define MAX_LINE_SIZE 1000 +#define MAX_GAMES 100 + +// If on windows remove first slash -> 'tmp/games.csv' +#define FILE_PATH "/tmp/games.csv" + +// Assert macro to print custom message when assertion fails +#define assert__(x) for (; !(x); assert(x)) + +// --------------------------- my_very_basic_string.h --------------------------- // + +typedef struct string_t string; + +typedef struct strarr_t strarr; + +struct string_t { + char *buf; + char (*char_at)(const string *, size_t); + int (*compare)(const string *, const string *); + void (*delete)(string *); + size_t (*find)(const string *, const char *); + size_t (*length)(const string *); + void (*replace)(string *, const char *, const char *); + strarr (*split)(const string *, const char *); + string (*substr)(const string *, size_t, size_t); + float (*to_float)(const string *); + int (*to_int)(const string *); + void (*trim)(string *); +}; + +struct strarr_t { + string *src; + size_t length; + void (*delete)(strarr *); +}; + +// string functions declaration +char str_char_at(const string *, size_t); +int str_compare(const string *, const string *); +void str_delete(string *); +size_t str_find(const string *, const char *); +size_t str_length(const string *); +void str_replace(string *, const char*, const char *); +string new_string(char *); +strarr str_split(const string *, const char *); +string str_substr(const string *, size_t, size_t); +float str_to_float(const string *); +int str_to_int(const string *); +void str_trim(string *); + +// strarr functions declaration +strarr new_strarr(string *, size_t); +void strarr_delete(strarr *); + +// --------------------------- my_very_basic_string.c --------------------------- // + +// Fuction to initialize string type +string new_string(char *src) +{ + string str; + if (src != NULL) { + if (strcmp(src, "") != 0) { + str.buf = malloc(sizeof(char) * (strlen(src) + 1)); + strcpy(str.buf, src); + } else { + str.buf = malloc(sizeof(char) * 5); + strcpy(str.buf, "null"); + } + } else str.buf = NULL; + str.char_at = &str_char_at; + str.compare = &str_compare; + str.delete = &str_delete; + str.find = &str_find; + str.replace = &str_replace; + str.length = &str_length; + str.split = &str_split; + str.substr = &str_substr; + str.to_float = &str_to_float; + str.to_int = &str_to_int; + str.trim = &str_trim; + return str; +} + +// Fuction to deallocate string type +void str_delete(string *self) +{ + free(self->buf); +} + +// Returns character present in the specified index of the string +// e.g: "foo" idx: 0 -> 'f' +char str_char_at(const string *self, size_t idx) +{ + // Macro to assert index is in bounds + assert__(idx < strlen(self->buf)) { + fprintf(stderr, "String index %ld is out of bounds\n", idx); + } + return self->buf[idx]; +} + +// Allows comparison between string type variables using strcmp in the raw data +int str_compare(const string *self, const string *another_str) +{ + return strcmp(self->buf, another_str->buf); +} + +// Searches the string for the first occurrence of the pattern specified. +// Returns the index position of the first character of the pattern +// or the length of the string if the pattern is not found +// e.g.: "foo bar", "bar" -> 4 | "foo bar", "baz" -> 7 +size_t str_find(const string *self, const char *lkf) +{ + size_t i = 0, + self_len = strlen(self->buf), + lkf_len = strlen(lkf); + + // Macro to assert pattern string is valid + assert__((strcmp(lkf, "") != 0) && (lkf_len <= self_len)) { + fprintf(stderr, "Pattern '%s' is invalid for the specified string\n", lkf); + } + + // Find algorithm + for (size_t j = 0; i < self_len; i++) { + if (self->buf[i] == lkf[j]) { + j++; + } else { + i-= j; j = 0; + } + if (j == lkf_len) { + i -= (j - 1); + break; + } + } + return i; +} + +// Returns the length of the string using strlen in the raw data +size_t str_length(const string *self) +{ + return strlen(self->buf); +} + +// Replaces all occurrences of specified pattern in the string with the new specified pattern +// if the string doesn't contains the pattern to be replaced the string is not changed +// e.g.: "foobar", "bar", "" -> "foo" | "foobar", "baz", "" -> "foobar" +void str_replace(string *self, const char *old_val, const char *new_val) +{ + // Saving useful lengths in variables + size_t old_len = strlen(self->buf), + nv_len = strlen(new_val), + ov_len = strlen(old_val); + + // Macro to assert given pattern is valid + assert__((strcmp(old_val, "") != 0) && (old_len >= ov_len)) { + fprintf(stderr, "Invalid pattern for argument 'old_val'\n"); + } + + // Calculating the max possible amount of patterns in the string + // then allocating an array to store their indexes + size_t idx_sz = (old_len / ov_len), + *idx = malloc(sizeof(size_t) * (idx_sz + 1)), + idx_i = 0, last_idx = 0, skip = 0; + + // Getting all starting indexes of where the pattern is found + string aux = new_string(NULL); + aux.buf = (self->buf); + while ((idx_i <= idx_sz) && (last_idx < old_len)) { + idx[idx_i] = str_find(&aux, old_val) + skip; + skip = idx[idx_i++] + ov_len; + aux.buf = (self->buf + skip); + last_idx = idx[idx_i - 1]; + } + + // Checking if the pattern is actually on the string + if (idx[0] != old_len) { + // Assuring 'idx_i' will not be 0 + idx_i > 1 ? (idx_i -= 1) : (idx_i = 1); + + // Calculating new length of the string then allocating + size_t new_len = (old_len + ((nv_len - ov_len) * idx_i)); + char *tmp = malloc(sizeof(char) * (new_len + 1)); + + // Main Replacement algorithm: copies old string char by char, + // when the index is equal to the range of the next found pattern + // starts copying the new pattern char by chat into the string + if (nv_len == 0) nv_len = 1; + for (size_t i = 0, j = 0, k = 0, l = 0; i < new_len;) { + if (k >= idx[l] && k < (idx[l] + ov_len)) { + if (strcmp(new_val, "") != 0) + tmp[i++] = new_val[j]; + j++; + if (j == nv_len) { + k += ov_len; + j = 0; l++; + } + } else tmp[i++] = self->buf[k++]; + } + tmp[new_len] = '\0'; // Adding null terminator in the new string + free(self->buf); // freeing old string + self->buf = tmp; // Pointing to new string + } + free(idx); // freeing array of indexes of the old string +} + +// Uses strtok to split the raw char array +// then build new strings with the splitted char arrays +// and bundle all of them in the strarr type +// e.g.: 'foo,bar' ',' -> ['foo', 'bar'] +strarr str_split(const string *self, const char *delim) +{ + char *str = malloc(sizeof(char) * (strlen(self->buf) + 1)); + strcpy(str, self->buf); + char *token = strtok(str, delim); + size_t count = 0; + strarr str_arr = new_strarr(NULL, 0); + string *tmp; + while(token != NULL) { + tmp = realloc(str_arr.src, sizeof(string) * (count + 1)); + if (tmp != NULL) { + tmp[count++] = new_string(token); + str_arr.src = tmp; + } + token = strtok(NULL, delim); + } + free(str); + free(token); + str_arr.length = count; + return str_arr; +} + +// Returns a new string that is a copy of the portion of the original string +// that starts at character position 'pos' and spans 'len' characters +// e.g.: "foo bar" pos: 4, len: 3 -> "bar" +string str_substr(const string *self, size_t pos, size_t len) +{ + // Macro to assert pos and len are in bounds + assert__((pos + len) <= strlen(self->buf)) { + fprintf(stderr, "String index %ld is out of bounds for '%s'\n", (pos + len), self->buf); + } + string substr = new_string(NULL); + substr.buf = malloc(sizeof(char) * (len + 1)); + for (size_t i = 0, j = 0; i < len; i++, j++) { + substr.buf[j] = self->buf[pos + i]; + } + substr.buf[len] = '\0'; + return substr; +} + +// Uses atof to convert string to float +float str_to_float(const string *self) +{ + return atof(self->buf); +} + +// Uses atoi to convert string to int +int str_to_int(const string *self) +{ + return atoi(self->buf); +} + +// Removes all spaces only from the beginning and ending of the string +// e.g.: " foo " -> "foo" | "foo bar" -> "foo bar" +void str_trim(string *self) +{ + size_t i = 0, j = (strlen(self->buf) - 1); + while (self->buf[i] == ' ' || self->buf[j] == ' ') { + if (self->buf[i] == ' ') i++; + if (self->buf[j] == ' ') j--; + } + string tmp = self->substr(self, i, (j - i) + 1); + free(self->buf); + self->buf = tmp.buf; +} + +// Fuction to initialize string array type +strarr new_strarr(string *src, size_t src_length) +{ + strarr str_arr; + if (src != NULL) + str_arr.src = src; + else { + src_length = (src_length > 0 ? src_length : 1); + str_arr.src = malloc(sizeof(string *)); + } + str_arr.length = src_length; + str_arr.delete = &strarr_delete; + return str_arr; +} + +// Fuction to deallocate string array type +void strarr_delete(strarr *self) +{ + for (size_t i = 0; i < self->length; i++) { + free(self->src[i].buf); + } + free(self->src); +} + +// ----------------------------------- game.h ----------------------------------- // + +typedef struct date_t date; + +struct date_t { + uint8_t day; + char month[4]; + unsigned year; + size_t value; +}; + +date new_date(string *); +unsigned strmon_to_int(const char *); + +typedef struct game_t game; + +struct game_t { + unsigned app_id; + string name; + date release_date; + string owners; + unsigned age; + float price; + unsigned dlcs; + strarr languages; + string website; + bool windows; + bool mac; + bool linux_; + float upvotes; + unsigned avg_pt; + string developers; + strarr genres; +}; + +game empty_game(void); +game new_game(string *); +game str_to_game(string *); +game chararr_to_game(char *); +void game_print(game *); +void game_read_csv(game *, size_t *, size_t); +char *avgpt_to_chararr (unsigned); + +// ----------------------------------- game.c ----------------------------------- // + +// Converts month abbreviated char array in format 'Mmm' +// to unsigned int, if unsuccessful returns 0 +// e.g.: "Aug" -> 8 | "aug" -> 0 +unsigned strmon_to_int(const char *mon) +{ + unsigned i = 0; + if (strcmp(mon, "Jan") == 0) { i = 1; } + else if (strcmp(mon, "Feb") == 0) { i = 2; } + else if (strcmp(mon, "Mar") == 0) { i = 3; } + else if (strcmp(mon, "Apr") == 0) { i = 4; } + else if (strcmp(mon, "May") == 0) { i = 5; } + else if (strcmp(mon, "Jun") == 0) { i = 6; } + else if (strcmp(mon, "Jul") == 0) { i = 7; } + else if (strcmp(mon, "Aug") == 0) { i = 8; } + else if (strcmp(mon, "Sep") == 0) { i = 9; } + else if (strcmp(mon, "Oct") == 0) { i = 10; } + else if (strcmp(mon, "Nov") == 0) { i = 11; } + else if (strcmp(mon, "Dec") == 0) { i = 12; } + return i; +} + +// Fuction to initialize date type +// Assumes that argument is a string +// in the format 'mmm dd, yyyy' or 'mmm yyyy' +// or NULL for empty initialization +date new_date(string *fmt) +{ + date d; + if (fmt != NULL) { + uint8_t i = 0; + string aux; + // If the format is 'mmm dd, yyyy' + if (strlen(fmt->buf) > 10) { + // parsing day + i = fmt->find(fmt, ",") - 4; + aux = fmt->substr(fmt, 4, i); + d.day = aux.to_int(&aux); + aux.delete(&aux); + + // parsing month + aux = fmt->substr(fmt, 0, 3); + strcpy(d.month, aux.buf); + aux.delete(&aux); + + // parsing year + i = fmt->find(fmt, ",") + 2; + aux = fmt->substr(fmt, i, (fmt->length(fmt) - i)); + d.year = aux.to_int(&aux); + aux.delete(&aux); + } else { // Assuming format is 'mmm yyyy' + // no day provided in this format + d.day = 0; + + // parsing month + i = fmt->find(fmt, " "); + aux = fmt->substr(fmt, 0, i); + strcpy(d.month, aux.buf); + aux.delete(&aux); + + // parsing year + aux = fmt->substr(fmt, (i + 1), fmt->length(fmt) - (i + 1)); + d.year = aux.to_int(&aux); + aux.delete(&aux); + } + // parsing value + unsigned mon = strmon_to_int(d.month); + d.value = ((d.year * 10000) + (mon * 100) + d.day); + } else { + d.day = 0; + strcpy(d.month, "mmm"); + d.year = 0; + d.value = 0; + } + return d; +} + +// Function to initialize +// an 'empty' game struct +game empty_game(void) +{ + game g; + g.app_id = 0; + g.name = new_string(""); + g.release_date = new_date(NULL); + g.owners = new_string(""); + g.age = 0; + g.price = 0.0f; + g.dlcs = 0; + g.languages = new_strarr(NULL, 0); + g.website = new_string(""); + g.windows = false; + g.mac = false; + g.linux_ = false; + g.upvotes = 0.0f; + g.avg_pt = 0; + g.developers = new_string(""); + g.genres = new_strarr(NULL, 0); + return g; +} + +// Function to initialize a new game with +// the retrievd data in the string array +game new_game(string *s_arr) +{ + game g; + if (s_arr != NULL) { + // App id + g.app_id = atoi(s_arr[0].buf); + // Name + g.name = new_string(s_arr[1].buf); + // Release date + g.release_date = new_date(s_arr + 2); + // Owners + g.owners = new_string(s_arr[3].buf); + // Age, Price, DLCs + g.age = atoi(s_arr[4].buf); + g.price = atof(s_arr[5].buf); + g.dlcs = atoi(s_arr[6].buf); + // Languages + // If string is not empty then splits it into a string array + if (strcmp(s_arr[7].buf, "[]") != 0) { + string tmp = s_arr[7].substr(s_arr + 7, 2, strlen(s_arr[7].buf) - 4); + tmp.replace(&tmp, "', '", "|"); + g.languages = tmp.split(&tmp, "|"); + tmp.delete(&tmp); + } else { + g.languages = new_strarr(NULL, 0); + string *tmp = realloc(g.languages.src, sizeof(string)); + if (tmp != NULL) { + tmp[0] = new_string(""); + g.languages.src = tmp; + g.languages.length = 1; + } + } + // Website + g.website = new_string(s_arr[8].buf); + // Windows, Mac, Linux + g.windows = (strcmp(s_arr[9].buf, "True") == 0) ? true : false; + g.mac = (strcmp(s_arr[10].buf, "True") == 0) ? true : false; + g.linux_ = (strcmp(s_arr[11].buf, "True") == 0) ? true : false; + // Upvotes + float up_votes = atof(s_arr[12].buf); + float down_votes = atof(s_arr[13].buf); + g.upvotes = (up_votes / ((up_votes + down_votes) / 100)); + // Avg pt + g.avg_pt = atoi(s_arr[14].buf); + // Developers + g.developers = new_string(s_arr[15].buf); + // Genres + // If string is not empty and has a ',' then splits it into a string array + if ((strcmp(s_arr[16].buf, "") != 0) && + (s_arr[16].find(s_arr + 16, ",") != strlen(s_arr[16].buf))) { + g.genres = s_arr[16].split(s_arr + 16, ","); + } else { + g.genres = new_strarr(NULL, 0); + string *tmp = realloc(g.genres.src, sizeof(string)); + if (tmp != NULL) { + tmp[0] = new_string(s_arr[16].buf); + g.genres.src = tmp; + g.genres.length = 1; + } + } + } else g = empty_game(); + return g; +} + +// function to deallocates all +// the heap variables inside the game struct +void game_delete(game *g) +{ + if (g != NULL) { + g->name.delete(&g->name); + g->owners.delete(&g->owners); + g->languages.delete(&g->languages); + g->website.delete(&g->website); + g->developers.delete(&g->developers); + g->genres.delete(&g->genres); + } +} + +// Receives a char array that contains +// the data of the game strut in a csv format +// parses it and returns a game data structure +game chararr_to_game(char *ch_arr) +{ + game g; + if (ch_arr != NULL) { + // Initializing static array of strings + // where each index will be a field of the game data struct + string s_arr[GAME_FIELDS_SIZE]; + for (int i = 0; i < GAME_FIELDS_SIZE; i++) { + s_arr[i] = new_string(NULL); + } + + // Initializing variables for the split algorithm + string aux = new_string(NULL); + aux.buf = malloc(sizeof(char) * MAX_LINE_SIZE); + size_t ch_arr_len = strlen(ch_arr), s_arr_i = 0, aux_i = 0; + bool is_str_lit = false; + + // Loop to split each csv cell into a string + // and adds it to the array of strings + for (size_t i = 0; i < ch_arr_len; i++) { + if (ch_arr[i] == '"') { + is_str_lit = (is_str_lit ? false : true); + } else if (ch_arr[i] == ',' && !is_str_lit) { + aux.buf[aux_i] = '\0'; + s_arr[s_arr_i].buf = malloc(sizeof(char) * (strlen(aux.buf) + 1)); + strcpy(s_arr[s_arr_i].buf, aux.buf); + s_arr_i++; aux_i = 0; + } else { + aux.buf[aux_i++] = ch_arr[i]; + } + } + // Adding last string cell to the array + aux.buf[aux_i] = '\0'; + s_arr[s_arr_i].buf = malloc(sizeof(char) * (strlen(aux.buf) + 1)); + strcpy(s_arr[s_arr_i].buf, aux.buf); + aux.delete(&aux); + // Creating game with parsed data + g = new_game(s_arr); + + // Deallocating the string array + for (size_t i = 0; i < GAME_FIELDS_SIZE; i++) { + free(s_arr[i].buf); + } + } else g = empty_game(); + return g; +} + +// Converts avg_pt (int with amount of minutes) +// to a string in format '[x]h [y]m' returns +// a string with 'null' if avg_pt equals zero +char *avgpt_to_chararr (unsigned avg_pt) +{ + char *c_arr = calloc(16, sizeof(char)); + if (avg_pt != 0) { + unsigned h = avg_pt / 60; + unsigned m = avg_pt % 60; + + if (h != 0 && m != 0) { + snprintf(c_arr, 16, "%dh %dm", h, m); + } else if (h != 0) { + snprintf(c_arr, 16, "%dh", h); + } else if (m != 0) { + snprintf(c_arr, 16, "%dm", m); + } + } else { + strcpy(c_arr, "null"); + } + return c_arr; +} + +// prints the game structure in the format +// as is specified in the assignment +void game_print(game *g) +{ + printf("%u %s %s/%d %s %u %.2f %u [", + g->app_id, g->name.buf, + g->release_date.month, + g->release_date.year, + g->owners.buf, g->age, + g->price, g->dlcs); + // languages + for (size_t i = 0; i < g->languages.length - 1; i++) + printf("%s, ", g->languages.src[i].buf); + char *aux = avgpt_to_chararr(g->avg_pt); + printf("%s] %s %s %s %s %.0f%% %s %s [", + g->languages.src[g->languages.length - 1].buf, g->website.buf, + (g->windows ? "true" : "false"), (g->mac ? "true" : "false"), + (g->linux_ ? "true" : "false"), g->upvotes, aux, g->developers.buf); + free(aux); + // genres + for (size_t i = 0; i < g->genres.length - 1; i++) + printf("%s, ", g->genres.src[i].buf); + printf("%s]\n", g->genres.src[g->genres.length - 1].buf); +} + +// Search for the matching 'app_id' and read the game data +// from the file 'games.csv' then adds the game to the games array +void game_read_csv(game *games, size_t *ids, size_t ids_len) +{ + // Handling file opening + FILE *f = fopen(FILE_PATH, "r"); + assert__(f != NULL) { + fprintf(stderr, "Failed to open '%s'\n%s", FILE_PATH, + "Hint: Try adding or removing a '/' in front (OS Dependent)\n"); + } + + char line[MAX_LINE_SIZE + 1]; + size_t cur_id = 0; + // Searching the desired id then calling the parser function + for (size_t i = 0; i < ids_len; i++) { + while (cur_id != ids[i]) { + // reads line from file + fgets(line, MAX_LINE_SIZE, f); + // checking if is a reserved char to then trim it + if (line[strlen(line) - 2] < ' ') line[strlen(line) - 2] = '\0'; + else if (line[strlen(line) - 1] < ' ') line[strlen(line) - 1] = '\0'; + // atoi will only parse the beginning of the string ;) + cur_id = atoi(line); + } + rewind(f); // Resets file pointer to the beginning of the file + games[i] = chararr_to_game(line); // calling parser function + } + fclose(f); // closing the file +} + +// ------------------------------------ main ------------------------------------ // + +// Uses strcmp to check if +// input string equals 'FIM' +// meaning end of input +bool is_fim(char *c_arr) +{ + return (strcmp(c_arr, "FIM") == 0); +} + +// The main function contains the solution to +// the second question of the assignment, which is +// to read 'n' lines of strings containing the +// desired IDs of games to be read from the file 'games.csv', +// parse the data, store it in a game struct already defined, +// and print all of them in a specified format. +int main(void) +{ + // game array + game games[MAX_GAMES]; + size_t games_sz = 0; + // input variables + char input[MAX_FIELD_SIZE ]; + size_t ids[MAX_GAMES]; + + // Reading input and converting to integer + // then adding it to the array of ids to be searched + fgets(input, MAX_LINE_SIZE, stdin); + input[strlen(input) - 1] = '\0'; + while (!is_fim(input)) { + ids[games_sz++] = atoi(input); + fgets(input, MAX_LINE_SIZE, stdin); + // checking if is a reserved char to then trim it + if (input[strlen(input) - 2] < ' ') input[strlen(input) - 2] = '\0'; + else if (input[strlen(input) - 1] < ' ') input[strlen(input) - 1] = '\0'; + } + // If the array of ids is not empty + if (games_sz > 0) { + // Calls function to read from file and populate array of games + game_read_csv(games, ids, games_sz); + + // Prints the games read and frees the heap allocated memory + for (size_t i = 0; i < games_sz; i++) { + game_print(games + i); + game_delete(games + i); + } + } + return 0; +} \ No newline at end of file From 37ab9e38c665a9e95adf9890f148198e6c9b1d3b Mon Sep 17 00:00:00 2001 From: Felipe Domingos Date: Sun, 25 Sep 2022 09:52:04 -0300 Subject: [PATCH 2/2] Rename game.c to Game01.c --- tps/fonte/{game.c => game01.c} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tps/fonte/{game.c => game01.c} (96%) diff --git a/tps/fonte/game.c b/tps/fonte/game01.c similarity index 96% rename from tps/fonte/game.c rename to tps/fonte/game01.c index 484b24a..39becc0 100644 --- a/tps/fonte/game.c +++ b/tps/fonte/game01.c @@ -718,4 +718,4 @@ int main(void) } } return 0; -} \ No newline at end of file +}