kopia lustrzana https://gitlab.com/tomaszg/geostat
365 wiersze
12 KiB
C++
365 wiersze
12 KiB
C++
#include "common.h"
|
|
#include "debug.h"
|
|
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
#include <vector>
|
|
#include <iterator>
|
|
|
|
void htmlencode(std::string& data) {
|
|
std::string tmp;
|
|
tmp.reserve(data.size() + 20);
|
|
|
|
for (size_t i = 0; i != data.size(); i++) {
|
|
switch (data[i]) {
|
|
case '&':
|
|
tmp += "&";
|
|
break;
|
|
case '\"':
|
|
tmp += """;
|
|
break;
|
|
case '\'':
|
|
tmp += "'";
|
|
break;
|
|
case '<':
|
|
tmp += "<";
|
|
break;
|
|
case '>':
|
|
tmp += ">";
|
|
break;
|
|
default:
|
|
tmp += data[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
data.swap(tmp);
|
|
}
|
|
|
|
static std::string string_mangle(const std::string& str) {
|
|
std::string tmp;
|
|
tmp.reserve(str.size());
|
|
|
|
for (size_t i = 0; i != str.size(); i++) {
|
|
if (str[i] == ' ') continue;
|
|
if (std::isupper(str[i]))
|
|
tmp.push_back(std::tolower(str[i]));
|
|
else
|
|
tmp.push_back(str[i]);
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
void show_histogram(const pCaches& cc, std::string Cache::*ptr, const std::string& caption, bool html, bool sort_by_val) {
|
|
if (cc.empty()) return;
|
|
|
|
std::map<std::string, int> histogram;
|
|
|
|
for (auto el : cc)
|
|
histogram[el->*ptr]++;
|
|
|
|
show_histogram(histogram, caption, html, sort_by_val);
|
|
}
|
|
|
|
void show_histogram(const Caches& cc, std::string Cache::*ptr, const std::string& caption, bool html, bool sort_by_val) {
|
|
if (cc.empty()) return;
|
|
|
|
std::map<std::string, int> histogram;
|
|
|
|
for (auto el : cc)
|
|
histogram[el.*ptr]++;
|
|
|
|
show_histogram(histogram, caption, html, sort_by_val);
|
|
}
|
|
|
|
void show_histogram(const pPowertrails& tt, const std::string& caption, bool html, bool sort_by_val) {
|
|
if (tt.empty()) return;
|
|
|
|
std::map<std::string, int> histogram;
|
|
|
|
for (auto el : tt) {
|
|
histogram[el.second->name] = 100 * el.second->found / el.second->caches.size();
|
|
}
|
|
|
|
show_histogram(histogram, caption, html, sort_by_val);
|
|
}
|
|
|
|
void show_histogram(const std::map<std::string, int>& data, const std::string& caption, bool html, bool sort_by_val) {
|
|
if (data.empty()) return;
|
|
|
|
int HIST_MAX = 20;
|
|
|
|
std::vector<std::pair<std::string, int>> pairs;
|
|
|
|
if (!sort_by_val) HIST_MAX = 1000;
|
|
|
|
std::copy(data.begin(), data.end(), std::back_inserter(pairs));
|
|
|
|
if (sort_by_val)
|
|
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
|
|
else
|
|
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });
|
|
|
|
if (html) {
|
|
int max;
|
|
if (sort_by_val)
|
|
max = pairs[0].second;
|
|
else
|
|
max = std::max_element(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second < b.second; })->second;
|
|
int i = 0;
|
|
std::cout << "<h2>" << caption << "</h2>\n";
|
|
std::cout << "<div class=\"histogram " << string_mangle(caption) << "\">\n";
|
|
for (auto own : pairs) {
|
|
htmlencode(own.first);
|
|
if (own.first.empty()) own.first = "[unknown]";
|
|
|
|
if (i < HIST_MAX)
|
|
std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
|
|
else if (i == HIST_MAX) {
|
|
std::cout << "</div>\n";
|
|
std::cout << "<details class=\"histogram_others " << string_mangle(caption) << "\">\n<summary>See more</summary>\n<p>\n";
|
|
std::cout << own.first << " (" << own.second << ")<br>";
|
|
}
|
|
if (i > HIST_MAX)
|
|
std::cout << own.first << " (" << own.second << ")<br>";
|
|
i++;
|
|
}
|
|
if (i <= HIST_MAX)
|
|
std::cout << "</div>\n";
|
|
else
|
|
std::cout << "</p></details>\n";
|
|
} else {
|
|
std::cout << caption << '\n';
|
|
for (auto own : pairs)
|
|
std::cout << own.first << ": " << own.second << '\n';
|
|
}
|
|
}
|
|
|
|
void show_nested_histogram(const pCaches& fcc, std::string Cache::*ptr, std::string Cache::*ptr2, const std::string& caption, bool html, bool sort_by_val) {
|
|
if (fcc.empty()) return;
|
|
|
|
std::map<std::string, int> histogram;
|
|
std::vector<std::pair<std::string, int>> pairs;
|
|
|
|
for (auto el : fcc)
|
|
histogram[el->*ptr]++;
|
|
std::copy(histogram.begin(), histogram.end(), std::back_inserter(pairs));
|
|
|
|
if (sort_by_val)
|
|
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
|
|
else
|
|
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });
|
|
|
|
if (html) {
|
|
int HIST_MAX = 20;
|
|
int max;
|
|
|
|
if (sort_by_val)
|
|
max = pairs[0].second;
|
|
else
|
|
max = std::max_element(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second < b.second; })->second;
|
|
int i = 0;
|
|
std::cout << "<h2>" << caption << "</h2>\n";
|
|
std::cout << "<div class=\"histogram nested " << string_mangle(caption) << "\">\n";
|
|
for (auto own : pairs) {
|
|
htmlencode(own.first);
|
|
if (own.first.empty()) own.first = "[unknown]";
|
|
if (i < HIST_MAX) {
|
|
std::map<std::string, int> subhistogram;
|
|
std::vector<std::pair<std::string, int>> subpairs;
|
|
for (auto el : fcc)
|
|
if (el->*ptr == own.first && !(el->*ptr2).empty()) //TODO problem with [unknown]
|
|
subhistogram[el->*ptr2]++;
|
|
if (!subhistogram.empty()) {
|
|
std::cout << "<details class=\"nested\"><summary class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></summary>\n";
|
|
std::copy(subhistogram.begin(), subhistogram.end(), std::back_inserter(subpairs));
|
|
if (sort_by_val)
|
|
sort(subpairs.begin(), subpairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
|
|
else
|
|
sort(subpairs.begin(), subpairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });
|
|
|
|
std::cout << "<div class=\"histogram inside\">\n";
|
|
for (auto own : subpairs) {
|
|
htmlencode(own.first);
|
|
if (own.first.empty()) own.first = "[unknown]";
|
|
std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
|
|
}
|
|
std::cout << "</div>\n";
|
|
std::cout << "</details>\n";
|
|
} else {
|
|
std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
|
|
}
|
|
} else if (i == HIST_MAX) {
|
|
std::cout << "</div>\n";
|
|
std::cout << "<details class=\"histogram_others " << string_mangle(caption) << "\">\n<summary>See more</summary>\n<p>\n";
|
|
std::cout << own.first << " (" << own.second << ")";
|
|
}
|
|
if (i > HIST_MAX)
|
|
std::cout << ", " << own.first << " (" << own.second << ")";
|
|
i++;
|
|
}
|
|
if (i <= HIST_MAX)
|
|
std::cout << "</div>\n";
|
|
else
|
|
std::cout << "</p></details>\n";
|
|
} else { //TODO text-only version not implemented, displays only first-level list
|
|
std::cout << caption << '\n';
|
|
for (auto own : pairs)
|
|
std::cout << own.first << ": " << own.second << '\n';
|
|
}
|
|
}
|
|
|
|
uint find_streak(const Date_Caches& cc, std::time_t& start) {
|
|
int max_str = 0;
|
|
int cur_str = 0;
|
|
|
|
int cur = 0;
|
|
int cur_start = cur;
|
|
int max_start = cur;
|
|
|
|
for (auto i : cc) {
|
|
if (cur == i.first / 86400)
|
|
continue;
|
|
if (i.first / 86400 - cur == 1) {
|
|
cur_str++;
|
|
cur = i.first / 86400;
|
|
continue;
|
|
}
|
|
|
|
// streak ended
|
|
|
|
if (max_str < cur_str) {
|
|
max_str = cur_str;
|
|
max_start = cur_start;
|
|
}
|
|
cur_str = 1;
|
|
cur = cur_start = i.first / 86400;
|
|
if (max_str == 0) {
|
|
max_str = 1;
|
|
max_start = cur;
|
|
}
|
|
}
|
|
|
|
if (max_str < cur_str) {
|
|
max_str = cur_str;
|
|
max_start = cur_start;
|
|
}
|
|
|
|
start = max_start * 86400; // convert back from month count to time_t
|
|
return max_str;
|
|
}
|
|
|
|
uint get_num(char c, char* opt) {
|
|
try {
|
|
if (std::stoi(opt) > 0) {
|
|
return std::stoi(opt);
|
|
} else {
|
|
std::cout << "Option \"-" << c << "\" requires a valid number as an argument\n";
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
catch (...) {
|
|
std::cout << "Option \"-" << c << "\" requires a valid number as an argument\n";
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
void average_html(const Caches& cc, float Cache::*ptr, const std::string& caption) {
|
|
std::cout << "<div class=\"basic_stats\">\n";
|
|
std::cout << "Average " << caption << ": <span class=\"value\">" << average(cc, ptr) << "</span><br>\n";
|
|
std::cout << "</div>\n";
|
|
}
|
|
|
|
void average_html(const Caches& cc, int Cache::*ptr, const std::string& caption) {
|
|
std::cout << "<div class=\"basic_stats\">\n";
|
|
std::cout << "Average " << caption << ": <span class=\"value\">" << average(cc, ptr) << "</span><br>\n";
|
|
std::cout << "</div>\n";
|
|
}
|
|
|
|
void average_html(const Caches& cc, uint Cache::*ptr, const std::string& caption) {
|
|
std::cout << "<div class=\"basic_stats\">\n";
|
|
std::cout << "Average " << caption << ": <span class=\"value\">" << average(cc, ptr) << "</span><br>\n";
|
|
std::cout << "</div>\n";
|
|
}
|
|
|
|
float average(const Caches& cc, float Cache::*ptr) {
|
|
return std::accumulate(cc.begin(), cc.end(), 0., [&](const float& a, const Cache& b) { return std::move(a) + b.*ptr; }) / cc.size();
|
|
}
|
|
|
|
float average(const Caches& cc, int Cache::*ptr) {
|
|
return 1.0 * std::accumulate(cc.begin(), cc.end(), 0, [&](const float& a, const Cache& b) { return std::move(a) + b.*ptr; }) / cc.size();
|
|
}
|
|
|
|
float average(const Caches& cc, uint Cache::*ptr) {
|
|
return 1.0 * std::accumulate(cc.begin(), cc.end(), 0, [&](const float& a, const Cache& b) { return std::move(a) + b.*ptr; }) / cc.size();
|
|
}
|
|
|
|
void sum_html(const Caches& cc, float Cache::*ptr, const std::string& caption) {
|
|
std::cout << "<div class=\"basic_stats\">\n";
|
|
std::cout << "Total " << caption << ": <span class=\"value\">" << sum(cc, ptr) << "</span><br>\n";
|
|
std::cout << "</div>\n";
|
|
}
|
|
|
|
void sum_html(const Caches& cc, int Cache::*ptr, const std::string& caption) {
|
|
std::cout << "<div class=\"basic_stats\">\n";
|
|
std::cout << "Total " << caption << ": <span class=\"value\">" << sum(cc, ptr) << "</span><br>\n";
|
|
std::cout << "</div>\n";
|
|
}
|
|
|
|
void sum_html(const Caches& cc, uint Cache::*ptr, const std::string& caption) {
|
|
std::cout << "<div class=\"basic_stats\">\n";
|
|
std::cout << "Total " << caption << ": <span class=\"value\">" << sum(cc, ptr) << "</span><br>\n";
|
|
std::cout << "</div>\n";
|
|
}
|
|
|
|
float sum(const Caches& cc, float Cache::*ptr) {
|
|
return std::accumulate(cc.begin(), cc.end(), 0., [&](const float& a, const Cache& b) { return std::move(a) + b.*ptr; });
|
|
}
|
|
|
|
int sum(const Caches& cc, int Cache::*ptr) {
|
|
return std::accumulate(cc.begin(), cc.end(), 0, [&](const int& a, const Cache& b) { return std::move(a) + b.*ptr; });
|
|
}
|
|
|
|
int sum(const Caches& cc, uint Cache::*ptr) {
|
|
return std::accumulate(cc.begin(), cc.end(), 0, [&](const int& a, const Cache& b) { return std::move(a) + b.*ptr; });
|
|
}
|
|
|
|
int count_caches(const Caches& cc, std::string& codes, std::function<bool(const Cache& c)> test) {
|
|
int count = 0;
|
|
codes.clear();
|
|
for (auto& c : cc) {
|
|
if (test(c)) {
|
|
count++;
|
|
codes += c.code;
|
|
codes += ' ';
|
|
}
|
|
}
|
|
codes.pop_back();
|
|
return count;
|
|
}
|
|
|
|
void header_html() {
|
|
std::cout << "<!DOCTYPE html>\n";
|
|
std::cout << "<html lang=\"en\">\n";
|
|
std::cout << " <head>\n";
|
|
std::cout << " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n";
|
|
std::cout << " <link rel=\"stylesheet\" type=\"text/css\" href=\"geo.css?ver=13\">\n";
|
|
std::cout << " <title>Geocaching stats</title>\n";
|
|
std::cout << "</head>\n";
|
|
std::cout << "<body>\n";
|
|
}
|
|
|
|
void footer_html() {
|
|
std::cout << "<footer>\n";
|
|
std::cout << "Stats generated by <a href=\"https://gitlab.com/tomaszg/geostat\">GeoStat</a>.<br>\n";
|
|
std::cout << "Data obtained from Opencaching sites via <a href=\"https://github.com/opencaching/okapi/\">OKAPI</a>:<br>\n";
|
|
std::cout << "<a href=\"https://www.opencaching.pl/\">Opencaching.pl</a>, ";
|
|
std::cout << "<a href=\"https://www.opencaching.de/\">Opencaching.de</a>, ";
|
|
std::cout << "<a href=\"http://www.opencaching.us/\">Opencaching.us</a>, ";
|
|
std::cout << "<a href=\"http://www.opencaching.nl/\">Opencaching.nl</a>, ";
|
|
std::cout << "<a href=\"http://www.opencaching.ro/\">Opencaching.ro</a>, ";
|
|
std::cout << "<a href=\"https://www.opencache.uk/\">Opencaching.uk</a>.";
|
|
std::cout << "</footer>\n";
|
|
std::cout << "</body>\n</html>\n";
|
|
}
|