Похоже, вы хотите читать данные CSV. Однако вам нужно игнорировать строку заголовка -
Я бы порекомендовал использовать "современный" подход C ++.
И все же все, кто говорит о csv, ссылаются на Как я могучитать и анализировать 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"(CS T 2pm 3 01 02 03
Math TH 10am 2 03 04
)" };
// 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, So, one liner
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 ++.
Этот пример, в принципе, не заботится о количестве строк и столбцов. Он съест все.
Пожалуйста, не забудьте прочитать первую строку в вашем реальном файле с помощью std::getline
и выбросить его.