From f24b4dcbd11336dabfd146c656e2437e4393b225 Mon Sep 17 00:00:00 2001 From: Steru Date: Fri, 16 Aug 2024 16:58:31 +0200 Subject: Integrated Sport class into SportModel class. --- CMakeLists.txt | 4 +- src/main/main.cpp | 2 +- src/model/Sport.cpp | 453 ---------------------------------------------- src/model/Sport.h | 127 ------------- src/model/SportModel.cpp | 455 +++++++++++++++++++++++++++++++++++++++++++++++ src/model/SportModel.h | 88 +++++++++ 6 files changed, 546 insertions(+), 583 deletions(-) delete mode 100644 src/model/Sport.cpp create mode 100644 src/model/SportModel.cpp create mode 100644 src/model/SportModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c30ba17..af81cc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,8 +31,8 @@ qt_add_qml_module(itat_challenge_olympics src/model/FilterModel.h src/model/MedalWinner.cpp src/model/MedalWinner.h - src/model/Sport.cpp - src/model/Sport.h + src/model/SportModel.cpp + src/model/SportModel.h RESOURCES res/pictograms/ARC_small.svg diff --git a/src/main/main.cpp b/src/main/main.cpp index 27a4800..a01a188 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -20,8 +20,8 @@ // console output #include // #include -#include "../model/Sport.h" #include "../model/FilterModel.h" +#include "../model/SportModel.h" int main(int argc, char *argv[]) { diff --git a/src/model/Sport.cpp b/src/model/Sport.cpp deleted file mode 100644 index 86ecd7c..0000000 --- a/src/model/Sport.cpp +++ /dev/null @@ -1,453 +0,0 @@ -#include "Sport.h" -#include "Competitor.h" - -// categories -#include -#include -#include -#include - -// sorting and filtering -#include -#include -#include - -// float to string formatting -#include -#include - -#include -#include -#include -#include - -namespace { - const QString &k_requestUrl = "https://sph-s-api.olympics.com/summer/schedules/api/ENG/schedule/discipline/"; -} - -SportModel::SportModel(QObject *parent) : QAbstractListModel(parent) { -} - -int SportModel::rowCount(const QModelIndex &parent) const { - Q_UNUSED(parent); - return m_sportList.size(); -} - -QVariant SportModel::data(const QModelIndex &index, int role) const { - if (index.isValid() && index.row() >= 0 && index.row() < m_sportList.size()) { - EventInfo *event = m_sportList[index.row()]; - - switch ((Role) role) { - case EventName: - return event->eventName(); - - case Competitors: - return event->competitors(); - } - } - - return {}; -} - - -QHash SportModel::roleNames() const { - QHash names; - names[EventName] = "eventName"; - names[Competitors] = "competitors"; - - return names; -} - -QString SportModel::discipline() const { - return m_discipline; -} - -void SportModel::setDiscipline(const QString &discipline) { - m_discipline = discipline; - disciplineChanged(); -} - - -void SportModel::request(QString discipline) { - setDiscipline(discipline); - m_reply = m_networkManager.get(QNetworkRequest( k_requestUrl + m_discipline)); - qDebug() << m_reply; - connect(m_reply, &QNetworkReply::finished, this, &SportModel::parseData); -} - -void SportModel::parseData() { - - if (m_reply->error() == QNetworkReply::NoError) { - beginResetModel(); - qDeleteAll(m_sportList); - m_sportList.clear(); - - - - QByteArray strReply = m_reply->readAll(); - - //parse json - // qDebug() << "Response:" << strReply; - QJsonDocument jsonDocument = QJsonDocument::fromJson(strReply); - - QJsonArray sports = jsonDocument["units"].toArray(); - for (const auto &sport : sports) { - QJsonObject entry = sport.toObject(); - - EventInfo *event = new EventInfo(this); - event->setEventName(entry["eventUnitName"].toString()); - - QList competitors; - for (const auto &competitor : entry["competitors"].toArray()) { - competitors << competitor.toObject()["name"].toString(); - } - event->setCompetitors(competitors); - - qDebug() << entry["eventUnitName"].toString(); - m_sportList << event; - } - endResetModel(); - } -} - -// 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; -} - -/** - * @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(QList &competitors) { - // validate competitors - if (competitors.isEmpty()) return; - - for (int i = 0; i < competitors.size(); i++) { - Competitor comp = competitors.value(i); - string fullName = comp.getName().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 - QString name = QString(lastName.substr(0, lastName.size() - 1).c_str()); - - // replace competitor name in list - comp.setName(name); - } -} - -/** - * @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. - */ -QList Sport::getCompetitorsByCategory(QString category) { - QList 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()) { - CompetitorWithResults comp = new CompetitorWithResults(); // TODO declare comp - comp.setCompetitorWithResults(compRef.toObject()); - competitors.push_back(comp); - } - } - - return 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, m_noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}} - */ -QList Sport::getCompetitorsWithMedal() { - map competitors; - - if (!validateDiscipline()) return QList(); - - // 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()["m_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("m_medalType")) continue; - - QString name = medalComp["name"].toString(); - QString medalType = medalComp["results"].toObject()["m_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 - QList output; - for (const pair &competitor : competitors) { - MedalWinner comp = new MedalWinner(); // TODO declare comp - comp.setMedalWinner(competitor.second); - output.append(comp); - } - - return output; -} - -/** - * @brief Sport::createCompetitorWithMedals Creates a competitor QJsonObject with the following attributes: code, name, m_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("m_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()}, - {"m_noc", comp["m_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(QList &competitors, QString name) { - filterCompetitors(competitors, 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(QList &competitors, QString nocShort) { - filterCompetitors(competitors, 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(QList &competitors, QString filter) { - for (int i = 0; i < competitors.size(); i++) { - if (!competitors.value(i).getNOC().contains(filter)) { - competitors.remove(i); - i--; - } - } -} - -/** - * @brief Sport::sortByName Sort the competitors by their name (alphabetical, ascending). - * @param competitors The competitors of one category. - */ -void Sport::sortByName(QList &competitors) { - if (competitors.isEmpty()) return; - sort(competitors.begin(), competitors.end(), Competitor::compareName); -} - -/** - * @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(QList &competitors) { - if (competitors.isEmpty()) return; - sort(competitors.begin(), competitors.end(), Competitor::compareNOC); -} - -/** - * @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(QList &competitors) { - if (competitors.isEmpty()) return; - sort(competitors.begin(), competitors.end(), CompetitorWithResults::compare); -} - -/** - * @brief Sport::sortByMedals Sort the competitors by their medal amounts in one specific category (numerical, ascending). - * @param competitors The competitors of one category. - */ -void Sport::sortByMedals(QList &competitors) { - if (competitors.isEmpty()) return; - sort(competitors.begin(), competitors.end(), MedalWinner::compare); -} - -/** - * @brief Sport::reverseOrder Reverses the order of the competitors. - * @param competitors The competitors of one category. - */ -void Sport::reverseOrder(QList &competitors) { - int iterations = competitors.size() / 2; // automatically rounds down - - for (int i = 0; i < iterations; i++) { - Competitor left = Competitor(competitors.value(i)); - Competitor right = Competitor(competitors.value(competitors.size() - 1 - i)); - - competitors.replace(i, right); - competitors.replace(competitors.size() - 1 - i, left); - } -} - -/** - * @brief Sport::addRelativeToFirst Adds a relative value to the result of all competitors. Relative to the first competitor in the QJsonArray. - * Stores the m_statistic in obj->results->stat for each competitor. - * @param competitors The competitors of one category. - */ -void Sport::addRelativeToFirst(QList &competitors) { - if (competitors.isEmpty()) return; - - QString reference = competitors.value(0).getMark(); - - for (CompetitorWithResults comp : competitors) { - QString result = comp.getMark(); - - // format relative float value to string with 2 digits after decimal point and sign - stringstream sstream; - sstream << fixed << setprecision(2) << calcRelativeStat(reference, result); - QString stat(sstream.str().c_str()); - stat.append("%"); - if (stat.at(0).isNumber()) stat = QString("+").append(stat); - - comp.setStatistic(stat); - } - -} - -/** - * @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 index c5d1c66..e69de29 100644 --- a/src/model/Sport.h +++ b/src/model/Sport.h @@ -1,127 +0,0 @@ -#pragma once - -#include "MedalWinner.h" -#include "CompetitorWithResults.h" -#include -#include -#include -#include - -#include -#include -#include -#include -#include "EventInfo.h" - -using namespace std; - -class SportModel : public QAbstractListModel -{ - Q_OBJECT - - Q_PROPERTY(QString discipline READ discipline WRITE setDiscipline NOTIFY disciplineChanged); - -public: - enum Role - { - EventName = Qt::UserRole + 1, - Competitors - }; - - explicit SportModel(QObject *parent = nullptr); - - virtual int rowCount(const QModelIndex &parent) const override; - virtual QVariant data(const QModelIndex &index, int role) const override; - virtual QHash roleNames() const override; - - QString discipline() const; - void setDiscipline(const QString &discipline); -public slots: - void request(QString discipline); - void parseData(); - - signals: - void disciplineChanged(); - - private: - QList m_sportList; - QString m_discipline; - QNetworkAccessManager m_networkManager; - QNetworkReply *m_reply = nullptr; -}; - -class Sport -{ - -public: - Sport(QJsonObject discipline) - { - this->discipline = discipline; - } - - set getCategories(); - QList getCompetitorsByCategory(QString category); - QList getCompetitorsWithMedal(); - - // filter to change the current competitor array - void lastName(QList &competitors); - void filterByName(QList &competitors, QString name); - void filterByCountry(QList &competitors, QString nocShort); - - // sort functions to change the order of the current competitor array - void sortByName(QList &competitors); - void sortByCountry(QList &competitors); - void sortByResult(QList &competitors); - void sortByMedals(QList &competitors); - void reverseOrder(QList &competitors); - - // statistic function(s) - void addRelativeToFirst(QList &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(QList &competitors, QString filter); - - bool validateDiscipline(); - QJsonObject createCompetitorWithMedals(QJsonObject medalComp); - - float calcRelativeStat(QString ref, QString val); -}; diff --git a/src/model/SportModel.cpp b/src/model/SportModel.cpp new file mode 100644 index 0000000..be9d326 --- /dev/null +++ b/src/model/SportModel.cpp @@ -0,0 +1,455 @@ +#include "SportModel.h" +#include "Competitor.h" + +// categories +#include +#include +#include +#include + +// sorting and filtering +#include +#include +#include + +// float to string formatting +#include +#include + +#include +#include +#include +#include + +namespace { + const QString &k_requestUrl = "https://sph-s-api.olympics.com/summer/schedules/api/ENG/schedule/discipline/"; +} + +SportModel::SportModel(QObject *parent) : QAbstractListModel(parent) { +} + +int SportModel::rowCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + return m_sportList.size(); +} + +QVariant SportModel::data(const QModelIndex &index, int role) const { + if (index.isValid() && index.row() >= 0 && index.row() < m_sportList.size()) { + EventInfo *event = m_sportList[index.row()]; + + switch ((Role) role) { + case EventName: + return event->eventName(); + + case Competitors: + return event->competitors(); + } + } + + return {}; +} + + +QHash SportModel::roleNames() const { + QHash names; + names[EventName] = "eventName"; + names[Competitors] = "competitors"; + + return names; +} + +QString SportModel::discipline() const { + return m_discipline; +} + +void SportModel::setDiscipline(const QString &discipline) { + m_discipline = discipline; + disciplineChanged(); +} + + +void SportModel::request(QString discipline) { + setDiscipline(discipline); + m_reply = m_networkManager.get(QNetworkRequest( k_requestUrl + m_discipline)); + qDebug() << m_reply; + connect(m_reply, &QNetworkReply::finished, this, &SportModel::parseData); +} + +void SportModel::parseData() { + + if (m_reply->error() == QNetworkReply::NoError) { + beginResetModel(); + qDeleteAll(m_sportList); + m_sportList.clear(); + + + + QByteArray strReply = m_reply->readAll(); + + //parse json + // qDebug() << "Response:" << strReply; + QJsonDocument jsonDocument = QJsonDocument::fromJson(strReply); + + QJsonArray sports = jsonDocument["units"].toArray(); + for (const auto &sport : sports) { + QJsonObject entry = sport.toObject(); + + EventInfo *event = new EventInfo(this); + event->setEventName(entry["eventUnitName"].toString()); + + QList competitors; + for (const auto &competitor : entry["competitors"].toArray()) { + competitors << competitor.toObject()["name"].toString(); + } + event->setCompetitors(competitors); + + qDebug() << entry["eventUnitName"].toString(); + m_sportList << event; + } + endResetModel(); + } +} + +// 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; +} + +/** + * @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 SportModel::lastName(QList &competitors) { + // validate competitors + if (competitors.isEmpty()) return; + + for (int i = 0; i < competitors.size(); i++) { + Competitor* comp = competitors.value(i); + string fullName = comp->getName().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 + QString name = QString(lastName.substr(0, lastName.size() - 1).c_str()); + + // replace competitor name in list + comp->setName(name); + } +} + +/** + * @brief Sport::validateDiscipline Validates the discipline object. Checks for the units attribute. + * @return True, if discipline contains units. + */ +bool SportModel::validateDiscipline() { + return this->o_discipline.contains("units"); +} + +/** + * @brief Sport::getCategories Reads all possible categories (also called units). + * @return A set of all category names. + */ +set SportModel::getCategories() { + set categoryNames; + + if (!validateDiscipline()) return categoryNames; + + // search in each unit for the category (named "eventUnitName") + for (const QJsonValueRef &unitRef : this->o_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. + */ +QList SportModel::getCompetitorsByCategory(QString category) { + QList competitors; + + if (!validateDiscipline()) return competitors; + + for (const QJsonValueRef &unitRef : this->o_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()) { + CompetitorWithResults *comp = new CompetitorWithResults(); // TODO declare comp + comp->setCompetitorWithResults(compRef.toObject()); + competitors.push_back(comp); + } + } + + return 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, m_noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}} + */ +QList SportModel::getCompetitorsWithMedal() { + map competitors; + + if (!validateDiscipline()) return QList(); + + // filter all units, which have medal events + QJsonArray units = filter(this->o_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()["m_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("m_medalType")) continue; + + QString name = medalComp["name"].toString(); + QString medalType = medalComp["results"].toObject()["m_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 + QList output; + for (const pair &competitor : competitors) { + MedalWinner *comp = new MedalWinner(); // TODO declare comp + comp->setMedalWinner(competitor.second); + output.append(comp); + } + + return output; +} + +/** + * @brief Sport::createCompetitorWithMedals Creates a competitor QJsonObject with the following attributes: code, name, m_noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE} + * @param comp The original competitor object. + * @return A competitor object with medal counts. + */ +QJsonObject SportModel::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("m_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()}, + {"m_noc", comp["m_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 SportModel::filterByName(QList &competitors, QString name) { + filterCompetitors(competitors, 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 SportModel::filterByCountry(QList &competitors, QString nocShort) { + filterCompetitors(competitors, 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 SportModel::filterCompetitors(QList &competitors, QString filter) { + for (int i = 0; i < competitors.size(); i++) { + if (!competitors.value(i)->getNOC().contains(filter)) { + competitors.remove(i); + i--; + } + } +} + +/** + * @brief Sport::sortByName Sort the competitors by their name (alphabetical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByName(QList &competitors) { + if (competitors.isEmpty()) return; + std::sort(competitors.begin(), competitors.end(), Competitor::compareName); +} + +/** + * @brief Sport::sortByCountry Sort the competitors by their national olympic comittee short name (alphabetical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByCountry(QList &competitors) { + if (competitors.isEmpty()) return; + std::sort(competitors.begin(), competitors.end(), Competitor::compareNOC); +} + +/** + * @brief Sport::sortByResult Sort the competitors by their results in one specific category (numerical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByResult(QList &competitors) { + if (competitors.isEmpty()) return; + std::sort(competitors.begin(), competitors.end(), CompetitorWithResults::compare); +} + +/** + * @brief Sport::sortByMedals Sort the competitors by their medal amounts in one specific category (numerical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByMedals(QList &competitors) { + if (competitors.isEmpty()) return; + std::sort(competitors.begin(), competitors.end(), MedalWinner::compare); +} + +/** + * @brief Sport::reverseOrder Reverses the order of the competitors. + * @param competitors The competitors of one category. + */ +void SportModel::reverseOrder(QList &competitors) { + int iterations = competitors.size() / 2; // automatically rounds down + + for (int i = 0; i < iterations; i++) { + Competitor *left = competitors.value(i); + Competitor *right = competitors.value(competitors.size() - 1 - i); + + competitors.replace(i, right); + competitors.replace(competitors.size() - 1 - i, left); + } +} + +/** + * @brief Sport::addRelativeToFirst Adds a relative value to the result of all competitors. Relative to the first competitor in the QJsonArray. + * Stores the m_statistic in obj->results->stat for each competitor. + * @param competitors The competitors of one category. + */ +void SportModel::addRelativeToFirst(QList &competitors) { + if (competitors.isEmpty()) return; + + QString reference = competitors.value(0)->getMark(); + + for (int i = 0; i < competitors.size(); i++) { + CompetitorWithResults *comp = competitors.value(i); + + QString result = comp->getMark(); + + // format relative float value to string with 2 digits after decimal point and sign + stringstream sstream; + sstream << fixed << setprecision(2) << calcRelativeStat(reference, result); + QString stat(sstream.str().c_str()); + stat.append("%"); + if (stat.at(0).isNumber()) stat = QString("+").append(stat); + + comp->setStatistic(stat); + } + +} + +/** + * @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 SportModel::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/SportModel.h b/src/model/SportModel.h new file mode 100644 index 0000000..5ad289c --- /dev/null +++ b/src/model/SportModel.h @@ -0,0 +1,88 @@ +#pragma once + +#include "MedalWinner.h" +#include "CompetitorWithResults.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include "EventInfo.h" + +using namespace std; + +class SportModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString discipline READ discipline WRITE setDiscipline NOTIFY disciplineChanged); + +public: + enum Role + { + EventName = Qt::UserRole + 1, + Competitors + }; + + explicit SportModel(QObject *parent = nullptr); + + void setDiscipline(QJsonObject discipline) + { + this->o_discipline = QJsonObject(discipline); + } + + virtual int rowCount(const QModelIndex &parent) const override; + virtual QVariant data(const QModelIndex &index, int role) const override; + virtual QHash roleNames() const override; + + set getCategories(); + QList getCompetitorsByCategory(QString category); // TODO ref instead of obj + QList getCompetitorsWithMedal(); // TODO ref instead of obj + + // filter to change the current competitor list + void lastName(QList &competitors); // TODO ref instead of obj + void filterByName(QList &competitors, QString name); // TODO ref instead of obj + void filterByCountry(QList &competitors, QString nocShort); // TODO ref instead of obj + + // sort functions to change the order of the current competitor list + void sortByName(QList &competitors); // TODO ref instead of obj + void sortByCountry(QList &competitors); // TODO ref instead of obj + void sortByResult(QList &competitors); // TODO ref instead of obj + void sortByMedals(QList &competitors); // TODO ref instead of obj + void reverseOrder(QList &competitors); // TODO ref instead of obj + + // statistic function + void addRelativeToFirst(QList &competitors); // TODO ref instead of obj + + QString discipline() const; + void setDiscipline(const QString &discipline); + +public slots: + void request(QString discipline); + void parseData(); + +signals: + void disciplineChanged(); + +private: + QList m_sportList; + QString m_discipline; + QNetworkAccessManager m_networkManager; + QNetworkReply *m_reply = nullptr; + + // data from api + QJsonObject o_discipline; + bool validateDiscipline(); + + void filterCompetitors(QList &competitors, QString filter); // TODO ref instead of obj + + QJsonObject createCompetitorWithMedals(QJsonObject medalComp); + + // function for statistic calculation + float calcRelativeStat(QString ref, QString val); + +}; -- cgit v1.2.3