如何在cpp中使用地图显示给定日期范围内(在下面的问题中)的费率?

How to display rate within a given date range(in the question below) using map in cpp?

本文关键字:在下面 范围内 问题 日期 费率 cpp 显示 地图      更新时间:2023-10-16

我的问题是:

Province/State,Country/Region,Lat,Long,1/22/20,1/23/20,1/24/20,1/25/20,1/26/20
,Afghanistan,33.0,65.0,0,0,0,0 
,Albania,41.1533,20.1683,0,0,0,0,0
,Algeria,28.0339,1.6596,0,0,0,0,0 
,Andorra,42.5063,1.5218,0,0,0,0,0 
,Angola,11.2027,17.8739,0,0,0,0,0 
New South Wales,Australia,33.8688,151.2093,10,0,20,0,0  
Northern Territory,Australia,-12.4634,130.8456,5,2,3,0,0  

以上是一个csv文件。我必须从用户那里读取国家/地区、开始日期和结束日期并打印该范围内的费率值?另外,我必须计算该国每天的平均恢复率并打印出来。如何做到这一点? 如果用户输入的开始日期是 01/22/2020,结束日期是 1/24/2020,国家/地区是澳大利亚, 预期结果应为:

Date           Rate
01/22/2020      7.5(10+5/2)
01/23/2020      1((0+2)/2)
01/24/2020      11.5((20+3)/2)

因此,它再次归结为将CSV文件解析为令牌。

首先也是最重要的,对于常规CSV文件,您永远不需要第三方库。Everthing是建立在易于使用的内置。

将字符串拆分为标记是一项非常古老的任务。有许多可用的解决方案。它们都有不同的属性。有些难以理解,有些难以开发,有些更复杂,更慢或更快或更灵活或不灵活。

选择

  1. 手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
  2. 使用旧式std::strtok功能。也许不安全。也许不应该再用了
  3. std::getline.最常用的实现。但实际上是一种"误用",并没有那么灵活
  4. 使用专用的现代功能,专门为此目的而开发,最灵活,最适合STL环境和算法环境。但更慢。

请在一段代码中查看 4 个示例。

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };

int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << 'n'; };
// Example 1:   Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2:   Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3:   Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4:   Most flexible iterator solution  ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };

Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);

// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
<小时 />

下一页。您的特定任务。通过将大任务拆分为较小的任务并选择正确的数据结构来解决这些任务。

然后,这些小部件可以很容易地实现并用于下一个更大的任务。

因此,我们将定义许多简单的函数来解决问题。

对于输入,我们将始终覆盖提取器运算符,以便我们可以将其与各种输入流一起使用。

如果我们查看 csv 文件,那么我们会看到我们有某种标题行和许多记录行。总之,我们有一个数据集。

这一观察将导致定义 3 种基本数据结构:

  1. 页眉
  2. 记录
  3. 数据

对于数据集,我们添加一个评估函数,该函数将在屏幕上打印结果。

我们将为所有类定义一个提取器运算符。

标头的提取器将读取一行,溢出到令牌中,检查数据是否在预期中,将时间字符串转换为std::tm然后存储所有值。最后,空行将被连接。

这是一行,完全直截了当。

记录的提取器是类似的简单,相同的机制。读取一行,将其拆分为令牌,进行健全性检查并分配所有值。

数据集的提取器将调用其他 2 个提取器。一个用于标头,然后在循环中用于所有记录。所有读取的数据都将存储在内部数据变量中。根据 OP 的要求,我们使用std::map进行分组,但实际上不是必需的。

在 evealuation 函数中,我们首先在标题行中查找给定开始和结束日期的日期。我们对国家名称和给定日期进行消毒检查。然后,我们遍历给定的国家/地区组,并将给定日期和 ountry 的所有值相加。

结果平均值将显示在显示屏上。

附加的帮助程序函数将从用户读取指定格式的日期。也相当坚定地向前走。

主要我们实现一些驱动程序代码。

首先,我们阅读完整的 csv 文件,该文件归结为一个语句。

通过随后覆盖外加运算符的应用,我们提出了一个不错的单行代码:csvFile >> dataset;

我们得到开始和结束日期,以及国家。我们调用评估函数并显示结果。

通过将最初的大问题分解为小问题,我们得出了一个非常简单的解决方案。

