From 94fb05a764c1d0d1f275f29ecd21686d835f9768 Mon Sep 17 00:00:00 2001 From: Steru Date: Mon, 12 Aug 2024 22:09:50 +0200 Subject: Created Model for competitors. --- src/discipline/Sport.cpp | 423 ------------------------------------ src/discipline/Sport.h | 92 -------- src/model/Competitor.cpp | 15 ++ src/model/Competitor.h | 39 ++++ src/model/CompetitorWithResults.cpp | 15 ++ src/model/CompetitorWithResults.h | 28 +++ src/model/MedalWinner.cpp | 18 ++ src/model/MedalWinner.h | 35 +++ src/model/Sport.cpp | 423 ++++++++++++++++++++++++++++++++++++ src/model/Sport.h | 92 ++++++++ 10 files changed, 665 insertions(+), 515 deletions(-) delete mode 100644 src/discipline/Sport.cpp delete mode 100644 src/discipline/Sport.h create mode 100644 src/model/Competitor.cpp create mode 100644 src/model/Competitor.h create mode 100644 src/model/CompetitorWithResults.cpp create mode 100644 src/model/CompetitorWithResults.h create mode 100644 src/model/MedalWinner.cpp create mode 100644 src/model/MedalWinner.h create mode 100644 src/model/Sport.cpp create mode 100644 src/model/Sport.h diff --git a/src/discipline/Sport.cpp b/src/discipline/Sport.cpp deleted file mode 100644 index 21b9b9f..0000000 --- a/src/discipline/Sport.cpp +++ /dev/null @@ -1,423 +0,0 @@ - -#include "Sport.h" - -// categories -#include - -// sorting and filtering -#include -#include -#include - -// float to string formatting -#include -#include - -#include -#include -#include -#include - -// QJsonArray filter function, provide with input array and evaluation function -QJsonArray filter(QJsonArray input, function eval) { - QJsonArray output; - - for (const QJsonValueRef &elemRef :input) { - QJsonObject elem = elemRef.toObject(); - if(eval(elem)) { - output.append(elem); - } - } - - return output; -} - -// static compare function for specific attribute in competitors -function genCompare(QString attribute) { - return [attribute](const QJsonValue &left, const QJsonValue &right) { - QString l = left.toObject()[attribute].toString(); - QString r = right.toObject()[attribute].toString(); - return l.compare(r) < 0; - }; -} - -// static compare function for the results of a competitor in a specific competition (also called mark) -bool compareMark(const QJsonValue &left, const QJsonValue &right) { - // check if one competitor has no mark - QJsonObject l = left.toObject(); - if (!l.contains("results")) return true; - QJsonObject r = right.toObject(); - if (!r.contains("results")) return false; - - QString lMark = l["results"].toObject()["mark"].toString(); - QString rMark = r["results"].toObject()["mark"].toString(); - - // check if the marks are numerical values - if (!lMark.contains(":") && !rMark.contains(":")) return lMark.toFloat() < rMark.toFloat(); - - // compare time values if not numerical - QString lTime(""), rTime(""); - - for (QChar c : lMark) if (c.isDigit()) lTime.append(c); - for (QChar c : rMark) if (c.isDigit()) rTime.append(c); - - return lTime.compare(rTime) < 0; -} - -// static compare function for the amount of medals a competitor has gotten -bool compareMedals(const QJsonValue &left, const QJsonValue &right) { - QJsonObject lMedals = left.toObject()["medals"].toObject(); - QJsonObject rMedals = right.toObject()["medals"].toObject(); - - int gold = lMedals["ME_GOLD"].toInt() - rMedals["ME_GOLD"].toInt(); - int silver = lMedals["ME_SILVER"].toInt() - rMedals["ME_SILVER"].toInt(); - int bronze = lMedals["ME_BRONZE"].toInt() - rMedals["ME_BRONZE"].toInt(); - - return gold < 0 || (gold == 0 && (silver < 0 || (silver == 0 && bronze < 0))); -} - -/** - * @brief Sport::lastName Reduce the full name to the part that is marked in capital letters (probably last name). - * @param competitors The competitors of one category. - */ -void Sport::lastName(QJsonArray &competitors) { - // validate competitors - if (competitors.isEmpty() || !competitors[0].toObject().contains("name")) return; - - for (int i = 0; i < competitors.size(); ++i) { - string fullName = competitors[i].toObject()["name"].toString().toUtf8().constData(); - - // regex to identify names, written in CAPS - regex r("[A-Z']{2,}"); - smatch m; - - regex_search(fullName, m, r); - - // combine found names again - string lastName = ""; - for (string s : m) lastName = lastName + s + " "; - - // remove last space - QJsonValue nameValue = QJsonValue(QString(lastName.substr(0, lastName.size() - 1).c_str())); - - // create new object with replaced name - QJsonObject comp(competitors[i].toObject()); - comp.remove("name"); - comp.insert("name", nameValue); - - // replace competitor in array - competitors.replace(i, comp); - } -} - -/** - * @brief Sport::validateDiscipline Validates the discipline object. Checks for the units attribute. - * @return True, if discipline contains units. - */ -bool Sport::validateDiscipline() { - return this->discipline.contains("units"); -} - -/** - * @brief Sport::getCategories Reads all possible categories (also called units). - * @return A set of all category names. - */ -set Sport::getCategories() { - set categoryNames; - - if (!validateDiscipline()) return categoryNames; - - // search in each unit for the category (named "eventUnitName") - for (const QJsonValueRef &unitRef : this->discipline["units"].toArray()) { - QJsonObject unit = unitRef.toObject(); - - // validate unit - if (!unit.contains("eventUnitName")) continue; - - categoryNames.insert(unit["eventUnitName"].toString()); - } - - return categoryNames; -} - -/** - * @brief Sport::getCompetitorsByCategory Searches for all competitors, who took part in the given category. - * @param category The category to search in. - * @return An QJsonArray with all competitors as QJsonValueRef, which can be casted to QJsonObject. - */ -QJsonArray Sport::getCompetitorsByCategory(QString category) { - QJsonArray competitors; - - if (!validateDiscipline()) return competitors; - - for (const QJsonValueRef &unitRef : this->discipline["units"].toArray()) { - QJsonObject unit = unitRef.toObject(); - - // validate unit - if (!unit.contains("eventUnitName") || !unit.contains("competitors")) continue; - - // search all units with the same category - if (unit["eventUnitName"].toString().compare(category, Qt::CaseSensitive) != 0) continue; - - // add all competitors from one unit - for (const QJsonValueRef &compRef : unit["competitors"].toArray()) { - competitors.push_back(compRef.toObject()); - } - } - - return QJsonArray(competitors); -} - -/** - * @brief Sport::getCompetitorsWithMedal Filters all competitors, who have at least one medal. These objects are different from getCompetitorsByCategory !!! - * @return All competitors, who won at least one medal. Structure of one competitor: {code, name, noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}} - */ -QJsonArray Sport::getCompetitorsWithMedal() { - map competitors; - - if (!validateDiscipline()) return QJsonArray(); - - // filter all units, which have medal events - QJsonArray units = filter(this->discipline["units"].toArray(), [](QJsonObject unit){ - // search all units with Final, Gold or Bronze in their name, because these are the categories with the medal winners - QString unitName = unit["eventUnitName"].toString(); - return unitName.contains("Bronze", Qt::CaseSensitive) - || unitName.contains("Gold", Qt::CaseSensitive) - || unitName.contains("Final", Qt::CaseSensitive); - }); - - for (const QJsonValueRef &unitRef : units) { - QJsonObject unit = unitRef.toObject(); - - // validate unit - if (!unit.contains("competitors")) continue; - - // filter all competitors, who won medals - QJsonArray medalComps = filter(unit["competitors"].toArray(), [](QJsonObject comp) { - if (!comp.contains("results")) return false; - - QString medalType = comp["results"].toObject()["medalType"].toString(); - return !medalType.isEmpty(); - }); - - for (const QJsonValueRef &medalCompRef : medalComps) { - QJsonObject medalComp = medalCompRef.toObject(); - - // validate competitor (with medal) - if (!medalComp.contains("name") - || !medalComp.contains("results") - || !medalComp["results"].toObject().contains("medalType")) continue; - - QString name = medalComp["name"].toString(); - QString medalType = medalComp["results"].toObject()["medalType"].toString(); - - // check if competitor has other medal(s) - if (competitors.find(name) == competitors.end()) { - competitors.insert({name, createCompetitorWithMedals(medalComp)}); - } - - // update the medal count - QJsonObject updatedMedalCount = QJsonObject(competitors.find(name)->second["medals"].toObject()); - - int amount = updatedMedalCount[medalType].toInt() + 1; - updatedMedalCount.remove(medalType); - updatedMedalCount.insert(medalType, amount); - - // create new medals QJsonObject and set it in the map - competitors.find(name)->second["medals"] = updatedMedalCount; - } - } - - // convert map to QJsonArray - QJsonArray output; - for (const pair &competitor : competitors) { - output.append(competitor.second); - } - - return output; -} - -/** - * @brief Sport::createCompetitorWithMedals Creates a competitor QJsonObject with the following attributes: code, name, noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE} - * @param comp The original competitor object. - * @return A competitor object with medal counts. - */ -QJsonObject Sport::createCompetitorWithMedals(QJsonObject comp) { - // repair competitor if something is missing - if (!comp.contains("code")) comp.insert("code", "0"); - if (!comp.contains("name")) comp.insert("code", ""); - if (!comp.contains("noc")) comp.insert("code", ""); - - // create new competitor QJsonObject and add it to the competitor map - QJsonObject medals { - {"ME_GOLD", 0}, - {"ME_SILVER", 0}, - {"ME_BRONZE", 0} - }; - - QJsonObject medalComp { - {"code", comp["code"].toString()}, - {"name", comp["name"].toString()}, - {"noc", comp["noc"].toString()}, - {"medals", medals} - }; - - return medalComp; -} - -/** - * @brief Sport::filterByName Filter the competitors by name (case insensitive). - * @param competitors The competitors of one category. - * @param name The (part of the) name to search for. - */ -void Sport::filterByName(QJsonArray &competitors, QString name) { - filterCompetitors(competitors, QString("name"), name); -} - -/** - * @brief Sport::filterByCountry Filter the competitors by their national olympics comittee (case insensitive, short form). - * @param competitors The competitors of one category. - * @param nocShort The (part of the) national olympics comittee short name to search for. - */ -void Sport::filterByCountry(QJsonArray &competitors, QString nocShort) { - filterCompetitors(competitors, QString("noc"), nocShort); -} - -/** - * @brief Sport::filterCompetitors Filters the given QJsonArray by comparing the value of a certain attribute with the given filter string. - * @param competitors The competitors of one category. - * @param attribute The attribute to filter by. - * @param filter The string, which should be contained. - */ -void Sport::filterCompetitors(QJsonArray &competitors, QString attribute, QString filter) { - for (int i = 0; i < competitors.size(); i++) { - QJsonObject comp = competitors[i].toObject(); - - if (!comp.contains(attribute) || !comp[attribute].toString().contains(filter, Qt::CaseInsensitive)) { - // remove the competitor, if the attribute does not fit the filter string - competitors.removeAt(i); - i--; - } - - } -} - -/** - * @brief Sport::sortByName Sort the competitors by their name (alphabetical, ascending). - * @param competitors The competitors of one category. - */ -void Sport::sortByName(QJsonArray &competitors) { - sortCompetitors(competitors, genCompare( QString("name") )); -} - -/** - * @brief Sport::sortByCountry Sort the competitors by their national olympic comittee short name (alphabetical, ascending). - * @param competitors The competitors of one category. - */ -void Sport::sortByCountry(QJsonArray &competitors) { - sortCompetitors(competitors, genCompare( QString("noc") )); -} - -/** - * @brief Sport::sortByResult Sort the competitors by their results in one specific category (numerical, ascending). - * @param competitors The competitors of one category. - */ -void Sport::sortByResult(QJsonArray &competitors) { - if (competitors.isEmpty()) return; - - QJsonObject comp = competitors[0].toObject(); - - if (comp.contains("results")) sortCompetitors(competitors, compareMark); - else if (comp.contains("medals")) sortCompetitors(competitors, compareMedals); -} - -/** - * @brief Sport::sortCompetitors Sorts the given QJsonArray according to the compare function. - * @param competitors The competitors of one category. - * @param compare A function to compare two competitors with each other. This defines the order. - */ -void Sport::sortCompetitors(QJsonArray &competitors, function compare) { - make_heap(competitors.begin(), competitors.end(), compare); - sort_heap(competitors.begin(), competitors.end(), compare); -} - -/** - * @brief Sport::reverseOrder Reverses the order of the competitors. - * @param competitors The competitors of one category. - */ -void Sport::reverseOrder(QJsonArray &competitors) { - int iterations = competitors.size() / 2; // automatically rounds down - - for (int i = 0; i < iterations; i++) { - QJsonObject temp = competitors[i].toObject(); - competitors[i] = competitors[competitors.size() - 1 - i].toObject(); - competitors[competitors.size() - 1 - i] = temp; - } -} - -/** - * @brief Sport::addRelativeToFirst Adds a relative value to the result of all competitors. Relative to the first competitor in the QJsonArray. - * Stores the statistic in obj->results->stat for each competitor. - * @param competitors The competitors of one category. - */ -void Sport::addRelativeToFirst(QJsonArray &competitors) { - if (competitors.isEmpty()) return; - - QJsonObject reference = competitors[0].toObject(); - - // validate competitors - if (!reference.contains("results")) return; - - QString refVal = reference["results"].toObject()["mark"].toString(); - - for (int i = 0; i < competitors.size(); i++) { - QJsonObject competitor = competitors[i].toObject(); - QJsonObject results = competitor["results"].toObject(); - - if (results.contains("stat")) results.remove("stat"); - - // format relative float value to string with 2 digits after decimal point and sign - stringstream sstream; - sstream << fixed << setprecision(2) << calcRelativeStat(refVal, results["mark"].toString()); - QString stat(sstream.str().c_str()); - stat.append("%"); - if (stat.at(0).isNumber()) stat = QString("+").append(stat); - - results.insert("stat", stat); - - competitor.remove("results"); - competitor.insert("results", results); - - competitors.replace(i, competitor); - } - -} - -/** - * @brief Sport::calcRelativeStat Calculates the relative deviation of val from ref in percent. - * @param ref The reference value. - * @param val The value to calculate the deviation from. - * @return The deviation from ref to val in percent. - */ -float Sport::calcRelativeStat(QString ref, QString val) { - // check if the value is not a time - if (!ref.contains(":") && !val.contains(":")) { - float fRef = ref.toFloat(); - - if (fRef == 0) return 0.0; - return ((val.toFloat() * 100)/ fRef) - 100; - } - - regex r("\\d+"); - smatch mref, mval; - string sref = ref.toUtf8().constData(); - string sval = val.toUtf8().constData(); - - regex_search(sref, mref, r); - regex_search(sval, mval, r); - - float timeRef = stof(mref.str(1)) * 60 * 100 + stof(mref.str(2)) * 100 + stof(mref.str(3)); - float timeVal = stof(mval.str(1)) * 60 * 100 + stof(mval.str(2)) * 100 + stof(mval.str(3)); - - return ((timeVal * 100) / timeRef) - 100; -} diff --git a/src/discipline/Sport.h b/src/discipline/Sport.h deleted file mode 100644 index 147f6e8..0000000 --- a/src/discipline/Sport.h +++ /dev/null @@ -1,92 +0,0 @@ - -#ifndef ITAT_CHALLANGE_OLYMPICS_SPORT_H -#define ITAT_CHALLANGE_OLYMPICS_SPORT_H - - -#include - -#include -#include -#include - -using namespace std; - - -class Sport { - -public: - - Sport(QJsonObject discipline) { - this->discipline = discipline; - } - - set getCategories(); - QJsonArray getCompetitorsByCategory(QString category); - QJsonArray getCompetitorsWithMedal(); - - // filter to change the current competitor array - void lastName(QJsonArray &competitors); - void filterByName(QJsonArray &competitors, QString name); - void filterByCountry(QJsonArray &competitors, QString nocShort); - - // sort functions to change the order of the current competitor array - void sortByName(QJsonArray &competitors); - void sortByCountry(QJsonArray &competitors); - void sortByResult(QJsonArray &competitors); - void reverseOrder(QJsonArray &competitors); - - // statistic function(s) - void addRelativeToFirst(QJsonArray &competitors); - - void setDiscipline(QJsonObject discipline) { - this->discipline = QJsonObject(discipline); - } - -private: - - /* - * Analysis of provided competitor objects: - * - * Attributes: - * - code - * - noc (national olympics comittee) - * - name (sometimes the country name? mostly the competitors name) - * - order - * [- results] (only if the results are out!) - * - * - * Analysis of provided result objects: - * - * Attributes: - * - position - * - mark - * - medalType - * - irk - * [- winnerLoserTie] (only if provided in the discipline?) - * - * Analysis of where to find the medal winners: - * - * Search for ... in category name. - * - "Bronze" - * - "Gold" - * - "Final" - * - * ! ATTENTION ! - * When searching for "Final" there might be "Final A", "Final B", etc. - * The results will only be in ONE of these categories! - * -> which is good... cause then we can count occurences. - */ - QJsonObject discipline; - - void filterCompetitors(QJsonArray &competitors, QString attribute, QString filter); - void sortCompetitors(QJsonArray &competitors, function compare); - - bool validateDiscipline(); - QJsonObject createCompetitorWithMedals(QJsonObject medalComp); - - float calcRelativeStat(QString ref, QString val); - -}; - - -#endif //ITAT_CHALLANGE_OLYMPICS_SPORT_H diff --git a/src/model/Competitor.cpp b/src/model/Competitor.cpp new file mode 100644 index 0000000..a5d2e69 --- /dev/null +++ b/src/model/Competitor.cpp @@ -0,0 +1,15 @@ + +#include "Competitor.h" + +bool Competitor::setCompetitor(const QJsonObject &competitor) { + if (!competitor.contains("code") + || !competitor.contains("name") + || !competitor.contains("noc")) { + throw invalid_argument("Not a competitor object."); + } + + this->code = competitor["code"].toString(); + this->name = competitor["name"].toString(); + this->noc = competitor["noc"].toString(); + return true; +} diff --git a/src/model/Competitor.h b/src/model/Competitor.h new file mode 100644 index 0000000..919c2fa --- /dev/null +++ b/src/model/Competitor.h @@ -0,0 +1,39 @@ + +#ifndef ITAT_CHALLANGE_OLYMPICS_COMPETITOR_H +#define ITAT_CHALLANGE_OLYMPICS_COMPETITOR_H + +#include +#include +#include +#include + +using namespace std; + +class Competitor { + +public: + Competitor(const Competitor &competitor) { + this->code = competitor.code; + this->name = competitor.name; + this->noc = competitor.noc; + } + + Competitor(const QJsonObject &competitor) { + setCompetitor(competitor); + } + + QString getCode() { return this->code; } + QString getName() { return this->name; } + QString getNOC() { return this->noc; } + + bool setCompetitor(const QJsonObject &competitor); + +private: + QString code; + QString name; + QString noc; + +}; + + +#endif //ITAT_CHALLANGE_OLYMPICS_COMPETITOR_H diff --git a/src/model/CompetitorWithResults.cpp b/src/model/CompetitorWithResults.cpp new file mode 100644 index 0000000..d29441d --- /dev/null +++ b/src/model/CompetitorWithResults.cpp @@ -0,0 +1,15 @@ + +#include "CompetitorWithResults.h" + +bool CompetitorWithResults::setResults(const QJsonObject &results) { + if (!results.contains("mark") + || !results.contains("medalType")) { + throw invalid_argument("Results object of competitor is incomplete."); + } + + this->results = { + {QString("mark"), results["mark"].toString()}, + {QString("medalType"), results["medalType"].toString()} + }; + return true; +} diff --git a/src/model/CompetitorWithResults.h b/src/model/CompetitorWithResults.h new file mode 100644 index 0000000..cb952f2 --- /dev/null +++ b/src/model/CompetitorWithResults.h @@ -0,0 +1,28 @@ + +#ifndef ITAT_CHALLANGE_OLYMPICS_COMPETITORWITHRESULTS_H +#define ITAT_CHALLANGE_OLYMPICS_COMPETITORWITHRESULTS_H + +#include "Competitor.h" +#include +#include +#include +#include + +class CompetitorWithResults : public Competitor { + +public: + CompetitorWithResults(const QJsonObject &competitor) : Competitor(competitor) { + if (!competitor.contains("results")) throw invalid_argument("Competitor does not contain results."); + QJsonObject results = competitor["results"].toObject(); + setResults(results); + } + + bool setResults(const QJsonObject &results); + +private: + QMap results; + +}; + + +#endif //ITAT_CHALLANGE_OLYMPICS_COMPETITORWITHRESULTS_H diff --git a/src/model/MedalWinner.cpp b/src/model/MedalWinner.cpp new file mode 100644 index 0000000..c4cb663 --- /dev/null +++ b/src/model/MedalWinner.cpp @@ -0,0 +1,18 @@ + +#include "MedalWinner.h" + +bool MedalWinner::setMedals(const QJsonObject &medals) { + if (!medals.contains("ME_GOLD") + || !medals.contains("ME_SILVER") + || !medals.contains("ME_BRONZE")) { + throw invalid_argument("Medal object of competitor is incomplete."); + } + + this->wonMedals = { + {QString("ME_GOLD"), medals["ME_GOLD"].toString()}, + {QString("ME_SILVER"), medals["ME_SILVER"].toString()}, + {QString("ME_BRONZE"), medals["ME_BRONZE"].toString()} + }; + + return true; +} diff --git a/src/model/MedalWinner.h b/src/model/MedalWinner.h new file mode 100644 index 0000000..e771185 --- /dev/null +++ b/src/model/MedalWinner.h @@ -0,0 +1,35 @@ + +#ifndef ITAT_CHALLANGE_OLYMPICS_MEDALWINNER_H +#define ITAT_CHALLANGE_OLYMPICS_MEDALWINNER_H + +#include "Competitor.h" +#include +#include +#include + +class MedalWinner : public Competitor { + +public: + MedalWinner(const MedalWinner &medalWinner) : Competitor(medalWinner) { + this->wonMedals = { + {QString("ME_GOLD"), medalWinner.wonMedals.value("ME_GOLD")}, + {QString("ME_SILVER"), medalWinner.wonMedals.value("ME_SILVER")}, + {QString("ME_BRONZE"), medalWinner.wonMedals.value("ME_BRONZE")} + }; + } + + MedalWinner(const QJsonObject &competitor) : Competitor(competitor) { + if (competitor.contains("medals")) throw invalid_argument("Competitor has no medals."); + QJsonObject medals = competitor["medals"].toObject(); + setMedals(medals); + } + + bool setMedals(const QJsonObject &medals); + +private: + QMap wonMedals; + +}; + + +#endif //ITAT_CHALLANGE_OLYMPICS_MEDALWINNER_H diff --git a/src/model/Sport.cpp b/src/model/Sport.cpp new file mode 100644 index 0000000..21b9b9f --- /dev/null +++ b/src/model/Sport.cpp @@ -0,0 +1,423 @@ + +#include "Sport.h" + +// categories +#include + +// sorting and filtering +#include +#include +#include + +// float to string formatting +#include +#include + +#include +#include +#include +#include + +// QJsonArray filter function, provide with input array and evaluation function +QJsonArray filter(QJsonArray input, function eval) { + QJsonArray output; + + for (const QJsonValueRef &elemRef :input) { + QJsonObject elem = elemRef.toObject(); + if(eval(elem)) { + output.append(elem); + } + } + + return output; +} + +// static compare function for specific attribute in competitors +function genCompare(QString attribute) { + return [attribute](const QJsonValue &left, const QJsonValue &right) { + QString l = left.toObject()[attribute].toString(); + QString r = right.toObject()[attribute].toString(); + return l.compare(r) < 0; + }; +} + +// static compare function for the results of a competitor in a specific competition (also called mark) +bool compareMark(const QJsonValue &left, const QJsonValue &right) { + // check if one competitor has no mark + QJsonObject l = left.toObject(); + if (!l.contains("results")) return true; + QJsonObject r = right.toObject(); + if (!r.contains("results")) return false; + + QString lMark = l["results"].toObject()["mark"].toString(); + QString rMark = r["results"].toObject()["mark"].toString(); + + // check if the marks are numerical values + if (!lMark.contains(":") && !rMark.contains(":")) return lMark.toFloat() < rMark.toFloat(); + + // compare time values if not numerical + QString lTime(""), rTime(""); + + for (QChar c : lMark) if (c.isDigit()) lTime.append(c); + for (QChar c : rMark) if (c.isDigit()) rTime.append(c); + + return lTime.compare(rTime) < 0; +} + +// static compare function for the amount of medals a competitor has gotten +bool compareMedals(const QJsonValue &left, const QJsonValue &right) { + QJsonObject lMedals = left.toObject()["medals"].toObject(); + QJsonObject rMedals = right.toObject()["medals"].toObject(); + + int gold = lMedals["ME_GOLD"].toInt() - rMedals["ME_GOLD"].toInt(); + int silver = lMedals["ME_SILVER"].toInt() - rMedals["ME_SILVER"].toInt(); + int bronze = lMedals["ME_BRONZE"].toInt() - rMedals["ME_BRONZE"].toInt(); + + return gold < 0 || (gold == 0 && (silver < 0 || (silver == 0 && bronze < 0))); +} + +/** + * @brief Sport::lastName Reduce the full name to the part that is marked in capital letters (probably last name). + * @param competitors The competitors of one category. + */ +void Sport::lastName(QJsonArray &competitors) { + // validate competitors + if (competitors.isEmpty() || !competitors[0].toObject().contains("name")) return; + + for (int i = 0; i < competitors.size(); ++i) { + string fullName = competitors[i].toObject()["name"].toString().toUtf8().constData(); + + // regex to identify names, written in CAPS + regex r("[A-Z']{2,}"); + smatch m; + + regex_search(fullName, m, r); + + // combine found names again + string lastName = ""; + for (string s : m) lastName = lastName + s + " "; + + // remove last space + QJsonValue nameValue = QJsonValue(QString(lastName.substr(0, lastName.size() - 1).c_str())); + + // create new object with replaced name + QJsonObject comp(competitors[i].toObject()); + comp.remove("name"); + comp.insert("name", nameValue); + + // replace competitor in array + competitors.replace(i, comp); + } +} + +/** + * @brief Sport::validateDiscipline Validates the discipline object. Checks for the units attribute. + * @return True, if discipline contains units. + */ +bool Sport::validateDiscipline() { + return this->discipline.contains("units"); +} + +/** + * @brief Sport::getCategories Reads all possible categories (also called units). + * @return A set of all category names. + */ +set Sport::getCategories() { + set categoryNames; + + if (!validateDiscipline()) return categoryNames; + + // search in each unit for the category (named "eventUnitName") + for (const QJsonValueRef &unitRef : this->discipline["units"].toArray()) { + QJsonObject unit = unitRef.toObject(); + + // validate unit + if (!unit.contains("eventUnitName")) continue; + + categoryNames.insert(unit["eventUnitName"].toString()); + } + + return categoryNames; +} + +/** + * @brief Sport::getCompetitorsByCategory Searches for all competitors, who took part in the given category. + * @param category The category to search in. + * @return An QJsonArray with all competitors as QJsonValueRef, which can be casted to QJsonObject. + */ +QJsonArray Sport::getCompetitorsByCategory(QString category) { + QJsonArray competitors; + + if (!validateDiscipline()) return competitors; + + for (const QJsonValueRef &unitRef : this->discipline["units"].toArray()) { + QJsonObject unit = unitRef.toObject(); + + // validate unit + if (!unit.contains("eventUnitName") || !unit.contains("competitors")) continue; + + // search all units with the same category + if (unit["eventUnitName"].toString().compare(category, Qt::CaseSensitive) != 0) continue; + + // add all competitors from one unit + for (const QJsonValueRef &compRef : unit["competitors"].toArray()) { + competitors.push_back(compRef.toObject()); + } + } + + return QJsonArray(competitors); +} + +/** + * @brief Sport::getCompetitorsWithMedal Filters all competitors, who have at least one medal. These objects are different from getCompetitorsByCategory !!! + * @return All competitors, who won at least one medal. Structure of one competitor: {code, name, noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}} + */ +QJsonArray Sport::getCompetitorsWithMedal() { + map competitors; + + if (!validateDiscipline()) return QJsonArray(); + + // filter all units, which have medal events + QJsonArray units = filter(this->discipline["units"].toArray(), [](QJsonObject unit){ + // search all units with Final, Gold or Bronze in their name, because these are the categories with the medal winners + QString unitName = unit["eventUnitName"].toString(); + return unitName.contains("Bronze", Qt::CaseSensitive) + || unitName.contains("Gold", Qt::CaseSensitive) + || unitName.contains("Final", Qt::CaseSensitive); + }); + + for (const QJsonValueRef &unitRef : units) { + QJsonObject unit = unitRef.toObject(); + + // validate unit + if (!unit.contains("competitors")) continue; + + // filter all competitors, who won medals + QJsonArray medalComps = filter(unit["competitors"].toArray(), [](QJsonObject comp) { + if (!comp.contains("results")) return false; + + QString medalType = comp["results"].toObject()["medalType"].toString(); + return !medalType.isEmpty(); + }); + + for (const QJsonValueRef &medalCompRef : medalComps) { + QJsonObject medalComp = medalCompRef.toObject(); + + // validate competitor (with medal) + if (!medalComp.contains("name") + || !medalComp.contains("results") + || !medalComp["results"].toObject().contains("medalType")) continue; + + QString name = medalComp["name"].toString(); + QString medalType = medalComp["results"].toObject()["medalType"].toString(); + + // check if competitor has other medal(s) + if (competitors.find(name) == competitors.end()) { + competitors.insert({name, createCompetitorWithMedals(medalComp)}); + } + + // update the medal count + QJsonObject updatedMedalCount = QJsonObject(competitors.find(name)->second["medals"].toObject()); + + int amount = updatedMedalCount[medalType].toInt() + 1; + updatedMedalCount.remove(medalType); + updatedMedalCount.insert(medalType, amount); + + // create new medals QJsonObject and set it in the map + competitors.find(name)->second["medals"] = updatedMedalCount; + } + } + + // convert map to QJsonArray + QJsonArray output; + for (const pair &competitor : competitors) { + output.append(competitor.second); + } + + return output; +} + +/** + * @brief Sport::createCompetitorWithMedals Creates a competitor QJsonObject with the following attributes: code, name, noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE} + * @param comp The original competitor object. + * @return A competitor object with medal counts. + */ +QJsonObject Sport::createCompetitorWithMedals(QJsonObject comp) { + // repair competitor if something is missing + if (!comp.contains("code")) comp.insert("code", "0"); + if (!comp.contains("name")) comp.insert("code", ""); + if (!comp.contains("noc")) comp.insert("code", ""); + + // create new competitor QJsonObject and add it to the competitor map + QJsonObject medals { + {"ME_GOLD", 0}, + {"ME_SILVER", 0}, + {"ME_BRONZE", 0} + }; + + QJsonObject medalComp { + {"code", comp["code"].toString()}, + {"name", comp["name"].toString()}, + {"noc", comp["noc"].toString()}, + {"medals", medals} + }; + + return medalComp; +} + +/** + * @brief Sport::filterByName Filter the competitors by name (case insensitive). + * @param competitors The competitors of one category. + * @param name The (part of the) name to search for. + */ +void Sport::filterByName(QJsonArray &competitors, QString name) { + filterCompetitors(competitors, QString("name"), name); +} + +/** + * @brief Sport::filterByCountry Filter the competitors by their national olympics comittee (case insensitive, short form). + * @param competitors The competitors of one category. + * @param nocShort The (part of the) national olympics comittee short name to search for. + */ +void Sport::filterByCountry(QJsonArray &competitors, QString nocShort) { + filterCompetitors(competitors, QString("noc"), nocShort); +} + +/** + * @brief Sport::filterCompetitors Filters the given QJsonArray by comparing the value of a certain attribute with the given filter string. + * @param competitors The competitors of one category. + * @param attribute The attribute to filter by. + * @param filter The string, which should be contained. + */ +void Sport::filterCompetitors(QJsonArray &competitors, QString attribute, QString filter) { + for (int i = 0; i < competitors.size(); i++) { + QJsonObject comp = competitors[i].toObject(); + + if (!comp.contains(attribute) || !comp[attribute].toString().contains(filter, Qt::CaseInsensitive)) { + // remove the competitor, if the attribute does not fit the filter string + competitors.removeAt(i); + i--; + } + + } +} + +/** + * @brief Sport::sortByName Sort the competitors by their name (alphabetical, ascending). + * @param competitors The competitors of one category. + */ +void Sport::sortByName(QJsonArray &competitors) { + sortCompetitors(competitors, genCompare( QString("name") )); +} + +/** + * @brief Sport::sortByCountry Sort the competitors by their national olympic comittee short name (alphabetical, ascending). + * @param competitors The competitors of one category. + */ +void Sport::sortByCountry(QJsonArray &competitors) { + sortCompetitors(competitors, genCompare( QString("noc") )); +} + +/** + * @brief Sport::sortByResult Sort the competitors by their results in one specific category (numerical, ascending). + * @param competitors The competitors of one category. + */ +void Sport::sortByResult(QJsonArray &competitors) { + if (competitors.isEmpty()) return; + + QJsonObject comp = competitors[0].toObject(); + + if (comp.contains("results")) sortCompetitors(competitors, compareMark); + else if (comp.contains("medals")) sortCompetitors(competitors, compareMedals); +} + +/** + * @brief Sport::sortCompetitors Sorts the given QJsonArray according to the compare function. + * @param competitors The competitors of one category. + * @param compare A function to compare two competitors with each other. This defines the order. + */ +void Sport::sortCompetitors(QJsonArray &competitors, function compare) { + make_heap(competitors.begin(), competitors.end(), compare); + sort_heap(competitors.begin(), competitors.end(), compare); +} + +/** + * @brief Sport::reverseOrder Reverses the order of the competitors. + * @param competitors The competitors of one category. + */ +void Sport::reverseOrder(QJsonArray &competitors) { + int iterations = competitors.size() / 2; // automatically rounds down + + for (int i = 0; i < iterations; i++) { + QJsonObject temp = competitors[i].toObject(); + competitors[i] = competitors[competitors.size() - 1 - i].toObject(); + competitors[competitors.size() - 1 - i] = temp; + } +} + +/** + * @brief Sport::addRelativeToFirst Adds a relative value to the result of all competitors. Relative to the first competitor in the QJsonArray. + * Stores the statistic in obj->results->stat for each competitor. + * @param competitors The competitors of one category. + */ +void Sport::addRelativeToFirst(QJsonArray &competitors) { + if (competitors.isEmpty()) return; + + QJsonObject reference = competitors[0].toObject(); + + // validate competitors + if (!reference.contains("results")) return; + + QString refVal = reference["results"].toObject()["mark"].toString(); + + for (int i = 0; i < competitors.size(); i++) { + QJsonObject competitor = competitors[i].toObject(); + QJsonObject results = competitor["results"].toObject(); + + if (results.contains("stat")) results.remove("stat"); + + // format relative float value to string with 2 digits after decimal point and sign + stringstream sstream; + sstream << fixed << setprecision(2) << calcRelativeStat(refVal, results["mark"].toString()); + QString stat(sstream.str().c_str()); + stat.append("%"); + if (stat.at(0).isNumber()) stat = QString("+").append(stat); + + results.insert("stat", stat); + + competitor.remove("results"); + competitor.insert("results", results); + + competitors.replace(i, competitor); + } + +} + +/** + * @brief Sport::calcRelativeStat Calculates the relative deviation of val from ref in percent. + * @param ref The reference value. + * @param val The value to calculate the deviation from. + * @return The deviation from ref to val in percent. + */ +float Sport::calcRelativeStat(QString ref, QString val) { + // check if the value is not a time + if (!ref.contains(":") && !val.contains(":")) { + float fRef = ref.toFloat(); + + if (fRef == 0) return 0.0; + return ((val.toFloat() * 100)/ fRef) - 100; + } + + regex r("\\d+"); + smatch mref, mval; + string sref = ref.toUtf8().constData(); + string sval = val.toUtf8().constData(); + + regex_search(sref, mref, r); + regex_search(sval, mval, r); + + float timeRef = stof(mref.str(1)) * 60 * 100 + stof(mref.str(2)) * 100 + stof(mref.str(3)); + float timeVal = stof(mval.str(1)) * 60 * 100 + stof(mval.str(2)) * 100 + stof(mval.str(3)); + + return ((timeVal * 100) / timeRef) - 100; +} diff --git a/src/model/Sport.h b/src/model/Sport.h new file mode 100644 index 0000000..147f6e8 --- /dev/null +++ b/src/model/Sport.h @@ -0,0 +1,92 @@ + +#ifndef ITAT_CHALLANGE_OLYMPICS_SPORT_H +#define ITAT_CHALLANGE_OLYMPICS_SPORT_H + + +#include + +#include +#include +#include + +using namespace std; + + +class Sport { + +public: + + Sport(QJsonObject discipline) { + this->discipline = discipline; + } + + set getCategories(); + QJsonArray getCompetitorsByCategory(QString category); + QJsonArray getCompetitorsWithMedal(); + + // filter to change the current competitor array + void lastName(QJsonArray &competitors); + void filterByName(QJsonArray &competitors, QString name); + void filterByCountry(QJsonArray &competitors, QString nocShort); + + // sort functions to change the order of the current competitor array + void sortByName(QJsonArray &competitors); + void sortByCountry(QJsonArray &competitors); + void sortByResult(QJsonArray &competitors); + void reverseOrder(QJsonArray &competitors); + + // statistic function(s) + void addRelativeToFirst(QJsonArray &competitors); + + void setDiscipline(QJsonObject discipline) { + this->discipline = QJsonObject(discipline); + } + +private: + + /* + * Analysis of provided competitor objects: + * + * Attributes: + * - code + * - noc (national olympics comittee) + * - name (sometimes the country name? mostly the competitors name) + * - order + * [- results] (only if the results are out!) + * + * + * Analysis of provided result objects: + * + * Attributes: + * - position + * - mark + * - medalType + * - irk + * [- winnerLoserTie] (only if provided in the discipline?) + * + * Analysis of where to find the medal winners: + * + * Search for ... in category name. + * - "Bronze" + * - "Gold" + * - "Final" + * + * ! ATTENTION ! + * When searching for "Final" there might be "Final A", "Final B", etc. + * The results will only be in ONE of these categories! + * -> which is good... cause then we can count occurences. + */ + QJsonObject discipline; + + void filterCompetitors(QJsonArray &competitors, QString attribute, QString filter); + void sortCompetitors(QJsonArray &competitors, function compare); + + bool validateDiscipline(); + QJsonObject createCompetitorWithMedals(QJsonObject medalComp); + + float calcRelativeStat(QString ref, QString val); + +}; + + +#endif //ITAT_CHALLANGE_OLYMPICS_SPORT_H -- cgit v1.2.3