如何在没有具体的情况下将 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 数据库

如何在不先定义表中的列的情况下将数据加载到 PostgreSQL 中?

如何在没有 Redux 的情况下将状态保存在本地存储 React 中?