#include "Sport.h" #include #include #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); }