Skip to main content
1 of 5

Covid Data Base Hash Map 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?

I would like to know if there is anything that needs significant improvement, and if there is any other wayof documenting code. One of my professor introduced me to doxygen but I found that it takes me a lot longer that just

// {
// desc:
// pre:
// post:
// }

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(&current_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(&current_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 acessors
  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)

```