C++ 如果没有具体的数据结构,您将如何在某种数据结构中存储CSV数据:C+中的列数、列类型等+;

C++ 如果没有具体的数据结构,您将如何在某种数据结构中存储CSV数据:C+中的列数、列类型等+;,c++,csv,C++,Csv,好的,基本上我需要从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 startYear,jo

好的,基本上我需要从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
startYear,job,entity
INT,STRING,STRING
2001,SALES ASSOCIATE,Jackie Cruz
1992,GENERAL MANAGER,Jorge Almandra
2004,CUSTODIAN,Jeffrey Howie 
2018,ELECTRICIAN,Katie Moody
前两行将告诉您属性的名称及其类型

我知道如何从CSV文件中读取数据,但问题是,当列数、属性类型(bool、int等)可能不同时,我真的不知道存储所述数据的最佳数据结构是什么

起初,我认为一个由行对象向量表示的表是可行的,但只有当我确切地知道有多少属性、它们的类型是什么、它们的名称是什么等时,这才有效

我想我可以基于数据的元数据来存储它,比如属性、属性类型、行位置等等,但我还不知道如何扩展这个想法

任何帮助都将不胜感激

编辑:

因此,基本上我的程序必须使用类似于我上面发布的结构的CSV文件,但是每个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
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::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的分隔符
常量std::正则表达式re(“,”);
//csv文件中的一个数据行
结构数据行{
std::向量列{};
friend std::istream&operator>>(std::istream&is、DataRow&dr){
//读一整行
if(std::string line{};std::getline(is,line)){
//将包含完整行的字符串拆分为多个部分
dr.columns.clear();
std::copy(std::sregex_令牌_迭代器(line.begin(),line.end(),re,-1),{},std::back_插入器(dr.columns));
}
回报是;
}
};
结构CSV{
受保护的:
//转换函数
std::anystringtoanystring(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);返回结果;}
std::any stringToAnyLONG(const std::string&s){long result=std::stol(s);返回结果;}
//使阅读更容易
使用ConvertToAny=std::any(CSV::*)(const std::string&);
//将转换函数映射到类型字符串
映射转换器{
{“STRING”,&CSV::stringToAnySTRING},
{“BOOL”,&CSV::stringToAnyBOOL},
{“INT”,&CSV::stringToAnyINT},
{“LONG”,&CSV::stringToAnyLONG}
};
公众:
//标题、类型和数据为std::any
std::向量头{};
std::向量类型{};
std::矢量数据{};
//提取器操作员
friend std::istream&operator>>(std::istream&is、CSV&c){
//读取标题行
if(std::string line{};std::getline(is,line)){
//将标题行拆分为子字符串
c、 header.clear();
std::copy(std::sregex_令牌_迭代器(line.begin(),line.end(),re,-1),{},std::back_插入器(c.header));
//读取类型行
if(std::getline(is,line)){
//将类型吐入子字符串
c、 类型。清除();
std::copy(std::sregex_令牌_迭代器(line.begin(),line.end(),re,-1),{},std::back_插入器(c.types));
//读取所有数据,因此读取所有行,拆分它们并将它们转换为所需的数据类型
c、 data.clear();
//这将读取所有行并将它们拆分为列
std::vector drs(std::istream_迭代器(is),{});
//至少进行一次合理性检查,确保所有行的列数相同
size\t minDataLength=std::min\u元素(drs.begin()、drs.end()、[](常量数据行和dr1、常量数据行和dr2)
{返回dr1.columns.size()columns.size();
if(c.header.size()==c.types.size()&&c.types.size()==MindAtalLength){
//现在,将所有列转换为读取类型数组表示的类型,并将它们存储为任何数据
//二维数组的双重变换
std::transform(drs.begin()、drs.end()、std::back_插入器(c.data)、[&c](const-DataRow&dr){
std::向量va{};
//这是到由types数组定义的类型的转换
//任何理解这一转变的人都将因混淆而获得诺贝尔奖
std::transform(dr.columns.begin()、dr.columns.end()、std::back_插入器(va),
[&c,i=0U](const std::string&s)可变{return(c.*(c.converter[c.types[i++]])(s);});
返回va;});
}
}
}
回报是;
}
//插入器操作员

friend std::ostream&operator如果列不固定,您将如何对数据进行有意义的处理?我希望每个记录(行)都是一个s