请看:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <regex>
#include <iterator>
#include <algorithm>
#include <iomanip>
#include <ctime>
#include <map>
// This is the source file. It does not matter, whether it is a fstream or an istringstream. It will always work
std::istringstream csvFile{ R"(Province/State,Country/Region,Lat,Long,1/22/20,1/23/20,1/24/20,1/25/20,1/26/20
,Afghanistan,33.0,65.0,0,0,0,0 
,Albania,41.1533,20.1683,0,0,0,0,0
,Algeria,28.0339,1.6596,0,0,0,0,0 
,Andorra,42.5063,1.5218,0,0,0,0,0 
,Angola,11.2027,17.8739,0,0,0,0,0 
New South Wales,Australia,33.8688,151.2093,10,0,20,0,0  
Northern Territory,Australia,-12.4634,130.8456,5,2,3,0,0  )" };
// The delimiter in the CSV file
const std::regex re{ "," };
//-------------------------------------------------------------------------------------------------
// Data structures
// Header Line. We will only store the dates
struct Header {
std::vector<std::tm> dates{};
};
// A data record, consiting of location information and rates
struct Record {
std::string provinceState{};
std::string countryRegion{};
std::string latitude{};
std::string longitude{};
std::vector<double> rate{};
};
// A DataSet are a header and many Records
struct Dataset {
// Heade line/data
Header header;  
// Reocrds, grouped by country
std::map<std::string, std::vector<Record>> data;
// This will generate the output for one dataset
void showAverageRatePerCountryAndPeriod(const std::string& country, const std::tm& startDate, const std::tm& endDate);
};
//-------------------------------------------------------------------------------------------------
// IO operations
// Overwriting the Extractor operator for each data struct
// Extractor operator for a Header line
std::istream& operator >> (std::istream& is, Header& h) {
// Read a line from a stream, and check, if that worked
std::string line{};
if (std::getline(is, line)) {
// Split string into tokens
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
// Sanity Check. Check for valid information in the header
if ((token.size()) > 4 && (token[0] == "Province/State") && (token[1] == "Country/Region") && (token[2] == "Lat") && (token[3] == "Long")) {

// Read all dates
std::transform(token.begin() + 4, token.end(), std::back_inserter(h.dates), [](const std::string& s) {
std::istringstream iss(s);  std::tm t{}; iss >> std::get_time(&t, "%m/%d/%y");  return t; });
}
}
// Read the empty line after the header
return std::getline(is, line);
}
// Extractor operator for one Record line
std::istream& operator >> (std::istream& is, Record& r) {
// Read a line from a stream and check, if that worked
if (std::string line{}; std::getline(is, line)) {
// Split line into tokens
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
// Sanity Check. There must be enough data available
if (token.size() > 4) {
// Clear old data from previous invocation
r.rate.clear();
// Assign the string data
r.provinceState = token[0]; r.countryRegion = token[1]; r.latitude = token[2]; r.longitude = token[3];
// Copy the rate values
std::transform(token.begin() + 4, token.end(), std::back_inserter(r.rate), [](const std::string& s) {
return std::stod(s); });
}
}
return is;
}
// Extractor Operator for a complete data set
std::istream& operator >> (std::istream& is, Dataset& d) {
// Frist read the header
if (is >> d.header) {
// Clear aold data from previous reads
d.data.clear();
// Now read all Records. Line by line
for (std::string line{}; std::getline(is, line) and not line.empty(); ) {
// Put the line in a stringstream and the extract the dates from there
std::istringstream iss{ line };
if (Record r{}; iss >> r) 
d.data[r.countryRegion].push_back(std::move(r));  // Store new record
}
}
return is;
}
// This is a helper function for showing the expected results on the screen
void Dataset::showAverageRatePerCountryAndPeriod(const std::string& country, const std::tm& startDate, const std::tm& endDate) {
// Do we have the given country name in ourt data set
if (data.find(country) != data.end()) {
// A simle Lambda for comparing 2 std::tm
auto tmCompare = [](std::tm l, std::tm r) { return std::mktime(&l) < std::mktime(&r); };
// Get the index of the start end end time in the vector fo dates
int indexStart = std::distance(header.dates.begin(), std::lower_bound(header.dates.begin(), header.dates.end(), startDate, tmCompare));
int indexEnd =   std::distance(header.dates.begin(), std::upper_bound(header.dates.begin(), header.dates.end(), endDate, tmCompare));
// Sanity check for valid date information
if (indexEnd - indexStart <= 0) {
std::cerr << "n***Error: Wrong Datesn";
}
else {
// Show output to user
std::cout << "nDatettRaten";
// Iterate through all rates for that date and country
for (int indexDate = indexStart; indexDate < indexEnd; ++indexDate) {
// Temporary for calculating the sum for one day for this country
double sumForDay{};
// For all records for this country
for (const Record& r : data[country]) {
// Sum up, if data is available
if (indexDate < r.rate.size()) sumForDay += r.rate[indexDate];
}
// Show date and average on display
std::cout << std::put_time(&header.dates[indexDate], "%m/%d/%Y") << 't' << sumForDay / data[country].size() << 'n';
}
}
}
else {
std::cerr << "n***Error: Country not foundn";
}
}
// Helper function for reading a valid date
std::tm getDate(std::string&& userInstruction) {
// Here we will store the result
std::tm t{};
// We will try to read data until we got something value from the user
bool inputIsValid{ false };
while (not inputIsValid) {
// Give instructions
std::cout << 'n' << userInstruction << "nPlease use the format  mm/dd/yy:t";
// Read a complete line
if (std::string line{}; std::getline(std::cin, line)) {
// Try to convert the string to a date
std::istringstream iss{ line };
if (iss >> std::get_time(&t, "%m/%d/%y")) inputIsValid = true;
}
}
return t;
}
// Driver code
int main() {
// Read a complete dataset from any stream
Dataset dataset{};
csvFile >> dataset;
// Get the start and end date 
std::tm startDate{ getDate("Please enter a start date.") };
std::tm endDate  { getDate("Please enter an end date.") };
// Read country from user
std::cout << "nEnter country: ";
if (std::string country{}; std::cin >> country) {
std::cout << 'n';
// Evaluate and show result
dataset.showAverageRatePerCountryAndPeriod(country, startDate, endDate);
}
return 0;
}