Как мне разобрать этот файл в cpp? - PullRequest
0 голосов
/ 16 февраля 2020

Я хочу проанализировать файл со следующим содержанием:

2 300
abc12 130
bcd22 456
3 400
abfg12 230
bcpd22 46
abfrg2 13

Здесь 2 - количество строк, 300 - вес.

Каждая строка содержит строку и номер (цена). То же самое с 3 и 400.

Мне нужно хранить 130, 456. В массиве.

В настоящее время я читаю файл, и каждая строка обрабатывается как std::string. Мне нужна помощь для дальнейшего развития.

Код:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

//void processString(string line);
void process2(string line);

int main(int argc, char ** argv) {
    cout << "You have entered " << argc <<
        " arguments:" << "\n";

    for (int i = 1; i < argc; ++i)
        cout << argv[i] << "\n";

    //2, 4 are the file names

    //Reading file - market price file
    string line;
    ifstream myfile(argv[2]);
    if (myfile.is_open()) {
        while (getline(myfile, line)) {
            //  cout << line << '\n';
        }
        myfile.close();
    } else cout << "Unable to open market price file";

    //Reading file - price list file
    string line_;
    ifstream myfile2(argv[4]);
    int c = 1;
    if (myfile2.is_open()) {
        while (getline(myfile2, line_)) {
            // processString(line_);
            process2(line_);
        }
        myfile2.close();
    } else cout << "Unable to open price lists file";

    //processString(line_);
    return 0;
}

void process2(string line) {

    string word = "";

    for (auto x: line) {
        if (x == ' ') {
            word += " ";
        } else {
            word = word + x;
        }
    }
    cout << word << endl;
}

Есть ли функция разделения, как в Java, так что я могу разделить и сохранить все как токены?

1 Ответ

1 голос
/ 16 февраля 2020

В вашем сообщении есть 2 вопроса:

  1. Как мне разобрать этот файл в cpp?
  2. Есть ли функция разделения, как в Java, поэтому я можно разделить и сохранить все как токены?

Я отвечу на оба вопроса и покажу демонстрационный пример.

Начнем с разбиения строки на токены. Есть несколько возможностей. Начнем с простых.

Поскольку токены в вашей строке разделены пробелами, мы можем воспользоваться функциональностью оператора экстрактора (>>). Это будет считывать данные из входного потока до пробела, а затем преобразует эти считанные данные в указанную переменную. Вы знаете, что эта операция может быть прикована цепью.

Тогда для примера строки

    const std::string line{ "Token1 Token2 Token3 Token4" };

вы можете просто поместить это в std::istringstream и затем извлечь переменные из потока:

    std::istringstream iss1(line);
    iss1 >> subString1 >> subString2 >> subString3 >> subString4;

Недостаток в том, что вам нужно написать много вещей и знать количество элементов в строке.

Мы можем преодолеть эту проблему с помощью вектора в качестве хранилища данных taget и заполнить его конструктором диапазона , Конструктор диапазона векторов берет начальный и конечный интегратор и копирует в него данные.

В качестве итератора мы используем std::istream_iterator. Проще говоря, вызывать оператор экстрактора (>>), пока все данные не будут использованы. Какое бы количество данных у нас не было.

Это будет выглядеть следующим образом:

    std::istringstream iss2(line);
    std::vector token(std::istream_iterator<std::string>(iss2), {});

Это может выглядеть сложно, но это не так. Мы определяем переменную «токен» типа std::vector. Мы используем его конструктор диапазона.

И мы можем определить std :: vector без аргумента шаблона. Компилятор может вывести аргумент из заданных параметров функции. Эта функция называется CTAD («дедукция аргумента шаблона класса», требуется C ++ 17).

Кроме того, вы можете видеть, что я не использую итератор end () явно.

Этот итератор будет создан из пустого инициализатора по умолчанию, заключенного в фигурные скобки, с правильным типом, потому что он будет выведен таким же, как тип первого аргумента, поскольку конструктор std :: vector требует этого.


Есть дополнительное решение. Это самое мощное решение и, следовательно, может быть немного сложным в начале.

При этом можно избежать использования std :: istringstream и напрямую преобразовать строку в токены с помощью std :: sregex_token_iterator. Очень прост в использовании. В результате получается один слой для разделения исходной строки:

std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

Итак, современный C ++ имеет встроенную функциональность, которая точно разработана для целей токенизации строк. Это называется std::sregex_token_iterator. Что это за вещь?

Как следует из названия, это итератор. Он будет перебирать строку (отсюда и «s» в ее имени) и возвращать токены разделения. Токены будут сопоставлены снова с регулярным выражением. Или изначально разделитель будет сопоставлен, а остальные будут считаться токеном и возвращаться. Это будет контролироваться с помощью последнего флага в его конструкторе.

Давайте посмотрим на этот конструктор:

token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

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

  • 1, если вы хотите иметь положительное совпадение с регулярным выражением
  • -1, вернет все, что не соответствует регулярному выражению

