Преобразование файлов в C ++: разделитель каналов разделяется запятыми - PullRequest
0 голосов
/ 09 апреля 2020

Я пытаюсь выяснить, как превратить этот входной файл, который находится в форме разделителя канала, в разделитель запятой. Я должен открыть файл, прочитать его в массив, преобразовать его в запятую в выходном 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,         

1 Ответ

0 голосов
/ 09 апреля 2020

Я покажу вам полное решение и объясню вам. Но давайте сначала посмотрим на это:

#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, разбиение их на токены, преобразование и запись
  • addQuotes. При необходимости добавьте кавычку
  • printData печать преобразованных данных в выходной поток

Начнем с main. main сначала откроет входной файл и выходной файл.

Входной файл содержит вид структурированных данных и также называется csv (значения, разделенные запятыми). Но здесь у нас не запятая, а символ канала в качестве разделителя.

И результат обычно сохраняется в 2d-векторе. В измерении 1 строки и другое измерение для столбцов.

Итак, что нам нужно делать дальше? Как мы видим, нам нужно сначала прочитать все полные текстовые строки из исходного потока. Это легко сделать с помощью одной строки:

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

Как вы можете видеть здесь , оператор for имеет часть объявления / инициализации, затем условие, а затем оператор , проведенный в конце л oop. Это хорошо известно.

Сначала мы определяем переменную "line" типа std::string и используем инициализатор по умолчанию для создания пустой строки. Затем мы используем std::getline, чтобы прочитать из потока полную строку и поместить ее в нашу переменную. std::getline возвращает ссылку на поток, и поток имеет перегруженный оператор bool, куда он возвращается, если произошел сбой (или конец файла). Таким образом, для l oop не требуется дополнительная проверка конца файла. И мы не используем последний оператор для l oop, потому что при чтении строки указатель файла продвигается автоматически.

Это дает нам очень простое значение для l oop, для чтения полный файл строка за строкой.

Обратите внимание: при определении переменной "строка" в for для l oop, будет применена область для l oop. Смысл, это видно только в течение l oop. Как правило, это хорошее решение для предотвращения загрязнения внешнего пространства имен.

ОК, теперь следующая строка:

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

Э-э, что это?

ОК, давайте go пошагово. Во-первых, мы явно хотим добавить что-то к нашему 2-мерному вектору данных. Мы будем использовать функцию std::vector s emplace_back . Мы могли бы также использовать push_back , но это означало бы, что нам нужно сделать ненужное копирование данных. Следовательно, мы выбрали emplace_back для создания на месте того, что мы хотим добавить к нашему двухмерному вектору данных.

И что мы хотим добавить? Мы хотим добавить полную строку, поэтому вектор столбцов. В нашем случае это std::vector<std::string>. И, поскольку мы хотим сделать на месте конструирование этого вектора, мы называем его конструктором диапазона векторов. Пожалуйста, смотрите здесь: Конструктор № 5 . Конструктор диапазона принимает в качестве параметра 2 итератора, начальный и конечный итератор, и копирует все значения, на которые указывают итераторы, в вектор.

Итак, мы ожидаем начальный и конечный итератор. И что мы видим здесь:

  • Начальный итератор: std::sregex_token_iterator(line.begin(), line.end(), digit)
  • А конечный итератор просто {}

Но что это вещь, sregex_token_iterator ?

Это итератор, который перебирает шаблоны в строке. И шаблон дается регулярное выражение . Вы можете прочитать здесь о библиотеке C ++ regex. Поскольку он очень мощный, вам, к сожалению, нужно узнать о нем немного дольше. И я не могу покрыть это здесь. Но давайте опишем его базовую функциональность c для нашей цели: вы можете описать шаблон на каком-то мета-языке, и std::sregex_token_iterator будет искать этот шаблон и, если найдет совпадение, вернуть соответствующие данные. В нашем случае картина очень проста: цифры. Это можно описать с помощью "\ d +" и, значит, попытаться сопоставить одну или несколько цифр.

Теперь с {} в качестве конечного итератора. Возможно, вы читали, что {} будет выполнять построение / инициализацию по умолчанию. И если вы прочитаете здесь, номер 1 , то увидите, что «конструктор по умолчанию» создает итератор конца последовательности. Итак, именно то, что нам нужно.


После того, как мы прочитаем все данные, мы преобразуем отдельные строки в требуемый вывод. Это будет сделано с помощью std::transform и функции addQuotes. Стратегия здесь заключается в том, чтобы сначала заменить одинарные кавычки на двойные.

И затем, смотрим, если в строке есть запятая или кавычка, мы дополнительно заключаем всю строку в кавычки.

И, наконец, что не менее важно, мы имеем простая функция вывода и печать преобразованных данных в файл и на экран.

...