Как я могу сохранить элементы файла .csv в двумерном векторе C ++? - PullRequest
0 голосов
/ 17 октября 2019

Я пытаюсь сохранить содержимое файла .csv в двухмерном векторе.

Я могу сохранить файл .csv в одномерном векторе, который я использую в своем классе в "читать "функцию для подсчета размера строки и столбца. Ниже приведен текущий код, который у меня есть, где я получаю сообщение об ошибке:

getline(ss,column.at(i),',');

Ошибка говорит о том, что векторный индекс находится вне диапазона, если я использую индексы в скобках, и вне диапазона в ячейке памяти при использовании ___. в().

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

class database {
public:
int row = 0;
int col = 0;
vector <vector<string>> dataset;
void read(string filename) {
    vector<string> data;
    ifstream file{ filename };
    string line;
    while (file)
    {
        getline(file, line);
        string newline;
        newline = line + "\n";
        data.push_back(newline);
    }
    col = count(data.at(0).begin(), data.at(0).end(), ',') + 1;
    row = data.size() - 1;
}
void write(string filename){
    ifstream file{ filename };
    string line;
    while (getline(file, line))
    {
        stringstream ss(line);
        vector<string> column;
        for (int i = 0; i < col; i++)
        {
            getline(ss, column.at(i), ',');
        }
        cout << column.size();
        dataset.push_back(column);
    }       
}
};

Я пытаюсь использовать cout << column.size (), чтобы увидеть, соответствует ли он размеру моего вектора данных, который является вектором с одним измерением, содержащим все содержимое файла .csv. </p>

1 Ответ

0 голосов
/ 27 октября 2019

Я бы использовал «более современный» подход C ++.

И все же все люди ссылаются на Как я могу читать и анализировать файлы CSV в C ++ ?, вопросы с 2009 года, а теперь старше 10 лет. ,Большинство ответов также старые и очень сложные. Так что, может быть, пришло время для изменений.

В современном C ++ у вас есть алгоритмы, которые выполняют итерации по диапазонам. Вы часто будете видеть что-то вроде «someAlgoritm (container.begin (), container.end (), someLambda)». Идея состоит в том, что мы перебираем некоторые похожие элементы.

В вашем случае мы перебираем токены во входной строке и создаем подстроки. Это называется токенизацией.

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

Это итератор. Для перебора строки, следовательно, sregex. Начальная часть определяет, с каким диапазоном ввода мы будем работать, затем есть std::regex для того, что должно совпадать / или что не должно совпадать во входной строке. Тип стратегии сопоставления указывается с последним параметром.

  • 1 -> дайте мне то, что я определил в регулярном выражении, и
  • -1 -> дайте мне то, что НЕ соответствует на основе регулярного выражения.

Итак, теперь, когда мы понимаем итератор, мы можем std :: скопировать токены из итератора в нашу цель, std::vector из std::string. И поскольку мы не знаем, как могут столбцы у нас, мы будем использовать std::back_inserter в качестве цели. Это добавит все токены, которые мы получаем от std::sregex_token_iterator, и добавим его к нашему std::vector<std::string>>. Неважно, сколько у нас столбцов.

Хорошо. Такое утверждение может выглядеть как

std::copy(                          // We want to copy something
    std::sregex_token_iterator      // The iterator begin, the sregex_token_iterator. Give back first token
    (
        line.begin(),               // Evaluate the input string from the beginning
        line.end(),                 // to the end
        re,                         // Add match a comma
        -1                          // But give me back not the comma but everything else 
    ),
    std::sregex_token_iterator(),   // iterator end for sregex_token_iterator, last token + 1
    std::back_inserter(cp.columns)  // Append everything to the target container
);

Теперь мы можем понять, как работает эта операция копирования.

Следующий шаг. Мы хотим читать из файла. Файл содержит также некоторые данные. Эти же данные являются строками.

И, как и выше, мы можем повторять аналогичные данные. Если это файл ввода или что-то еще. Для этого в C ++ есть std::istream_iterator. Это шаблон, и в качестве параметра шаблона он получает тип данных, которые он должен прочитать, а в качестве параметра конструктора он получает ссылку на входной поток. Не имеет значения, является ли входной поток std::cin, std::ifstream или std::istringstream. Поведение идентично для всех видов потоков.

И поскольку у нас нет файлов SO, я использую (в следующем примере) std::istringstream для хранения входного файла CSV. Но, конечно, вы можете открыть файл, определив std::ifstream testCsv(filename). Нет проблем.

И с std::istream_iterator мы перебираем ввод и читаем похожие данные. В нашем случае одна проблема состоит в том, что мы хотим перебирать специальные данные, а не какие-то встроенные типы данных.

Чтобы решить эту проблему, мы определяем класс Proxy, который выполняет внутреннюю работу за нас (мы не делаемхотите знать, как, что должно быть включено в прокси). В прокси мы перезаписываем оператор приведения типа, чтобы получить результат для ожидаемого нами типа для std::istream_iterator.

И последний важный шаг. A std::vector имеет конструктор диапазона. Он также имеет много других конструкторов, которые мы можем использовать в определении переменной типа std::vector. Но для наших целей этот конструктор подходит лучше всего.

Таким образом, мы определяем переменную csv, используем ее конструктор range и даем ей начало диапазона и конец диапазона. И, в нашем конкретном примере, мы используем начальный и конечный итератор std::istream_iterator.

Если мы объединим все вышеперечисленное, чтение полного CSV-файла будет однострочным , этоэто определение переменной с вызовом ее конструктора.

Пожалуйста, посмотрите получившийся код:

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

std::istringstream testCsv{ R"(0, 6/19/2019, 16:41:33, 33.972622, -117.323482, 24.25, 23.5, 23.25, 24.75, 25.5, 24.25, 25.25, 25.5, 24.5, 24, 24, 24.25, 25.5, 25.75, 25.25, 25, 24.5, 24.75, 24.75, 24.75, 25.25, 24.5, 24.5, 25.5, 23.75, 24.25, 24.75, 24, 24.25, 24, 24.5, 25, 24.25, 24, 24.25, 24.25, 24, 24.25, 24.5, 25.5, 24, 25, 24.5, 24.75, 24.5, 24.75, 24.75, 25.5, 24.5, 24.25, 24.25, 25.25, 25.25, 23.5, 25, 24.75, 24.5, 24.75, 25.5, 24.25, 23.5, 24, 25.25, 25, 605, 597, 515, 514, 509, 511, 508
0, 6/19/ 2019, 16:41:42, 33.972648, -117.323492, 24, 23.5, 23.75, 24.25, 25.5, 25.5, 25.25, 25.25, 25, 24.5, 24.25, 24.5, 25, 25.5, 25.5, 25.75, 24.25, 23.5, 24.75, 24.5, 24.25, 24.25, 24.5, 25.5, 24, 23.75, 24.5, 24, 24.25, 24, 24.75, 25.25, 25, 23.75, 24.75, 25.5, 25.5, 26, 24.75, 25.25, 24.5, 25, 25.25, 25.25, 26, 24.75, 24.5, 25.5, 24.5, 24.5, 25, 24.75, 24.25, 24.25, 25, 25, 24, 24, 24.75, 25, 23.25, 24.25, 25.5, 25.5, 609, 595, 1229, 1227, 1200, 1196, 1171
0, 6/19/2019, 16:41:49, 33.972643, -117.323479, 24.5, 23, 22.75, 24, 25.25, 25.5, 25, 26, 24.75, 24, 24, 24.75, 24.75, 25.25, 25.5, 26, 24.75, 24, 24.75, 25, 24.25, 24.25, 24.75, 26, 24.5, 23.5, 24.5, 24, 24, 24, 25, 25.75, 24.75, 23.25, 24.5, 24.5, 24.5, 25, 25.25, 25.25, 24, 25, 24.5, 25.25, 25.25, 25.25, 25.25, 25.5, 24.5, 24, 25.25, 25, 25, 24.25, 25, 25.25, 24.25, 24, 24.75, 25.25, 23.75, 24.25, 25, 25.5, 621, 601, 706, 725, 703, 707, 704
1, 6/19/2019, 16:41:55, 33.972631, -117.323483, 24.25, 23.75, 23.25, 24, 25.25, 25.25, 25.5, 26, 24.5, 24.25, 23.75, 24.5, 24.75, 25.5, 26, 25.5, 25, 23.75, 24.75, 24.75, 25.25, 25.25, 25, 26.25, 24.5, 23.5, 24.25, 25, 24.25, 24.25, 24.75, 25.75, 24.75, 23.75, 24.25, 24.25, 24.25, 24.5, 25.25, 25.25, 24.5, 24.5, 24.75, 25, 25.25, 26, 25.5, 25.25, 24.5, 24, 24.75, 25, 25, 25.25, 25.5, 25.5, 24.25, 25, 25, 25.75, 24.25, 24.5, 25.25, 25.5, 613, 602, 721, 720, 699, 704, 696
)" };


// Define Alias for Easier Reading
using Columns = std::vector<std::string>;
using CSV = std::vector<Columns>;


// Proxy for the input Iterator
struct ColumnProxy {    
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {

        // Read a line
        std::string line; cp.columns.clear();
        std::getline(is, line);

        // The delimiter
        const std::regex re(",");

        // Split values and copy into resulting vector
        std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
            std::sregex_token_iterator(),
            std::back_inserter(cp.columns));
        return is;
    }

    // Type cast operator overload.  Cast the type 'Columns' to std::vector<std::string>
    operator std::vector<std::string>() const { return columns; }
protected:
    // Temporary to hold the read vector
    Columns columns{};
};


int main()
{
    // Define variable CSV with its range constructor. Read complete CSV in this statement
    CSV csv{ std::istream_iterator<ColumnProxy>(testCsv), std::istream_iterator<ColumnProxy>() };

    // Print result. Go through all lines and then copy line elements to std::cout
    std::for_each(csv.begin(), csv.end(), [](Columns& c) {
        std::copy(c.begin(), c.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << "\n";   });
}

Я надеюсь, что объяснение было достаточно подробным, чтобы дать вам представление, что вы можете сделать ссовременный C ++

...