Вырезание и реорганизация чисел в файле с помощью C ++ - PullRequest
0 голосов
/ 08 мая 2020

У меня есть следующий код

#include<iostream>
#include<cmath>
#include<string>
using namespace std;
#include<fstream>
#include<iomanip>
#include<cstdio>



int main() {

//Here the code that creates the file benchmarks_cleaned.dat

int ncol = 16; //number of data per row
double datos[ncol];
char aux2;
double aux3;
int icol;

ifstream benchmarks2;
ofstream out_benchmarks2;

benchmarks2.open("benchmarks_cleaned.dat");
out_benchmarks2.open("benchmarks_final.dat");

if (benchmarks2.is_open()) {//second if

  for (icol = 0; icol < ncol; icol++) {
    benchmarks2>>datos[icol];
    out_benchmarks2<<datos[icol]<<" ";
  };

  out_benchmarks2<<"\n";
  benchmarks2.get(aux2);

  while (aux2 != 'X') {
    benchmarks2.unget();
    benchmarks2>>aux3;

    if (aux3 != datos[0]) {
      benchmarks2.get(aux2);
    } else {
      out_benchmarks2<<datos[0]<<" ";
      for (icol = 1; icol < ncol; icol++) {
        benchmarks2>>datos[icol];
        out_benchmarks2<<datos[icol]<<" ";
      };

      out_benchmarks2<<"\n";
      benchmarks2.get(aux2);

    };

  };

} else {
  cout<<"ERROR: unable to open the file"<<endl;
};//end of second if

out_benchmarks2<<"X";

out_benchmarks2.close();

out_benchmarks2.close();
benchmarks2.close();


return 0;
}; //end of main

Файл данных dummyValues.dat:

