Merge branch 'master' of https://github.com/icei-pucminas/aeds2
This commit is contained in:
commit
2c3de0c8a1
|
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue