【问题标题】:C++ file conversion: pipe delimited to comma delimitedC++ 文件转换:管道分隔到逗号分隔
【发布时间】:2020-07-21 19:40:19
【问题描述】:

我试图弄清楚如何将这个以管道分隔形式的输入文件转换为逗号分隔。我必须打开文件,将其读入数组,将其转换为以逗号分隔的输出 CSV 文件,然后关闭所有文件。有人告诉我,最简单的方法是在 excel 中,但我不太确定如何。

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
    ifstream inFile;
    string myArray[5];

    cout << "Enter the input filename:";
    cin >> inFileName;

    inFile.open(inFileName);
    if(inFile.is_open())
    std::cout<<"File Opened"<<std::endl;

    // read file line by line into array
    cout<<"Read";

    for(int i = 0; i < 5; ++i)
    {
       file >> myArray[i];
    }

    // File conversion 

    // close input file
    inFile.close();

    // close output file
    outFile.close();
...

我需要转换的是:

Miles per hour|6,445|being the "second" team |5.54|9.98|6,555.00    
"Ending" game| left at "beginning"|Elizabeth, New Jersey|25.25|6.78|987.01   
|End at night, or during the day|"Let's go"|65,978.21|0.00|123.45    
Left-base night|10/07/1900|||4.07|777.23       
"Let's start it"|Start Baseball Game|Starting the new game to win  

逗号分隔形式的输出应该是什么样子:

Miles per hour,"6,445","being the ""second"" team member",5.54,9.98,"6,555.00",    
"""Ending"" game","left at ""beginning""","Denver, Colorado",25.25,6.78,987.01,      
,"End at night, during the day","""Let's go""","65,978.21",0.00,123.45,       
Left-base night, 10/07/1900,,,4.07,777.23,               
"""Let's start it""", Start Baseball Game, Starting the new game to win,         

【问题讨论】:

  • 只要没有带引号的分隔符,它就是一个简单的替换。逐个字符读取文件并将其写入另一个文件。将所有出现的| 替换为,。但是,如果有引号分隔符,它会变得有点复杂。
  • 为什么要在myArray中存储5个字符串?
  • 是的,有引号分隔符,这就是我如此困惑的原因。从输入到输出,我什至不得不为其中一些添加更多引号。 @Thomas Sablik
  • 如果 excel 是一个选项,请使用它。我不知道 CSV 中的引用规则。但是你必须了解他们才能编写代码。
  • 问题格式。

标签: c++ xcode file-conversion


【解决方案1】:

我将向您展示一个完整的解决方案并向您解释。但让我们先来看看它:

#include <iostream>
#include <vector>
#include <fstream>
#include <regex>
#include <string>
#include <algorithm>

// I omit in the example here the manual input of the filenames. This exercise can be done by somebody else
// Use fixed filenames in this example.
const std::string inputFileName("r:\\input.txt");
const std::string outputFileName("r:\\output.txt");

// The delimiter for the source csv file
std::regex re{ R"(\|)" };