И последнее, но не менее важное - само регулярное выражение. Пожалуйста, прочитайте в net о регулярных выражениях. Есть тонны доступных страниц.

Пожалуйста, ознакомьтесь с демонстрацией для всех 3 решений здесь:

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

/// Split string into tokens
int main() {

    // White space separated tokens in a string
    const std::string line{ "Token1 Token2 Token3 Token4" };

    // Solution 1: Use extractor operator ----------------------------------

    // Here, we will store the result
    std::string subString1{}, subString2{}, subString3{}, subString4{};

    // Put the line into an istringstream for easier extraction
    std::istringstream iss1(line);
    iss1 >> subString1 >> subString2 >> subString3 >> subString4;

    // Show result
    std::cout << "\nSolution 1:  Use inserter operator\n- Data: -\n" << subString1 << "\n"
        << subString2 << "\n" << subString3 << "\n" << subString4 << "\n";


    // Solution 2: Use istream_iterator ----------------------------------
    std::istringstream iss2(line);
    std::vector token(std::istream_iterator<std::string>(iss2), {});

    // Show result
    std::cout << "\nSolution 2:  Use istream_iterator\n- Data: -\n";
    std::copy(token.begin(), token.end(), std::ostream_iterator<std::string>(std::cout, "\n"));


    // Solution 3: Use std::sregex_token_iterator ----------------------------------
    const std::regex re(" ");

    std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

    // Show result
    std::cout << "\nSolution 3:  Use sregex_token_iterator\n- Data: -\n";
    std::copy(token2.begin(), token2.end(), std::ostream_iterator<std::string>(std::cout, "\n"));


    return 0;
}


Итак, теперь ответ о том, как вы могли бы прочитать ваш текст файл.

Важно создать правильные структуры данных. Затем перезапишите оператор вставки и извлечения и вставьте в него вышеуказанные функции.

Пожалуйста, посмотрите демонстрационный пример ниже. Конечно, есть много других возможных решений:

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

struct ItemAndPrice {
    // Data
    std::string item{};
    unsigned int price{};

    // Extractor
    friend std::istream& operator >> (std::istream& is, ItemAndPrice& iap) {

        // Read a complete line from the stream and check, if that worked
        if (std::string line{}; std::getline(is, line)) {

            // Read the item and price from that line and check, if that worked
            if (std::istringstream iss(line); !(iss >> iap.item >> iap.price))

                // There was an error, while reading item and price. Set failbit of input stream
                is.setf(std::ios::failbit);
        }
        return is;
    }

    // Inserter
    friend std::ostream& operator << (std::ostream& os, const ItemAndPrice& iap) {
        // Simple output of our internal data
        return os << iap.item << " " << iap.price;
    }
};

struct MarketPrice {
    // Data
    std::vector<ItemAndPrice> marketPriceData{};
    size_t numberOfElements() const { return marketPriceData.size(); }
    unsigned int weight{};

    // Extractor
    friend std::istream& operator >> (std::istream& is, MarketPrice& mp) {

        // Read a complete line from the stream and check, if that worked
        if (std::string line{}; std::getline(is, line)) {

            size_t numberOfEntries{};
            // Read the number of following entries and the weigth from that line and check, if that worked
            if (std::istringstream iss(line); (iss >> numberOfEntries >> mp.weight)) {

                mp.marketPriceData.clear();
                // Now copy the numberOfEntries next lines into our vector
                std::copy_n(std::istream_iterator<ItemAndPrice>(is), numberOfEntries, std::back_inserter(mp.marketPriceData));
            }
            else {
                // There was an error, while reading number of following entries and the weigth. Set failbit of input stream
                is.setf(std::ios::failbit);
            }
        }
        return is;
    };

    // Inserter
    friend std::ostream& operator << (std::ostream& os, const MarketPrice& mp) {

        // Simple output of our internal data
        os << "\nNumber of Elements: " << mp.numberOfElements() << "   Weight: " << mp.weight << "\n";

        // Now copy all marekt price data to output stream
        if (os) std::copy(mp.marketPriceData.begin(), mp.marketPriceData.end(), std::ostream_iterator<ItemAndPrice>(os, "\n"));

        return os;
    }
};

// For this example I do not use argv and argc and file streams. 
// This, because on Stackoverflow, I do not have files on Stackoverflow
// So, I put the file data in an istringstream. But for the below example, 
// there is no difference between a file stream or a string stream

std::istringstream sourceFile{R"(2 300
abc12 130
bcd22 456
3 400
abfg12 230
bcpd22 46
abfrg2 13)"};


int main() {

    // Here we will store all the resulting data
    // So, read the complete source file, parse the data and store result in vector
    std::vector mp(std::istream_iterator<MarketPrice>(sourceFile), {});

    // Now, all data are in mp. You may work with that now

    // Show result on display
    std::copy(mp.begin(), mp.end(), std::ostream_iterator<MarketPrice>(std::cout, "\n"));

    return 0;
}
...