如何在没有具体的情况下将 CSV 数据存储在某种数据结构中:C++ 中的列数、列类型等
Posted
技术标签:
【中文标题】如何在没有具体的情况下将 CSV 数据存储在某种数据结构中:C++ 中的列数、列类型等【英文标题】:How would you store CSV data in some sort of data structure without a concrete : number of columns, column types, etc in C++ 【发布时间】:2020-02-03 17:58:13 【问题描述】:好吧,基本上我需要从 CSV 文件中读取数据并将其存储在某种数据结构中。 CSV 数据看起来像这样:
year,position,MVP,entity
INT,STRING,BOOL,STRING
2020,FORWARD,TRUE,Lionel Messi
2020,MIDFIELDER,FALSE,Jordan Henderson
2020,GOALKEEPER,FALSE,David De Gea
2020,DEFENDER,FALSE,Virgil van Dijk
前两行将告诉您属性的名称及其类型。
我知道如何从 CSV 文件中读取数据,但问题是我真的不知道在列数、属性类型(bool、int 等)时存储所述数据的最佳数据结构是什么,可能会有所不同。
最初我认为由 Row 对象的向量表示的表可以工作,但只有当我确切知道有多少属性、它们的类型、它们的名称等时才有效。
我想我可以根据数据的元数据以某种方式存储它,比如属性的数量、属性类型、行的位置等,但我真的不知道如何扩展这个想法。
任何帮助将不胜感激!
编辑:
所以基本上我的程序必须使用类似于我上面发布的结构的 CSV 文件,但每个 CSV 文件可能有不同的列数、不同的属性类型等。
一个 csv 文件可能看起来像上面的示例,另一个可能看起来像这样:
startYear,job,entity
INT,STRING,STRING
2001,SALES ASSOCIATE,Jackie Cruz
1992,GENERAL MANAGER,Jorge Almandra
2004,CUSTODIAN,Jeffrey Howie
2018,ELECTRICIAN,Katie Moody
即使列数及其类型不同,我仍然需要能够将数据存储到某种数据结构中。
【问题讨论】:
如果列不固定,您将如何对数据做任何有意义的事情? 我希望单个文件中的每条记录(行)都相同?您是否在一个文件中混合了不同类型的记录?然后,您仍然可以为每种记录类型设置目标结构(这确实是我推荐的)。 任何具有文本表示的类型都可以存储在std::string
中,然后取决于您要如何处理这些值..
可以是std::vector<std::vector<std::string>>
,其中std::vector<std::string>
代表CSV的一行。
您必须使用 std::variant
之类的东西,其中包含所有可能的类型。然后解析第一行以获取列名,第二行 - 获取列类型,然后您只需逐行读取并填充值向量(使用类型验证)
【参考方案1】:
这是一种可能的解决方案
我讨厌它。我永远不会做那样的事情。因为设计理念或要求已经是无稽之谈了。
或者,我们使用类型并且我们知道哪一列有什么类型,或者我们只是为所需的上下文使用适合所有类型的类型。在这种情况下,只需一个 std::string
。
但动态执行此操作会导致代码非常丑陋且不可维护。
这里的解决方案是 std::any。但也许类层次结构会更好。我稍后再试试。
请看这段丑陋的代码:
#include <iostream>
#include <sstream>
#include <vector>
#include <regex>
#include <string>
#include <iterator>
#include <algorithm>
#include <utility>
#include <any>
#include <map>
#include <tuple>
// the delimiter for the csv
const std::regex re(",");
// One DataRow from the csv file
struct DataRow
std::vector<std::string> columns;
friend std::istream& operator >> (std::istream& is, DataRow& dr)
// Read one complete line
if (std::string line; std::getline(is, line))
// Split the string, containing the complete line into parts
dr.columns.clear();
std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1), , std::back_inserter(dr.columns));
return is;
;
struct CSV
protected:
// Conversion functions
std::any stringToAnySTRING(const std::string& s) return s;
std::any stringToAnyBOOL(const std::string& s) bool result false ; if (s == "TRUE") result = true; return result;
std::any stringToAnyINT(const std::string& s) int result = std::stoi(s); return result;
std::any stringToAnyLONG(const std::string& s) long result = std::stol(s); return result;
// Making Reading easier
using ConvertToAny = std::any(CSV::*)(const std::string&);
// Map conversion functions to type strings
std::map<std::string, ConvertToAny> converter
"STRING", &CSV::stringToAnySTRING,
"BOOL", &CSV::stringToAnyBOOL,
"INT", &CSV::stringToAnyINT,
"LONG", &CSV::stringToAnyLONG
;
public:
// Header, Types and data as std::any
std::vector<std::string> header;
std::vector<std::string> types;
std::vector<std::vector<std::any>> data;
// Extractor operator
friend std::istream& operator >> (std::istream& is, CSV& c)
// Read header line
if (std::string line; std::getline(is, line))
// Split header line into sub strings
c.header.clear();
std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1), , std::back_inserter(c.header));
// Read types line
if (std::getline(is, line))
// Spit types into sub strings
c.types.clear();
std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1), , std::back_inserter(c.types));
// Read all data, so all lines, split them and convert them to the desired data type
c.data.clear();
// This will read all lines and split them into columns
std::vector<DataRow> drs(std::istream_iterator<DataRow>(is), );
// Make at least one plausibility check, that all rows have the same number of columns
size_t minDataLength = std::min_element(drs.begin(), drs.end(), [](const DataRow& dr1, const DataRow& dr2)
return dr1.columns.size() < dr2.columns.size(); )->columns.size();
if (c.header.size() == c.types.size() && c.types.size() == minDataLength)
// Now convert all columns into the type denoted by the read type array and store them as any data
// Double transform because of 2 dimensional array
std::transform(drs.begin(), drs.end(), std::back_inserter(c.data), [&c](const DataRow& dr)
std::vector<std::any> va;
// This is the conversion into a type defined by the types array
// Anybody who understands this transfrom will get the Nobel price for Obfuscation
std::transform(dr.columns.begin(), dr.columns.end(), std::back_inserter(va),
[&c, i = 0U](const std::string& s) mutable return (c.*(c.converter[c.types[i++]]))(s); );
return va; );
return is;
// Inserter operator
friend std::ostream& operator << (std::ostream& os, const CSV& c)
// Write header
os << "Header: ";
std::copy(c.header.begin(), c.header.end(), std::ostream_iterator<std::string>(os, " "));
// And the type names
os << "\nTypes: ";
std::copy(c.types.begin(), c.types.end(), std::ostream_iterator<std::string>(os, " "));
os << "\n\nData:\n";
// And the types. Arrgh. How ugly
std::for_each(c.data.begin(), c.data.end(), [&c,&os](const std::vector<std::any>& va)
for (size_t i = 0U; i < va.size(); ++i)
if (c.types[i] == "INT") int v = std::any_cast<int>(va[i]); os << v << " ";
else if (c.types[i] == "LONG") long v = std::any_cast<long>(va[i]); os << v << " ";
else if (c.types[i] == "STRING") std::string v = std::any_cast<std::string>(va[i]); os << v << " ";
else if (c.types[i] == "BOOL") bool v = std::any_cast<bool>(va[i]); os << v << " ";
os << "\n";
);
return os;
;
// The data. Does not matter if file or stringstream. Is the same
std::istringstream csvFile R"(year,category,winner,entity
INT,STRING,BOOL,STRING
2015,CHEF OF THE YEAR,FALSE,John Doe
2015,CHEF OF THE YEAR,FALSE,Bob Brown
2015,CHEF OF THE YEAR,TRUE,William Thorton
2015,CHEF OF THE YEAR,FALSE,Jacob Smith)" ;
int main()
// Define varaiable of type csv
CSV csv;
// Read from somewhere
csvFile >> csv;
// Show some debug output
std::cout << csv;
return 0;
【讨论】:
以上是关于如何在没有具体的情况下将 CSV 数据存储在某种数据结构中:C++ 中的列数、列类型等的主要内容,如果未能解决你的问题,请参考以下文章
如何在没有“for xml”子句的情况下将 sql 结果列作为 csv 获取?
在没有 CSV 的情况下将 CSV 附加到 Python 中的电子邮件
Python 3:如何在不保存在磁盘上的情况下将 pandas 数据帧作为 csv 流上传?
在这种情况下将 javascript 变量数据传递给 MySQL 数据库