diff options
| author | Steru <jerrydream111@gmail.com> | 2024-08-12 22:09:50 +0200 | 
|---|---|---|
| committer | Steru <jerrydream111@gmail.com> | 2024-08-12 22:09:50 +0200 | 
| commit | 94fb05a764c1d0d1f275f29ecd21686d835f9768 (patch) | |
| tree | 283d60ee309ca35132c3cd64b55d018cc2cbfc87 /src/model | |
| parent | c539242efa11538e5e3f91bf3ff2f0de1c526bed (diff) | |
Created Model for competitors.
Diffstat (limited to 'src/model')
| -rw-r--r-- | src/model/Competitor.cpp | 15 | ||||
| -rw-r--r-- | src/model/Competitor.h | 39 | ||||
| -rw-r--r-- | src/model/CompetitorWithResults.cpp | 15 | ||||
| -rw-r--r-- | src/model/CompetitorWithResults.h | 28 | ||||
| -rw-r--r-- | src/model/MedalWinner.cpp | 18 | ||||
| -rw-r--r-- | src/model/MedalWinner.h | 35 | ||||
| -rw-r--r-- | src/model/Sport.cpp | 423 | ||||
| -rw-r--r-- | src/model/Sport.h | 92 | 
8 files changed, 665 insertions, 0 deletions
| 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 <QString> +#include <QMap> +#include <QJsonObject> +#include <stdexcept> + +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 <QString> +#include <QMap> +#include <QJsonObject> +#include <stdexcept> + +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<QString, QString> 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 <QMap> +#include <QJsonObject> +#include <stdexcept> + +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<QString, QString> 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 <set> + +// sorting and filtering +#include <map> +#include <algorithm> +#include <regex> + +// float to string formatting +#include <iostream> +#include <iomanip> + +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValueRef> +#include <QString> + +// QJsonArray filter function, provide with input array and evaluation function +QJsonArray filter(QJsonArray input, function<bool (QJsonObject)> 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<bool (const QJsonValue &left, const QJsonValue &right)> 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<QString> Sport::getCategories() { +    set<QString> 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<QString, QJsonObject> 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<QString, QJsonObject> &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<bool (const QJsonValue &left, const QJsonValue &right)> 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 <set> + +#include <QJsonObject> +#include <QJsonDocument> +#include <QString> + +using namespace std; + + +class Sport { + +public: + +    Sport(QJsonObject discipline) { +        this->discipline = discipline; +    } + +    set<QString> 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<bool (const QJsonValue &left, const QJsonValue &right)> compare); + +    bool validateDiscipline(); +    QJsonObject createCompetitorWithMedals(QJsonObject medalComp); + +    float calcRelativeStat(QString ref, QString val); + +}; + + +#endif //ITAT_CHALLANGE_OLYMPICS_SPORT_H | 
