C ++: чтение неопределенного числа переменных в текстовом файле и изменение структуры, а затем замена строки в текстовом файле. - PullRequest
0 голосов
/ 28 октября 2019

Наш инструктор просит нас прочитать текстовый файл и сохранить его в другой структуре и массиве, а затем разрешить пользователю изменять. Но когда я использовал fstream >> name >> day >>...>> roster и получал текст построчно. Это всегда ошибки, возможно, потому что размер каждой строки различен.

Заголовок класса

Class Course
private: 
   string name;
   int num_of_student;
   string *roster;

Struct class_time
   string day;
   string time;

test.txt (первую строку можно игнорировать)

<name><day><time><num_of_student><roster(student ids separate by space)>
CS T 2pm 3 01 02 03
Math TH 10am 2 03 04

Я понятия не имею, как читать этот тип текстового файла исохранить их в другой массив или локальные переменные.

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

num_courses(int){
// function get num of lines in text file
}

void load_Data(){
fstream read;
string name1, day1, time1;
int enroll;
int num_c; // number of courses
read.open("test.txt",ios::in);
if(!read.is_open()){
    cout << "No file exist." << endl;
}
num_courses(num_c);
Course course[num_c];
class_time sch[num_c];
while(!read.eof()) {
    for (int i = 0; i < num_c; i++) {
        read >> name1 >> day1 >> time1 >> enroll;
        course[i].name = name1;
        sch[i].day = day1;
        sch[i].time = time1;
        course[i].num_of_student = enroll;
        string ros[enroll];
        for (int j = 0; j < enroll; j++) {
            read >> ros[j];
            course[i].roster[j] = ros[j];
        }
    }
}

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

1 Ответ

1 голос
/ 28 октября 2019

Похоже, вы хотите читать данные 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 и выбросить его.

...