This is a follow up question to Date Checking in C++
I have these two function prototypes:
void CovidDB::add_covid_data(std::string const COVID_FILE)
void CovidDB::add_rehashed_data(std::string const COVID_FILE)
both essentially do the same thing except that one re_hashes the data before logging it to a .log
file. I've heard about function pointers but was never really comfortable using them. Is there an STL container or something that would help me in this case?
Is the stuff I am doing with defining "random" macros such as LOG
or WAIT
bad practice?
I would like to know if there is anything that needs significant improvement, and if there is any other way of documenting code. One of my professors introduced me to doxygen
but I found that it takes me a lot longer than just
// {
// desc:
// pre:
// post:
// }
I would also be very grateful if someone could point me on how to log errors without creating a logging class. Is there a library for that?
Edit: I am very familiar with Python
, but I was forced to start using C++
in Data Structures (class I am taking right now), that's why I added the beginner tag.
Here is the full code:
run.h
#include <string>
#include "CovidDB.h"
/** DOCUMENTATION:
* @author Jakob Balkovec
* @file run.cpp [Source Code]
* @note Header file for helper functions
* @remark this file defines a set of helper functions that most get or check parameters
*/
#pragma once
/** @brief
* @name get_date: Takes a string reference as an argument and returns a string
* @name get_country: Takes a string reference as an argument and returns a string.
* @name get_country: Takes a string reference as an argument and returns a string.
* @name get_coivd_cases: Takes an integer reference as an argument and returns an integer.
* @name get_covid_deaths: Takes an integer reference as an argument and returns an integer.
*/
std::string get_date();
std::string get_country();
int get_coivd_cases();
int get_covid_deaths();
/** @brief
* @name set_data: Takes a DataEntry* pointer and several arguments
* @param (country, date, cases, deaths)
* to set the data.
*/
void set_data(DataEntry* data, std::string country, std::string date, int cases, int deaths);
/** @brief
* @name get_input: Takes no arguments and returns an integer.
* @name goodbye: Takes no arguments and returns void.
* @name menu: Takes no arguments and returns void.
* @name run: Takes no arguments and returns void.
*/
int get_input();
void goodbye();
void menu();
void run();
/** @brief
* @name valid_month: Takes a string and returns a boolean indicating the validity of the month.
* @name valid_day: Takes a string and returns a boolean indicating the validity of the day.
* @name valid_year: Takes a string reference and returns a boolean indicating the validity of */
bool valid_month(std::string);
bool valid_day(std::string);
bool valid_year(std::string &year);
run.cpp
#include <string>
#include <iostream>
#include <vector>
#include <chrono>
#include <algorithm>
#include <limits>
#include <stdexcept>
#include <thread>
#include <atomic>
#include "run.h"
#include "CovidDB.h"
#define LOG
typedef std::numeric_limits<int> int_limits;
static int constexpr INT_MAX = int_limits::max();
/** DOCUMENTATION:
* @author Jakob Balkovec
* @file assignment4.cpp [Driver Code]
* @note Driver code for A4
*
* @brief This assigment focuses on using multiple operations regarding BST like:
* - Insertion
* - Traversals
* - Searching
* - Deletion
*
* Those operations were implemented using a ShelterBST class that includes a struct for Pets and
* A struct for the TreeNodes.
*/
/** @todo
* test and assert
* Clarify what to print
* Make the output neater
*/
/** @name OPID (opeartion ID)
* @enum for the switch statement
* @brief Every operation has a numerical value
*/
enum OPID {
ID_1 = 1,
ID_2 = 2,
ID_3 = 3,
ID_4 = 4,
ID_5 = 5,
ID_6 = 6,
ID_7 = 7,
ID_8 = 8,
ID_9 = 9,
ID_10 = 10
};
/**
* @brief Displays the menu for user interaction.
*/
void menu() {
std::cout << "\n*** Welcome to the COVID-19 Data Base ***\n\n"
<< "[1]\tCreate a Hash Table with the WHO file\n"
<< "[2]\tAdd a Data entry\n"
<< "[3]\tGet a Data entry\n"
<< "[4]\tRemove a Data entry\n"
<< "[5]\tDisplay HasH Table\n"
<< "[6]\tRehash Table\n"
<< "[7]\tLog Data [covid_db.log]\n"
<< "[8]\tLog Re-hashed Data [rehash_covid_db.log]\n"
<< "[9]\tClear screen\n"
<< "[10]\tQuit\n";
}
/**
* @brief Takes a string and returns a boolean indicating the validity of the month.
*
* @param month The input month as a string.
* @return True if the month is valid, false otherwise.
*/
bool valid_month(std::string month) {
if(month.length() != 1 and month.length() != 2) {
return false;
} else if (std::stoi(month) > 13) {
return false;
} else if (std::stoi(month) < 1) {
return false;
}
return true;
}
/**
* @brief Takes a string and returns a boolean indicating the validity of the day.
*
* @param day The input day as a string.
* @return True if the day is valid, false otherwise.
*/
bool valid_day(std::string day) {
if(day.length() != 1 and day.length() != 2) {
return false;
} else if (std::stoi(day) > 31) {
return false;
} else if (std::stoi(day) < 1) {
return false;
}
return true;
}
/**
* @brief Takes a string reference and returns a boolean indicating the validity of the year.
*
* @param year The input year as a string reference.
* @return True if the year is valid, false otherwise.
*/
bool valid_year(std::string &year) {
if(year.length() == 4) {
year = year[2] + year [3];
return true;
} else if(year.length() != 2) {
return false;
}
return true;
}
/**
* @brief Takes no parameters and returns a string.
* @return The processed date as a string.
* IMPORTANT: @invariant user does not enter a word input
*/
std::string get_date() {
std::string date = "\0";
std::string month = "\0";
std::string day = "\0";
std::string year = "\0";
bool is_valid = false;
while (!is_valid) {
std::cout << "[FORMAT: mm/dd/yy][Enter a Date]: ";
std::getline(std::cin, date);
std::size_t month_loc = date.find("/");
std::string month_prev = date.substr(0, month_loc);
if (month_prev[0] == '0') {
month = month_prev[1]; // if preceding 0 -> trim
} else {
month = month_prev; // if single digit -> keep
}
std::string s_str = date.substr(month_loc + 1);
std::size_t day_loc = s_str.find("/");
std::string day_prev = s_str.substr(0, day_loc);
if (day_prev[0] == '0') {
day = day_prev[1];
} else {
day = day_prev;
}
year = s_str.substr(day_loc + 1);
is_valid = valid_day(day) && valid_month(month) && valid_year(year);
//{
/** @brief
* c_ stands for current
* e_ satnds for entered
*/
//}
if (is_valid) {
try {
std::time_t now = std::time(nullptr);
std::tm* c_time = std::localtime(&now);
int c_day = c_time->tm_mday;
int c_month = c_time->tm_mon + 1; // Month is zero-based
int c_year = c_time->tm_year % 100; // Last two digits of the year
const int e_day = std::stoi(day);
const int e_month = std::stoi(month);
const int e_year = std::stoi(year);
if (e_year > c_year) {
is_valid = false; // Date is in the future
throw std::invalid_argument("\n[Invalid]\n");
} else if (e_year == c_year) {
if (e_month > c_month) {
is_valid = false; // Date is in the future
throw std::invalid_argument("\n[Invalid]\n");
} else if (e_month == c_month) {
if (e_day > c_day) {
is_valid = false; // Date is in the future
throw std::invalid_argument("\n[Invalid]\n");
}
}
} else {
return month + "/" + day + "/" + year;
}
} catch (const std::exception& error) {
std::cout << error.what();
is_valid = false;
}
}
}
return month + "/" + day + "/" + year;
}
/**
* @brief Takes no arguments and returns a string.
* @return The processed country as a string.
*/
std::string get_country() {
std::string country;
while (true) {
std::cout << "[Enter a country]: ";
std::cin >> country;
std::cin.ignore();
if (std::all_of(country.begin(), country.end(), [](char c) { return std::isalpha(c); })) {
bool is_all_lowercase = std::all_of(country.begin(), country.end(), [](char c) { return std::islower(c); });
if (is_all_lowercase) {
country[0] = std::toupper(country[0]);
std::transform(country.begin() + 1, country.end(), country.begin() + 1, [](char c) { return std::tolower(c); });
}
return country;
} else {
std::cout << "[Input must be a string]\n";
country = ""; // Reset the input
}
}
}
/**
* @brief Takes no parameters and returns an integer.
* @return The processed Covid cases as an integer.
*/
int get_covid_cases() {
int deaths;
while (true) {
std::cout << "[Enter cases]: ";
std::cin >> deaths;
if (deaths >= 0) {
break;
} else {
std::cout << "[invalid input]\n";
// clear the input stream and ignore any remaining characters
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
return deaths;
}
/**
* @brief Takes no parameters and returns an integer.
* @return The processed Covid deaths as an integer.
*/
int get_covid_deaths() {
int deaths;
while (true) {
std::cout << "[Enter deaths]: ";
std::cin >> deaths;
if (deaths >= 0) {
break;
} else {
std::cout << "[invalid input]\n";
// clear the input stream and ignore any remaining characters
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
return deaths;
}
/**
* @brief Prompts the user to enter a valid intiger coresponding to one of the valus in the menu
* the user is prompted to enter the input again if it's not a number
*
* @return The processed input as an integer.
*/
int get_input() {
int const MIN = 1;
int const MAX = 10;
int choice = 0;
std::cout << "\n[Enter]: ";
while (true) {
try {
std::cin >> choice;
if (std::cin.fail()) { //std::cin.fail() if the input is not an intiger returns true
/// @link https://cplusplus.com/forum/beginner/2957/
std::cin.clear(); // clear error flags
std::cin.ignore(10000, '\n'); // ignore up to 10000 characters or until a newline is encountered
throw std::invalid_argument("[Invalid input]");
}
else if (choice < MIN || choice > MAX) {
throw std::out_of_range("[Input out of range. Please enter an integer between 1 and 8]");
}
else {
return choice;
}
}
catch (const std::exception& error) {
std::cout << error.what() << std::endl;
std::cout << "[Re-enter]: ";
}
}
}
/** @name goodbye()
* @brief The function prompts the user goodbye
* @remark Handles UI
* @return void-type
*/
void goodbye() {
std::cout << "\n\nGoodbye!\n\n";
}
/**
* @brief clears screen
*/
static inline void clear_screen() {
#define SLEEP std::this_thread::sleep_for(std::chrono::milliseconds(500))
SLEEP;
std::system("clear");
SLEEP;
}
/**
* @brief Takes a DataEntry* pointer and sets it's data
* @param data A pointer to a DataEntry object.
*/
void set_data(DataEntry* data) {
data->set_country(get_country());
data->set_date(get_date());
data->set_c_cases(get_covid_cases());
data->set_c_deaths(get_covid_deaths());
}
/**
* @brief Executes the main logic of the program in a while(true) loop.
*/
void run() {
/** DECLARATIONS: */
std::cout << std::endl << std::endl << std::flush;
CovidDB data_base(17);
DataEntry *data = new DataEntry();
/** DECLARATIONS: */
while(true) {
menu();
switch(get_input()) {
case OPID::ID_1: {
data_base.add_covid_data(COVID_FILE);
break;
}
case OPID::ID_2: {
set_data(data);
bool added = data_base.add(data);
if(added) {
std::cout << "\n[Record added]\n";
} else {
std::cout << "\n[Failed to add the entry]\n";
}
break;
}
case OPID::ID_3: {
data_base.fetch_data(data, get_country());
break;
}
case OPID::ID_4: {
data_base.remove(get_country());
break;
}
case OPID::ID_5: {
data_base.display_table();
break;
}
case OPID::ID_6: {
data_base.rehash();
break;
}
case OPID::ID_7: {
#ifdef LOG
data_base.log();
#else
std::cout << "\n[Define [LOG macro in run.cpp] to run]\n";
#endif // LOG
break;
}
case OPID::ID_8: {
#ifdef LOG
data_base.log_rehashed();
#else
std::cout << "\n[Define [LOG macro in run.cpp] to run]\n";
#endif // LOG
break;
}
case OPID::ID_9: {
clear_screen();
break;
}
case OPID::ID_10: {
goodbye();
delete data;
std::exit(EXIT_SUCCESS);
break;
}
default: {
/** @note do nothing...*/
}
}
}
std::cout << std::endl << std::endl << std::flush;
}
main.cpp
#include <iostream>
#include "run.h"
/**
* @brief
* @name show_last_compiled
* @param file Name of the file that is being compiled
* @param date Date of when the file was last compiled
* @param time Time of when the file was last compiled
* @param author Author of the code
* @note all params are arrays of chars
*/
static inline void show_last_compiled(const char* file, const char* date, const char* time, const char* author) {
std::cout << "\n[" << file << "] " << "Last compiled on [" << date << "] at [" << time << "] by [" << author << "]\n" << std::endl;
}
#define AUTHOR "Jakob" //define a macro
/**
* @brief The entry point of the program.
* @return [EXIT_SUCESS], the exit status of the program.
*/
int main() {
show_last_compiled(__FILE__, __DATE__, __TIME__, AUTHOR);
run();
return EXIT_SUCCESS;
}
CovidDB.cpp
/** DOCUMENTATION:
* @author Jakob Balkovec
* @file CovidDB.cpp [Driver Code]
* @note Driver code for A4
*
* @brief This assigment focuses on using multiple operations regarding HasHTables such as:
* - Insertion
* - Printing
* - Hashing
* - Deletion
* - [File reading]
*
* Those operations were implemented using a DataEntry and a CovidDB class
*/
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <fstream>
#include <cassert>
#include <ctime>
#include <chrono>
#include <iomanip>
#include <thread>
#include <cmath>
#include <cstdlib>
#include <unistd.h>
#include <atomic>
#include <sys/types.h>
#include <sys/wait.h>
#include <algorithm>
#include "CovidDB.h"
#define LOG
//#define WAIT //-> for submmission uncomment
/**
* @brief Constructs an object of DataEntry type with default parameters
* @return DataEntry Object
*/
DataEntry::DataEntry() {
this->date = "\0";
this->country = "\0";
this->c_cases = 0;
this->c_deaths = 0;
}
/**
* @brief Hashfunction that creates a hash
* @param country std::string entry -> country in the CSV file
* @return Hash
*/
int CovidDB::hash(std::string country) {
int sum = 0;
int count = 0;
for (char c : country) {
sum = sum + ((count + 1) * c);
count++;
}
return sum % size; //returns the hash
}
int CovidDB::re_hash_key(std::string country) {
int sum = 0;
int count = 0;
for (char c : country) {
sum = sum + ((count + 1) * c);
count++;
}
return sum % size; //returns the new hash
}
/**
* @brief Re-Hashfunction that rehashes the whole table
*/
void CovidDB::rehash() {
auto is_prime = [](int num) {
if (num <= 1)
return false;
for (int i = 2; i <= std::sqrt(num); ++i) {
if (num % i == 0)
return false;
}
return true;
};
auto find_next_prime = [&is_prime](int num) {
while (true) {
if (is_prime(num))
return num;
++num;
}
};
int new_size = size * 2;
int new_prime_size = find_next_prime(new_size);
// Create a new hash table with the new size
CovidDB new_table(new_prime_size);
// Rehash all entries from the original table to the new table
for (std::vector<DataEntry*>& row : HashTable) {
for (DataEntry* entry : row) {
if (entry != nullptr) {
int re_hash_i = re_hash_key(entry->get_country());
new_table.HashTable[re_hash_i].push_back(entry);
}
}
}
for(auto row : HashTable) {
row.clear();
}
HashTable.clear();
HashTable = std::move(new_table.HashTable);
size = new_prime_size;
#ifdef LOG
std::cout << "[LOG]: Table rehashed. New table size: " << size << std::endl;
#endif //LOG
return;
}
/**
* @brief Inserts one data entry into the hash table.
* @param entry The DataEntry object to be added
* @return true if record is added, false if record is rejected (date < than current date)
*/
bool CovidDB::add(DataEntry* entry) {
time_t now = time(0);
tm* ltm = localtime(&now);
// DATE FORMAT: mm/dd/yy
std::string current_date_str = std::to_string(1 + ltm->tm_mon) + "/" + std::to_string(ltm->tm_mday) + "/" + std::to_string(ltm->tm_year % 100);
std::istringstream iss(current_date_str);
std::tm current_date = {};
iss >> std::get_time(¤t_date, "%m/%d/%y");
std::tm entry_date = {};
std::istringstream iss2(entry -> get_date());
iss2 >> std::get_time(&entry_date, "%m/%d/%y");
if (mktime(¤t_date) > mktime(&entry_date)) {
std::cout << "[Record rejected]" << std::endl;
return false;
}
int index = hash(entry -> get_country());
if (HashTable[index].empty()) {
HashTable[index].push_back((entry));
} else {
bool added = false;
for (DataEntry* existing_entry : HashTable[index]) {
std::atomic<bool> valid(false);
valid.store(hash(existing_entry->get_country()) == hash(entry->get_country()) &&
existing_entry->get_country() == entry->get_country());
if (valid) {
existing_entry->set_date(entry -> get_date());
existing_entry->set_c_cases(existing_entry->get_c_cases() + entry->get_c_cases());
existing_entry->set_c_deaths(existing_entry->get_c_deaths() + entry->get_c_deaths());
added = true;
//delete entry;
break;
}
}
if (!added) {
HashTable[index].push_back(entry);
}
}
return true;
}
/**
* @brief Retrieves a data entry with the specific country name
* @param country The country to search for
* @return The DataEntry object with the matching country name, or NULL if no such entry exists
*/
DataEntry* CovidDB::get(std::string country) {
int index = hash(country);
assert(index < 17);
for (DataEntry* entry : HashTable[index]) {
if (entry->get_country() == country) {
return entry;
}
}
return nullptr;
}
/**
* @brief Fetches the data entry for a specific country and assigns it
* to the provided DataEntry pointer.
*
* @param set A pointer to a DataEntry object where the fetched data will be assigned.
* @param country The name of the country to fetch data for.
* @return void
*/
void CovidDB::fetch_data(DataEntry* set, std::string country) {
set = get(country);
if(set == nullptr) {
std::cout << "\n[No entry found for: \"" << country << "\"]\n";
return; //if nullptr don't derefernece
}
std::cout << set << std::endl;
}
/**
* @brief Removes the data entry with the specific country name
* @param country The country to remove
*/
void CovidDB::remove(std::string country) {
int index = hash(country);
for (auto it = HashTable[index].begin(); it != HashTable[index].end(); ++it) {
if ((*it)->get_country() == country) {
delete *it;
HashTable[index].erase(it);
return;
}
}
std::cout << "\n[No entry found for: " << country << "]" << std::endl;
}
/**
* @brief Prints the contents of the hash table using
* nested for each loops
*/
//{
// @bug when adding 2 entires with the same hash -> SIGSEV
//}
void CovidDB::display_table() const {
char const STICK = '|';
bool is_empty = true;
/** @note guard against printing an empty table*/
for(const auto& vec : HashTable) { //if 1st dimension is empty
if(!vec.empty()) {
is_empty = false;
break;
}
}
if(is_empty) {
std::cout << "\n[Data Base is empty]\n";
return;
}
/** @note guard against printing an empty table*/
std::cout << "\n[Printing Data Base]\n|\n";
for (int i = 0; i < 3; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << STICK << std::endl;
}
std::cout << std::flush; //flush buffer
std::string const SPACE = " ";
for(std::vector<DataEntry*> vec : HashTable) {
for(DataEntry* entry : vec) {
if (entry != nullptr) { //guard against dereferencing nullptr
std::cout << std::flush;
std::cout << entry << std::endl;
}
}
}
std::cout << std::endl;
return;
}
/**
* @brief Logs the contents of the hash table using
* nested for each loops and writes them to a .log file
*/
void CovidDB::log() {
#ifdef LOG
add_covid_data(COVID_FILE); //add data and log
std::ofstream covid_file;
covid_file.open("covid_db.log");
if (covid_file.is_open()) {
covid_file << std::flush;
std::string const SPACE = " ";
covid_file << "\n\n****************************** COIVD DATA LOG ******************************\n\n\n";
for (auto& vec : HashTable) {
for (auto& entry : vec) {
if (entry != nullptr) {
covid_file << "[Date: " << entry->get_date() << "]," << SPACE
<< "[Country: " << entry->get_country() << "]," << SPACE
<< "[Cases: " << entry->get_c_cases() << "]," << SPACE
<< "[Deaths: " << entry->get_c_deaths() << "]"
<< std::endl;
}
}
}
covid_file.close();
} else {
covid_file << "\n[Error opening the file covidDB.log]\n";
std::exit(EXIT_FAILURE);
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "\n------ [Log avalible] ------\n\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::exit(EXIT_SUCCESS);
return;
}
#else
std::cout << "\n[Define [LOG macro in CovidDB.cpp] to run]\n";
#endif // LOG
/**
* @brief Logs re-hashed data
*/
void CovidDB::log_rehashed() {
#ifdef LOG
add_covid_data(COVID_FILE); //add data and log
std::ofstream covid_file;
covid_file.open("rehash_covid_db.log");
if (covid_file.is_open()) {
covid_file << std::flush;
std::string const SPACE = " ";
covid_file << "\n\n************************** REHASHED COIVD DATA LOG **************************\n\n\n";
for (auto& vec : HashTable) {
for (auto& entry : vec) {
if (entry != nullptr) {
//use of overloaded << ostream operator
covid_file << entry << std::endl;
}
}
}
covid_file.close();
} else {
covid_file << "\n[Error opening the file rehash_covidDB.log]\n";
std::exit(EXIT_FAILURE);
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "\n------ [Rehash Log avalible] ------\n\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::exit(EXIT_SUCCESS);
return;
}
#else
std::cout << "\n[Define [LOG macro in CovidDB.cpp] to run]\n";
#endif // LOG
/**
* @brief Reads a CSV file containing COVID data and adds the data to the database.
* @param COVID_FILE The name of the CSV file to read.
* @return void
*/
void CovidDB::add_covid_data(std::string const COVID_FILE) {
std::ifstream file(COVID_FILE);
// Measure time it takes to fetch and process data
std::chrono::steady_clock::time_point start_time;
std::chrono::steady_clock::time_point end_time;
start_time = std::chrono::steady_clock::now(); // Start stopwatch
if (!file) {
std::cout << "\n[File ERROR]\n " << COVID_FILE << std::endl;
std::exit(EXIT_FAILURE);
}
std::cout << "\n[Fetching Data]\n";
std::string line;
std::getline(file, line); // Skip header line
std::string latest_date_str = "11/02/22"; // Initialize to an old date
std::tm latest_date = {};
std::istringstream iss(latest_date_str);
iss >> std::get_time(&latest_date, "%m/%d/%y");
// Get the total number of lines in the file
std::ifstream count_lines(COVID_FILE);
int total_lines = std::count(std::istreambuf_iterator<char>(count_lines), std::istreambuf_iterator<char>(), '\n');
count_lines.close();
int progress_interval = total_lines / 10; // Update progress every 10% of the total lines
int current_line = 0;
// Progress bar variables
int progress_bar_width = 40;
while (std::getline(file, line)) {
std::stringstream ss(line);
std::string country, date_str, cases_str, deaths_str;
std::getline(ss, date_str, ',');
std::getline(ss, country, ',');
std::getline(ss, cases_str, ',');
std::getline(ss, deaths_str, ',');
int cases = std::stoi(cases_str);
int deaths = std::stoi(deaths_str);
std::tm entry_date = {};
std::istringstream iss2(date_str);
iss2 >> std::get_time(&entry_date, "%m/%d/%y");
if (mktime(&entry_date) > mktime(&latest_date)) {
latest_date_str = date_str;
latest_date = entry_date;
}
DataEntry* entry = new DataEntry();
entry->set_country(country);
entry->set_date(latest_date_str);
entry->set_c_cases(cases);
entry->set_c_deaths(deaths);
add(entry);
current_line++;
// Update progress bar
if (current_line % progress_interval == 0 || current_line == total_lines) {
int progress = static_cast<int>((static_cast<double>(current_line) / total_lines) * 100);
std::cout << "\r[";
int completed_width = progress_bar_width * progress / 100;
for (int i = 0; i < progress_bar_width; ++i) {
if (i < completed_width) {
std::cout << "#";
} else {
std::cout << " ";
}
}
std::cout << "] [" << progress << "%]" << std::flush;
}
}
file.close();
end_time = std::chrono::steady_clock::now(); // Stop stopwatch
std::chrono::duration<double> elapsedSeconds = end_time - start_time;
// Static cast elapsed time to an int and round up
auto elapsedSecondsCount = static_cast<int>(std::round(elapsedSeconds.count()));
std::cout << "\n[Data Fetched] [Elapsed Time: " << elapsedSecondsCount << "s]\n\n";
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
return;
}
/**
* @brief Reads a CSV file containing COVID data and adds the new data to the database.
* @param COVID_FILE The name of the CSV file to read.
* @return void
*/
void CovidDB::add_rehashed_data(std::string const COVID_FILE) {
std::ifstream file(COVID_FILE);
// Measure time it takes to fetch and process data
std::chrono::steady_clock::time_point start_time;
std::chrono::steady_clock::time_point end_time;
start_time = std::chrono::steady_clock::now(); // Start stopwatch
if (!file) {
std::cout << "\n[File ERROR]\n " << COVID_FILE << std::endl;
std::exit(EXIT_FAILURE);
}
std::cout << "\n[Fetching Data]\n";
std::string line;
std::getline(file, line); // Skip header line
std::string latest_date_str = "11/02/22"; // Initialize to an old date
std::tm latest_date = {};
std::istringstream iss(latest_date_str);
iss >> std::get_time(&latest_date, "%m/%d/%y");
// Get the total number of lines in the file
std::ifstream count_lines(COVID_FILE);
int total_lines = std::count(std::istreambuf_iterator<char>(count_lines), std::istreambuf_iterator<char>(), '\n');
count_lines.close();
int progress_interval = total_lines / 10; // Update progress every 10% of the total lines
int current_line = 0;
// Progress bar variables
int progress_bar_width = 40;
while (std::getline(file, line)) {
std::stringstream ss(line);
std::string country, date_str, cases_str, deaths_str;
std::getline(ss, date_str, ',');
std::getline(ss, country, ',');
std::getline(ss, cases_str, ',');
std::getline(ss, deaths_str, ',');
int cases = std::stoi(cases_str);
int deaths = std::stoi(deaths_str);
std::tm entry_date = {};
std::istringstream iss2(date_str);
iss2 >> std::get_time(&entry_date, "%m/%d/%y");
if (mktime(&entry_date) > mktime(&latest_date)) {
latest_date_str = date_str;
latest_date = entry_date;
}
DataEntry* entry = new DataEntry();
entry->set_country(country);
entry->set_date(latest_date_str);
entry->set_c_cases(cases);
entry->set_c_deaths(deaths);
add(entry);
rehash();
current_line++;
// Update progress bar
if (current_line % progress_interval == 0 || current_line == total_lines) {
int progress = static_cast<int>((static_cast<double>(current_line) / total_lines) * 100);
std::cout << "\r[";
int completed_width = progress_bar_width * progress / 100;
for (int i = 0; i < progress_bar_width; ++i) {
if (i < completed_width) {
std::cout << "#";
} else {
std::cout << " ";
}
}
std::cout << "] [" << progress << "%]" << std::flush;
}
}
file.close();
end_time = std::chrono::steady_clock::now(); // Stop stopwatch
std::chrono::duration<double> elapsedSeconds = end_time - start_time;
// Static cast elapsed time to an int and round up
auto elapsedSecondsCount = static_cast<int>(std::round(elapsedSeconds.count()));
std::cout << "\n[Data Fetched] [Elapsed Time: " << elapsedSecondsCount << "s]\n\n";
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
return;
}
CovidDB.h
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#pragma once
static std::string const COVID_FILE = "WHO-COVID-data.csv";
/** DOCUMENTATION:
* @author Jakob Balkovec
* @file CovidDB.h [Header file]
* @note Header file for DataEntry and CovidDB class
*
* @brief This assigment focuses on using multiple operations regarding HasHTables such as:
* - Insertion
* - Printing
* - Hashing
* - Deletion
* - [File reading]
*
* Those operations were implemented using a DataEntry and a CovidDB class
*/
/**
* @class DataEntry
* @brief DataEntry class represents a single entry of COVID-19 data
* for a particular date and country.
* @note This class is immutable once created.
*/
class DataEntry final {
private:
std::string date;
std::string country;
int c_cases;
int c_deaths;
public:
DataEntry();
/** @note mutators and acessors*/
inline void set_date(std::string set_date) { this->date = set_date;};
inline void set_country(std::string set_country) { this->country = set_country;};
inline void set_c_deaths(int set_c_deaths) { this->c_deaths = set_c_deaths;};
inline void set_c_cases(int set_c_cases) { this->c_cases = set_c_cases;};
inline int get_c_cases() { return c_cases;};
inline int get_c_deaths() { return c_deaths;};
inline std::string get_date() { return date;};
inline std::string get_country() { return country;};
inline static void print_data_entry(std::ostream& stream, DataEntry* entry) {
char const SPACE = ' ';
if (entry != nullptr) {
stream << "[Date: " << entry->get_date() << "]," << SPACE
<< "[Country: " << entry->get_country() << "]," << SPACE
<< "[Cases: " << entry->get_c_cases() << "]," << SPACE
<< "[Deaths: " << entry->get_c_deaths() << "]";
}
}
//declare as "friend" so it doesn't give a fuck about access specifiers
inline friend std::ostream& operator<<(std::ostream& stream, DataEntry* entry) {
print_data_entry(stream, entry);
return stream;
}
};
/**
* @brief A hash table implementation to store Covid-19 data by country
* @class CovidDB
* @note The hash table size is fixed at 17.
*/
class CovidDB final {
private:
int size;
std::vector<std::vector<DataEntry*>> HashTable;
public:
//non default construcotr with parameters
//@param size -> custom size
inline CovidDB(int size) : size(size), HashTable(size) {};
//default construcotr
inline CovidDB() { //default size
HashTable.resize(17);
}
inline void clear() {
for (auto& row : HashTable) {
for (auto& entry : row) {
delete entry;
}
row.clear();
}
HashTable.clear();
HashTable.shrink_to_fit();
}
inline ~CovidDB() { //handles memory
clear();
}
/** @note Copy constructor */
CovidDB(const CovidDB& other) {
size = other.size;
HashTable.resize(size);
for (int i = 0; i < size; ++i) {
for (const auto& entry : other.HashTable[i]) {
HashTable[i].push_back(new DataEntry(*entry));
}
}
}
/** @note Move constructor */
CovidDB(CovidDB&& other) noexcept
: size(other.size)
, HashTable(std::move(other.HashTable))
{
other.size = 0;
}
/** @note Overloaded assigment operator*/
CovidDB& operator=(CovidDB other) {
std::swap(other.size, size);
std::swap(other.HashTable, HashTable);
return *this;
}
DataEntry* get(std::string country);
void fetch_data(DataEntry* set, std::string country);
bool add(DataEntry* entry);
void add_covid_data(std::string const COVID_FILE);
void add_rehashed_data(std::string const COVID_FILE);
void rehash();
int re_hash_key(std::string country);
int hash(std::string country);
void remove(std::string country);
void display_table() const;
void log();
void log_rehashed();
};
MakeFile
CC = g++
CFLAGS = -Wall -Werror -std=c++11 -pedantic -O3
SRCS = run.cpp CovidDB.cpp main.cpp
OBJS = $(SRCS:.cpp=.o)
TARGET = main
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(TARGET)
%.o: %.cpp
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)