{5.12, 0.1, 0.25}   {{0.10, 4, 3, 2, 1, 1.44, 10.2}}       {11.1, 12.2, 13.3, 14.4, 15.5, 16.6} 1000 2000 {{{{ 5.12, 0.1} {17.7, 18.08, 19.0, 020.0}   {1.115, 2.0, 3.01, 4.65, 5, 6, 7, 8, 9, 10.0}, 3000 4000    { 5.12, 0.1} {117.7, 118.08, 119.0, 0120.0}   {11.115, 21.0, 31.01, 41.65, 51, 61, 71, 81, 91, 110.0} 5000 6000       X

В benchmarks_cleaned.dat вы сокращаете этот файл до чисел, разделенных пробелом . Теперь идея состоит в том, чтобы написать benchmarks_final.dat, где в каждой строке у вас есть только 16 значений, и они должны начинаться с одного и того же числа 5.12 = datos[0], которое вы можете проверить, повторяется в dummyValues.dat

Тем не менее, пока benchmarks_cleaned. dat действительно создается по желанию (см. ниже), benchmarks_final.dat никогда не запускается. Я проверил, что программа работает, но в benchmarks_final.dat ничего не записывает. Где ошибка?

benchmarks_cleaned.dat:

5.12 0.1 0.25 0.1 4 3 2 1 1.44 10.2 11.1 12.2 13.3 14.4 15.5 16.6 1000 2000 5.12 0.1 17.7 18.08 19 20 1.115 2 3.01 4.65 5 6 7 8 9 10 3000 4000 5.12 0.1 117.7 118.08 119 120 11.115 21 31.01 41.65 51 61 71 81 91 110 5000 6000 X

Ответы [ 2 ]

1 голос
/ 08 мая 2020

Ваши звонки на get(...) захватывают пробелы и пропускают 'X'.

Нет необходимости в get. Избавьтесь от aux2 и измените while l oop на:

while(benchmarks2 >> aux3)
0 голосов
/ 08 мая 2020

Итак, ответ дан. Это хорошо, правильно. Он одобрен и принят. Очень хорошо.

В качестве дополнительной информации я покажу решение C ++, использующее более современные языковые элементы. Это решение пропускает промежуточный файл (может быть сгенерирован однострочным), а также не требует использования символа «X» (также может быть добавлено очень просто).

Используя STL, мы можем прийти с решением, от исходного исходного файла до конечного файла назначения в net 5 строках кода.

См.:

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

std::regex reFloat{ R"([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)" };

using SVector = std::vector<std::string>;
using SVectorIter = SVector::iterator;

int main() {
    // Open source file and check, if it coud be opened
    if (std::ifstream sourceFileStream{ "r:\\dummyValues.dat" }; sourceFileStream) {

        // Open destination file and check, if it could be opened
        if (std::ofstream finalFileStream("r:\\benchmarks_final.dat"); finalFileStream) {

            // Algorithm start ----------------------------------------------------------
            // Define a string variable and initialize it with the contents of the file
            std::string completeFile(std::istreambuf_iterator<char>(sourceFileStream), {});

            // Define vector and initialize it with all float values from the file
            SVector values(std::sregex_token_iterator(completeFile.begin(), completeFile.end(), reFloat), {});

            // Iterate over the vector and find the next value equal to first-value
            for (SVectorIter svi{ values.begin() }; (svi = std::find(svi, values.end(), values[0])) != values.end(); ++svi) {

                 // Copy 16 value to the final file
                std::copy_n(svi, std::min(16, std::distance(svi, values.end())), std::ostream_iterator<std::string>(finalFileStream, " "));
                finalFileStream << '\n';
            }
            // Algorithm end ----------------------------------------------------------
        }
        else {
            std::cerr << "\n*** Error: Could not open final file\n";
        }
    }
    else {
        std::cerr << "\n*** Error: Could not open source file\n";
    }
}

Я думаю, что нет, но если вам нужно Если вы заинтересованы в том, чтобы понять это, я вам объясню. Пожалуйста, спрашивайте, я рад помочь.


РЕДАКТИРОВАТЬ Вас интересуют дальнейшие объяснения. Это нормально, но я, конечно, не могу дать здесь полное руководство.

Тогда давайте начнем с некоторых важных функций, которые мы здесь используем. Так называемый constructor. Вы уже должны это знать. Это специальная функция класса, которая в основном используется для «инициализации» данных класса. Если у вас есть такой класс, как

struct MyClass {
    int a;
    // Constructor. Very simplified
    MyClass(int value) { a = value; }
};

И если вы хотите определить переменную этого типа (создать экземпляр этого типа), вы можете написать:

MyClass test(3);

This вызовет конструктор и присвоит 3 члену класса a. Вы уже должны в принципе это знать. Если вы будете искать классы в Справочнике по C ++ , вы всегда найдете конструктор для определенных классов.

Хорошо, тогда давайте начнем. Во-первых, мы хотим открыть файл для ввода. Для этого мы будем использовать класс STL std::ifstream. И мы будем использовать один из его конструкторов . Пожалуйста, посмотрите ссылку. Конструктор 2 принимает имя файла как const char* и открывает файл. Неявно вызывает open (см. Описание в ссылке). Деструктор, который будет вызван автоматически, когда переменная выпадет из области видимости (После следующего совпадения }. Деструктор автоматически закроет файл для вас. Это очень хорошо и позволяет избежать ошибок.

Итак, вы можете написать:

    // Some previous code here
    // . . .

    {   
        // Some previous code here
        // . . .

        // Open the file
        std::ifstream sourceFileStream{ "r:\\dummyValues.dat" };

        // more code
        // . . .

    }  // Now the scope is closed. The Variable "sourceFileStream" will fall out of scope
       //  and the file will be close automatically.

Cool.

Теперь очень важная рекомендация. Вы всегда должны проверять, была ли операция ввода-вывода успешной. Как это сделать. Для этого вы должен знать, что std::stream имеет своего рода состояние ошибки. И вы можете и должны это проверить. Вы можете прочитать об этих «состояниях» здесь . Если вы открываете файл, и он не работает . Будет установлен failbit. Это вы можете прочитать здесь . Итак, вы можете проверить бит отказа с помощью оператора:

if (sourceFileStream.rdstate() == std::ios_base::failbit)

Но это как-то сложно. Итак, разработчики классов IO-потока определили для класса оператор bool . И, как вы можете прочитать в описании, он проверяет, нет ли в потоке ошибок. Для оператора if требуется логическое значение состояние. И я Если вы напишете

    if (sourceFileStream) {

, то возвращается логический оператор этой переменной и показывает, есть ли ошибка. Это очень полезно.

Затем, в C ++ 17, был введен новый синтаксис для оператора if . Теперь вы можете определить и инициализировать переменную в операторе if. Например,

    if (int inner = 0; x < y) {  . . . }

По-прежнему условие x < y. Кроме того, будет определена переменная "внутренняя", и преимущество состоит в том, что эта переменная видна только во внутренней области действия оператора if. После закрывающей скобки } блока if переменная исчезнет.

И теперь мы можем собрать все части вместе:

    if (std::ifstream sourceFileStream{ "r:\\dummyValues.dat" }; sourceFileStream) {

Первая часть этого if оператора определяет переменную «sourceFileStream» и вызывает ее конструктор с именем файла. Итак, он открывает файл. Вторая часть if statement - это условие, вызывающее оператор bool в «sourceFileStream», чтобы проверить, можно ли открыть файл. И хорошо то, что переменная "sourceFileStream" видна только в блоке if. Нет необходимости, чтобы он был доступен за пределами этого блока, потому что, если бы файл не мог быть открыт, что бы вы с ним сделали. И, кроме того. После того, как блок if завершится символом "}", файл будет автоматически закрыт, потому что переменная выпадает из области видимости, что вызовет деструктор.

Вау, это было большим объяснением для одного строка кода.


Следующая строка:

std::string completeFile(std::istreambuf_iterator<char>(sourceFileStream), {});

Ух, ох. Что это такое?

Очевидно, мы определяем переменную с именем «completeFile» с типом данных std::string. Хорошо, это все еще понятно. А затем у нас есть (...), поэтому вызываем конструктор std::string s. Пожалуйста, прочтите здесь о конструкторе номер 6. Он говорит:

Создает строку с содержимым диапазона [first, last).

Таким образом, требуется iterator для первого элемента диапазона и итератор после последнего элемента диапазона. Затем он копирует все символы из этого диапазона в переменную «completeFile». Таким образом, он заполнит std::string символами, заданными двумя итераторами.

Итак, nect part: std::istreambuf_iterator<char>(sourceFileStream). Это итератор begin диапазона. Пожалуйста, прочтите здесь , что

std :: istreambuf_iterator - это однопроходный итератор ввода, который считывает последовательные символы из объекта std :: basic_streambuf, для которого он был создан.

Итак, эта штука читает последовательные символы из нашего "sourceFileStream". Он будет читать символ после символа из исходного файла и копирует их в нашу строковую переменную. Это происходит до итератора end, поэтому до {}. Хм, что это? Это пустой инициализатор по умолчанию в фигурных скобках, и он просто вызывает конструктор по умолчанию. И если вы посмотрите описание конструктора std::istreambuf_iterator s номер 1 , то увидите, что это будет «итератор конца потока». И при этом все символы из исходного файла, от первого до последнего символа, будут прочитаны и скопированы в нашу std::string переменную "completeFile".

Круто, правда? Одновременное определение доступного файла и чтение всего исходного файла.


Следующая строка:

std::vector<std::string>  values(std::sregex_token_iterator(completeFile.begin(), completeFile.end(), reFloat), {});

Это может быть слишком сложно для этого здесь, и поэтому я могу объяснить только основы . Итак, сначала мы определим переменную с именем «values» типа std::vector`````of std :: strings . So, we want to store many std :: strings in a std :: vector````. Это понятно. И угадайте, что, мы инициализируем эту переменную, используя ее конструктор. И мы снова будем использовать конструктор диапазона, здесь номер конструктора 5 . И это

Создает контейнер с содержимым диапазона [first, last).

То же, что и std::string выше. Он копирует все строки, на которые указывают итераторы, от первой до последней в наш вектор. Итератор begin - это std :: sregex_token_iterator . Я не могу объяснить здесь теорию regex, но просто сказал, что она будет перебирать шаблон похожих элементов, определенных регулярным выражением на «метаязыке», и возвращать их, чтобы их можно было скопировать в наш вектор.

И это шаблон значения с плавающей запятой. Если вы посмотрите на значение с плавающей запятой, вы можете обнаружить закономерность. Он состоит из необязательных знаков + -, затем нескольких цифр, может быть, точки и других цифр. Это всегда одно и то же. И этот шаблон мы описываем с помощью регулярного выражения [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?. Вы можете скопировать и вставить это в здесь и поиграть с ним. Тогда вы сможете получить первое понимание.

Вернуться к итератору. Таким образом, он вернет только те строки, которые соответствуют шаблонам. Таким образом, он вернет только строки с плавающей запятой. Он не вернет никаких других символов. Только те, которые вам нужны.

И так же, как и выше, он начинается с первого шаблона в данной строке и заканчивается на {}. Как и выше, конструктор по умолчанию № 1"Создает итератор конца последовательности".


Если вас интересует больше, прокомментируйте, а я объясню остальное

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...