diff --git a/track_analyzing/track_analyzer/cmd_match.cpp b/track_analyzing/track_analyzer/cmd_match.cpp index 19abb8fe5b..5f0ede7264 100644 --- a/track_analyzing/track_analyzer/cmd_match.cpp +++ b/track_analyzing/track_analyzer/cmd_match.cpp @@ -98,7 +98,7 @@ void CmdMatch(string const & logFile, string const & trackFile, { MwmToTracks mwmToTracks; ParseTracks(logFile, numMwmIds, mwmToTracks); - AddStat(mwmToTracks, *numMwmIds, storage, stat); + stat.AddTracksStats(mwmToTracks, *numMwmIds, storage); MwmToMatchedTracks mwmToMatchedTracks; MatchTracks(mwmToTracks, storage, *numMwmIds, mwmToMatchedTracks); diff --git a/track_analyzing/track_analyzer/cmd_table.cpp b/track_analyzing/track_analyzer/cmd_table.cpp index 971e6aa118..0fcf235ab8 100644 --- a/track_analyzing/track_analyzer/cmd_table.cpp +++ b/track_analyzing/track_analyzer/cmd_table.cpp @@ -320,8 +320,7 @@ public: out << user << "," << countryName << "," << it.first.GetSummary() << "," << it.second.GetSummary() << '\n'; - stats.m_mwmToTotalDataPoints[mwmName] += it.second.GetDataPointsNumber(); - stats.m_countryToTotalDataPoints[countryName] += it.second.GetDataPointsNumber(); + stats.AddDataPoints(mwmName, countryName, it.second.GetDataPointsNumber()); } return out.str(); diff --git a/track_analyzing/track_analyzer/utils.cpp b/track_analyzing/track_analyzer/utils.cpp index 2e179a704d..35d2dc1b3f 100644 --- a/track_analyzing/track_analyzer/utils.cpp +++ b/track_analyzing/track_analyzer/utils.cpp @@ -9,7 +9,11 @@ #include "platform/platform.hpp" +#include "coding/csv_reader.hpp" + +#include "base/checked_cast.hpp" #include "base/logging.hpp" +#include "base/string_utils.hpp" #include #include @@ -52,19 +56,88 @@ void Add(Stats::NameToCountMapping const & addition, Stats::NameToCountMapping & } } -void AddStat(uint32_t dataPointNum, routing::NumMwmId numMwmId, - NumMwmIds const & numMwmIds, Storage const & storage, Stats & stats) +void PrintMap(string const & keyName, string const & descr, + Stats::NameToCountMapping const & mapping, ostringstream & ss) { - auto const mwmName = numMwmIds.GetFile(numMwmId).GetName(); - auto const countryName = storage.GetTopmostParentFor(mwmName); + ss << descr << '\n'; + if (mapping.empty()) + { + ss << "Map is empty." << endl; + return; + } - // Note. In case of disputed mwms |countryName| will be empty. - stats.m_mwmToTotalDataPoints[mwmName] += dataPointNum; - stats.m_countryToTotalDataPoints[countryName] += dataPointNum; + MappingToCsv(keyName, mapping, true /* printPercentage */, ss); +} +} // namespace + +namespace track_analyzing +{ +// Stats =========================================================================================== +Stats::Stats(NameToCountMapping const & mwmToTotalDataPoints, + NameToCountMapping const & countryToTotalDataPoint) + : m_mwmToTotalDataPoints(mwmToTotalDataPoints) + , m_countryToTotalDataPoints(countryToTotalDataPoint) +{ } -void PrintMap(string const & keyType, string const & descr, - map const & mapping, ostringstream & ss) +bool Stats::operator==(Stats const & stats) const +{ + return m_mwmToTotalDataPoints == stats.m_mwmToTotalDataPoints && + m_countryToTotalDataPoints == stats.m_countryToTotalDataPoints; +} + +void Stats::Add(Stats const & stats) +{ + ::Add(stats.m_mwmToTotalDataPoints, m_mwmToTotalDataPoints); + ::Add(stats.m_countryToTotalDataPoints, m_countryToTotalDataPoints); +} + +void Stats::AddTracksStats(MwmToTracks const & mwmToTracks, NumMwmIds const & numMwmIds, + Storage const & storage) +{ + for (auto const & kv : mwmToTracks) + { + auto const & userToTrack = kv.second; + uint32_t dataPointNum = 0; + for (auto const & userTrack : userToTrack) + dataPointNum += userTrack.second.size(); + + NumMwmId const numMwmId = kv.first; + auto const mwmName = numMwmIds.GetFile(numMwmId).GetName(); + auto const countryName = storage.GetTopmostParentFor(mwmName); + // Note. In case of disputed mwms |countryName| will be empty. + AddDataPoints(mwmName, countryName, dataPointNum); + } +} + +void Stats::AddDataPoints(string const & mwmName, string const & countryName, + uint32_t dataPointNum) +{ + m_mwmToTotalDataPoints[mwmName] += dataPointNum; + m_countryToTotalDataPoints[countryName] += dataPointNum; +} + +void Stats::SaveMwmDistributionToCsv(string const & csvPath) +{ + if (csvPath.empty()) + return; + + ostringstream ss(csvPath); + MappingToCsv("mwm", m_mwmToTotalDataPoints, false /* printPercentage */, ss); +} + +Stats::NameToCountMapping const & Stats::GetMwmToTotalDataPointsForTesting() const +{ + return m_mwmToTotalDataPoints; +} + +Stats::NameToCountMapping const & Stats::GetCountryToTotalDataPointsForTesting() const +{ + return m_countryToTotalDataPoints; +} + +void MappingToCsv(string const & keyName, Stats::NameToCountMapping const & mapping, + bool printPercentage, basic_ostream & ss) { struct KeyValue { @@ -72,12 +145,14 @@ void PrintMap(string const & keyType, string const & descr, uint32_t m_value = 0; }; - ss << descr << '\n'; if (mapping.empty()) - { - ss << "Map is empty." << endl; return; - } + + ss << keyName; + if (printPercentage) + ss << ",number,percent\n"; + else + ss << ",number\n"; // Sorting from bigger to smaller values. vector keyValues; @@ -86,38 +161,37 @@ void PrintMap(string const & keyType, string const & descr, keyValues.push_back({kv.first, kv.second}); sort(keyValues.begin(), keyValues.end(), - [](KeyValue const & a, KeyValue const & b) { return a.m_value > b.m_value; }); + [](KeyValue const & a, KeyValue const & b) { return a.m_value > b.m_value; }); uint32_t allValues = 0; for (auto const & kv : keyValues) allValues += kv.m_value; - ss << keyType << ",number,percent\n"; for (auto const & kv : keyValues) { if (kv.m_value == 0) continue; - ss << kv.m_key << "," << kv.m_value << "," - << 100.0 * static_cast(kv.m_value) / allValues << "\n"; + ss << kv.m_key << "," << kv.m_value; + if (printPercentage) + ss << "," << 100.0 * static_cast(kv.m_value) / allValues; + ss << '\n'; } - ss << "\n" << endl; -} -} // namespace - -namespace track_analyzing -{ -// Stats =========================================================================================== -void Stats::Add(Stats const & stats) -{ - ::Add(stats.m_mwmToTotalDataPoints, m_mwmToTotalDataPoints); - ::Add(stats.m_countryToTotalDataPoints, m_countryToTotalDataPoints); + ss.flush(); } -bool Stats::operator==(Stats const & stats) const +void MappingFromCsv(basic_istream & ss, Stats::NameToCountMapping & mapping) { - return m_mwmToTotalDataPoints == stats.m_mwmToTotalDataPoints && - m_countryToTotalDataPoints == stats.m_countryToTotalDataPoints; + for (auto const & row : + coding::CSVRunner(coding::CSVReader(ss, true /* hasHeader */, ',' /* kCsvDelimiter */))) + { + CHECK_EQUAL(row.size(), 2, (row)); + auto const & key = row[0]; + uint64_t value = 0; + CHECK(strings::to_uint64(row[1], value), ()); + auto const it = mapping.insert(make_pair(key, base::checked_cast(value))); + CHECK(it.second, ()); + } } void ParseTracks(string const & logFile, shared_ptr const & numMwmIds, @@ -134,27 +208,13 @@ void ParseTracks(string const & logFile, shared_ptr const & numMwmIds parser.Parse(logFile, mwmToTracks); } -void AddStat(MwmToTracks const & mwmToTracks, NumMwmIds const & numMwmIds, Storage const & storage, - Stats & stats) -{ - for (auto const & kv : mwmToTracks) - { - auto const & userToTrack = kv.second; - uint32_t dataPointNum = 0; - for (auto const & userTrack : userToTrack) - dataPointNum += userTrack.second.size(); - - ::AddStat(dataPointNum, kv.first, numMwmIds, storage, stats); - } -} - string DebugPrint(Stats const & s) { ostringstream ss; ss << "Stats [\n"; PrintMap("mwm", "Mwm to total data points number:", s.m_mwmToTotalDataPoints, ss); PrintMap("country", "Country name to data points number:", s.m_countryToTotalDataPoints, ss); - ss << "]\n" << endl; + ss << "]" << endl; return ss.str(); } diff --git a/track_analyzing/track_analyzer/utils.hpp b/track_analyzing/track_analyzer/utils.hpp index 517e139108..f32408243a 100644 --- a/track_analyzing/track_analyzer/utils.hpp +++ b/track_analyzing/track_analyzer/utils.hpp @@ -9,31 +9,56 @@ #include #include #include +#include #include -#include namespace track_analyzing { -struct Stats +class Stats { +public: + friend std::string DebugPrint(Stats const & s); + using NameToCountMapping = std::map; - void Add(Stats const & stats); - bool operator==(Stats const & stats) const; + Stats() = default; + Stats(NameToCountMapping const & mwmToTotalDataPoints, + NameToCountMapping const & countryToTotalDataPoint); + bool operator==(Stats const & stats) const; + void Add(Stats const & stats); + + /// \brief Adds some stats according to |mwmToTracks|. + void AddTracksStats(MwmToTracks const & mwmToTracks, routing::NumMwmIds const & numMwmIds, + storage::Storage const & storage); + + /// \brief Adds |dataPointNum| to |m_mwmToTotalDataPoints| and |m_countryToTotalDataPoints|. + void AddDataPoints(std::string const & mwmName, std::string const & countryName, + uint32_t dataPointNum); + + /// \brief Saves csv file with numbers of DataPoints for each mwm to |csvPath|. + /// If |csvPath| is empty it does nothing. + void SaveMwmDistributionToCsv(std::string const & csvPath); + + NameToCountMapping const & GetMwmToTotalDataPointsForTesting() const; + NameToCountMapping const & GetCountryToTotalDataPointsForTesting() const; + +private: /// \note These fields may present mapping from territory name to either DataPoints /// or MatchedTrackPoint count. NameToCountMapping m_mwmToTotalDataPoints; NameToCountMapping m_countryToTotalDataPoints; }; +/// \brief Saves |mapping| as csv to |ss|. +void MappingToCsv(std::string const & keyName, Stats::NameToCountMapping const & mapping, + bool printPercentage, std::basic_ostream & ss); +/// \breif Fills |mapping| according to csv in |ss|. Csv header is skipped. +void MappingFromCsv(std::basic_istream & ss, Stats::NameToCountMapping & mapping); + /// \brief Parses tracks from |logFile| and fills |mwmToTracks|. void ParseTracks(std::string const & logFile, std::shared_ptr const & numMwmIds, MwmToTracks & mwmToTracks); -/// \brief Fills |stat| according to |mwmToTracks|. -void AddStat(MwmToTracks const & mwmToTracks, routing::NumMwmIds const & numMwmIds, - storage::Storage const & storage, Stats & stats); - std::string DebugPrint(Stats const & s); } // namespace track_analyzing diff --git a/track_analyzing/track_analyzing_tests/statistics_tests.cpp b/track_analyzing/track_analyzing_tests/statistics_tests.cpp index 618e10d4f4..c5db6eac64 100644 --- a/track_analyzing/track_analyzing_tests/statistics_tests.cpp +++ b/track_analyzing/track_analyzing_tests/statistics_tests.cpp @@ -14,6 +14,7 @@ #include "geometry/latlon.hpp" #include +#include namespace { @@ -23,7 +24,33 @@ using namespace storage; using namespace track_analyzing; using namespace traffic; -UNIT_TEST(StatTest) +void TestSerializationToCsv(Stats::NameToCountMapping const & mapping) +{ + std::stringstream ss; + MappingToCsv("mwm", mapping, false /* printPercentage */, ss); + + Stats::NameToCountMapping readMapping; + MappingFromCsv(ss, readMapping); + + TEST_EQUAL(mapping, readMapping, (ss.str())); +} + +UNIT_TEST(AddDataPointsTest) +{ + Stats stats; + stats.AddDataPoints("mwm1", "country1", 3); + stats.AddDataPoints("mwm1", "country1", 1); + stats.AddDataPoints("mwm2", "country1", 5); + stats.AddDataPoints("mwm3", "country3", 7); + + Stats const expected = { + {{"mwm1", 4}, {"mwm2", 5}, {"mwm3", 7} /* Mwm to number */}, + {{"country1", 9}, {"country3", 7} /* Country to number */}}; + + TEST_EQUAL(stats, expected, ()); +} + +UNIT_TEST(AddStatTest) { Stats stats1 = { {{"Belarus_Minsk Region", 1}, {"Uzbekistan", 7}, {"Russia_Moscow", 5} /* Mwm to number */}, @@ -41,7 +68,7 @@ UNIT_TEST(StatTest) TEST_EQUAL(stats1, expected, ()); } -UNIT_TEST(AddStatTest) +UNIT_TEST(AddTracksStatsTest) { DataPoint const dp1(1 /* timestamp */, ms::LatLon(), static_cast(SpeedGroup::G5)); DataPoint const dp2(2 /* timestamp */, ms::LatLon(), static_cast(SpeedGroup::G5)); @@ -62,13 +89,67 @@ UNIT_TEST(AddStatTest) auto numMwmIds = CreateNumMwmIds(storage); MwmToTracks const mwmToTracks = {{numMwmIds->GetId(CountryFile(kMwmName)), userToTrack}}; - Stats stat; - AddStat(mwmToTracks, *numMwmIds, storage, stat); + Stats stats; + stats.AddTracksStats(mwmToTracks, *numMwmIds, storage); Stats::NameToCountMapping const expectedMwmToTotalDataMapping = {{kMwmName, kDataPointNumber}}; - TEST_EQUAL(stat.m_mwmToTotalDataPoints, expectedMwmToTotalDataMapping, ()); + TEST_EQUAL(stats.GetMwmToTotalDataPointsForTesting(), expectedMwmToTotalDataMapping, ()); Stats::NameToCountMapping expectedCountryToTotalDataMapping = {{"Italy", kDataPointNumber}}; - TEST_EQUAL(stat.m_countryToTotalDataPoints, expectedCountryToTotalDataMapping, ()); + TEST_EQUAL(stats.GetCountryToTotalDataPointsForTesting(), expectedCountryToTotalDataMapping, ()); +} + +UNIT_TEST(MappingToCsvTest) +{ + Stats::NameToCountMapping const mapping = { + {{"Belarus_Minsk Region", 2}, {"Uzbekistan", 5}, {"Russia_Moscow", 3}}}; + { + std::ostringstream ss; + MappingToCsv("mwm", mapping, true /* printPercentage */, ss); + std::string const expected = R"(mwm,number,percent +Uzbekistan,5,50 +Russia_Moscow,3,30 +Belarus_Minsk Region,2,20 +)"; + TEST_EQUAL(ss.str(), expected, ()); + } + { + std::ostringstream ss; + MappingToCsv("mwm", mapping, false /* printPercentage */, ss); + std::string const expected = R"(mwm,number +Uzbekistan,5 +Russia_Moscow,3 +Belarus_Minsk Region,2 +)"; + TEST_EQUAL(ss.str(), expected, ()); + } +} + +UNIT_TEST(SerializationToCsvTest) +{ + Stats::NameToCountMapping const mapping1 = {{"Belarus_Minsk Region", 2}, + {"Uzbekistan", 5}, + {"Russia_Moscow", 1}, + {"Russia_Moscow Oblast_East", 2}}; + TestSerializationToCsv(mapping1); + + Stats::NameToCountMapping const mapping2 = { + {{"Belarus_Minsk Region", 2}, {"Uzbekistan", 5}, {"Russia_Moscow", 3}}}; + TestSerializationToCsv(mapping2); +} + +UNIT_TEST(SerializationToCsvWithZeroValueTest) +{ + Stats::NameToCountMapping const mapping = {{"Russia_Moscow Oblast_East", 2}, + {"Poland_Lesser Poland Voivodeship", 0}}; + Stats::NameToCountMapping const expected = {{"Russia_Moscow Oblast_East", 2}}; + + std::stringstream ss; + MappingToCsv("mwm", mapping, false /* printPercentage */, ss); + + Stats::NameToCountMapping readMapping; + MappingFromCsv(ss, readMapping); + + TEST_EQUAL(readMapping, expected, (ss.str())); } } // namespace