std::string addQuotes(const std::string& s) {
    // if there are single quotes in the string, then replace them with double quotes
    std::string result = std::regex_replace(s, std::regex(R"(")"), R"("")");

    // If there is any quote (") or comma in the file, then quote the complete string
    if (std::any_of(result.begin(), result.end(), [](const char c) { return ((c == '\"') || (c == ',')); })) {
        result = "\"" + result + "\"";
    }
    return result;
}


// Some output function
void printData(std::vector<std::vector<std::string>>& v, std::ostream& os) {
    // Go throug all rows
    std::for_each(v.begin(), v.end(), [&os](const std::vector<std::string>& vs) {
        // Define delimiter
        std::string delimiter{ "" };
        // Show the delimited strings
        for (const std::string& s : vs) {
            os << delimiter << s;
            delimiter = ",";
        }
        os << "\n";
    });
}

int main() {


    // We first open the ouput file, becuse, if this cannot be opened, then no meaning to do the rest of the exercise
    // Open output file and check, if it could be opened
    if (std::ofstream outputFileStream(outputFileName); outputFileStream) {

        // Open the input file and check, if it could be opened
        if (std::ifstream inputFileStream(inputFileName); inputFileStream) {

            // In this variable we will store all lines from the CSV file including the splitted up columns
            std::vector<std::vector<std::string>> data{};

            // Now read all lines of the CSV file and split it into tokens
            for (std::string line{}; std::getline(inputFileStream, line); ) {

                // Split line into tokens and add to our resulting data vector
                data.emplace_back(std::vector<std::string>(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {}));
            }
            std::for_each(data.begin(), data.end(), [](std::vector<std::string>& vs) {
                std::transform(vs.begin(), vs.end(), vs.begin(), addQuotes);
            });

            // Output, to file
            printData(data, outputFileStream);

            // And to the screen
            printData(data, std::cout);
        }
        else {
            std::cerr << "\n*** Error: could not open input file '" << inputFileName << "'\n";
        }

    }
    else {
        std::cerr << "\n*** Error: could not open output file '" << outputFileName << "'\n";
    }
    return 0;
}

那么,让我们来看看。我们有功能

  • main,读取csv文件,拆分成token,转换,写入
  • addQuotes。必要时添加报价
  • printData打印他将数据转换成输出流

让我们从main 开始。 main会先打开输入文件和输出文件。

输入文件包含一种结构化数据,也称为 csv(逗号分隔值)。但是这里我们没有逗号,而是一个管道符号作为分隔符。

结果通常会存储在二维向量中。第一个维度是行,另一个维度是列。

那么,接下来我们需要做什么?正如我们所见,我们首先需要从源流中读取所有完整的文本行。这可以通过单行轻松完成:

for (std::string line{}; std::getline(inputFileStream, line); ) {

如您所见here,for 语句有一个声明/初始化部分,然后是一个条件,然后是一个语句,在循环结束时执行。这是众所周知的。

我们首先定义一个std::string 类型的变量“line”,并使用默认初始化器创建一个空字符串。然后我们使用std::getline 从流中读取一个完整的行并将其放入我们的变量中。 std::getline 返回对 sthe 流的引用,并且流具有重载的 bool 运算符,如果出现故障(或文件结尾),它将返回该处。因此,for 循环不需要额外检查文件结尾。而且我们不使用for循环的最后一条语句,因为通过读取一行,文件指针会自动前进。

这给了我们一个非常简单的for循环,逐行读取一个完整的文件。

请注意:在 for 循环中定义变量“line”,会将其范围限定为 for 循环。意思是,它只在 for 循环中可见。这通常是防止外部名称空间污染的良好解决方案。

好的,现在是下一行:

data.emplace_back(std::vector<std::string>(std::sregex_token_iterator(line.begin(), line.end(), digit), {}));

哦哦,那是什么?

好的,让我们一步一步来。首先,我们显然想在我们的二维数据向量中添加一些东西。我们将使用std::vectors 函数emplace_back。我们也可以使用push_back,但这意味着我们需要进行不必要的数据复制。因此,我们选择了emplace_back 来就地构建我们想要添加到二维数据向量中的东西。

我们要添加什么?我们想要添加一个完整的行,因此是一个列向量。在我们的例子中是std::vector&lt;std::string&gt;。而且,因为我们想在原地构造这个向量,所以我们用向量范围构造函数来调用它。请看这里:Constructor number 5。范围构造函数接受两个迭代器,一个开始迭代器和一个结束迭代器作为参数,并将迭代器指向的所有值复制到向量中。

所以,我们期望一个开始和结束迭代器。我们在这里看到了什么:

  • 开始迭代器是:std::sregex_token_iterator(line.begin(), line.end(), digit)
  • 而结束迭代器就是{}

但这是什么东西,sregex_token_iterator

这是一个迭代一行中的模式的迭代器。模式由regex 给出。您可以阅读 here 关于 C++ 正则表达式库的信息。由于它非常强大,不幸的是,您需要了解它的时间更长一些。我不能在这里覆盖它。但是让我们为我们的目的描述它的基本功能:你可以用某种元语言描述一个模式,并且 std::sregex_token_iterator 将查找该模式,如果找到匹配项,则返回相关数据。在我们的例子中,模式非常简单:数字。这可以用“\d+”来描述,意思是,尝试匹配一个或多个数字。

现在将{} 作为结束迭代器。您可能已经读到{} 将执行默认构造/初始化。如果您阅读here, number 1,那么您会看到“默认构造函数”构造了一个序列结束迭代器。所以,正是我们需要的。


读取所有数据后,我们会将单个字符串转换为所需的输出。这将通过std::transform 和函数addQuotes 完成。 这里的策略是先用双引号替换单引号。

然后,接下来,我们看看,如果字符串中有任何逗号或引号,那么我们将整个字符串附加在引号中。

最后但同样重要的是,我们有一个简单的输出函数,可以将转换后的数据打印到文件中并显示在屏幕上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-05
    • 1970-01-01
    • 2015-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多