Как прочитать список CSV из файла .txt и отделить каждый из элементов в их собственный массив - PullRequest
2 голосов
/ 10 марта 2020

Вот список, с которым я работаю:

Painting,150.10,10
Lamp,100.20,10
Rug,200.00,10
Clock,100.00,10
Sculpture,300.00,10
Photograph,200.00,10
Pottery,100.00,10
Watch,300.00,10
Wedgwood,70.00,10
Tiles,500.00,10

Я до сих пор придумал это для разделения элементов и получаю ошибки:

ifstream file("antiquelist.txt");
string name;
float price;
int quantity;
while(file.good()){
    getline(file, name, ',', price, ',', quantity);
    cout << name << endl;

Ответы [ 3 ]

2 голосов
/ 10 марта 2020

Есть несколько способов приблизиться к разбору значений из строки в три значения, которые вы хотите std::string item, double price, int qty. Вы можете выбрать несколько маршрутов, (1.) прочитать непосредственно из файлового потока или (2.) создать std::stringstream из каждой строки и прочитать значения из строки. Разница заключается в том, что при удалении непосредственно из вашего файлового потока необходимо удалить '\n', оставленный во входном потоке.

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

Основной подход c заключается в объявлении строки для хранения каждой строки и откройте ваш файл (и подтвердите, что файл открыт для чтения), например,

    std::string s;
    std::ifstream f (argv[1]);  /* open file stream */

    if (!f.good()) { /* validate file stream state good */
        std::cerr << "error: file open failed.\n";
        return 1;
    }

Далее l oop, прочитав каждую строку и создав поток строк из каждой строки. То, как вы анализируете значение из строкового потока, может занять несколько разных маршрутов, но просто проверяя чтение каждого значения сразу, избегайте чтения во временные строки и затем конвертируйте в double и int, что потребует отдельного try {...} catch() {..} обработка исключений.

Основной подход c заключается в чтении элемента с использованием getline (...,',') с разделителем ',', который будет считывать все до первого ',' отбрасывания ','. Затем вы можете прочитать ваш price напрямую. Цена считывания извлечения символов остановится на ',', которую вы можете прочитать, используя getline (...,','), во временную переменную (вы можете просто прочитать char, но это не получится, если между концом price и ','. Наконец, просто прочитайте qty напрямую (проверяя, что каждое чтение выполнено успешно)

    while (getline (f, s)) {        /* read each line into s */
        int qty;
        double price;
        std::string item, tmp;
        std::stringstream ss (s);   /* create stringstream from s */
        /* read all 3 values from stringstream into separate values */
        if (getline(ss,item,',') && ss >> price && getline(ss,tmp,',') && ss >> qty) {
            std::cout << "item: " << std::left << std::setw(10) << item 
                        << "  price: " << std::right << std::setw(5) 
                        << std::fixed << std::setprecision(1) << price 
                        << "  qty: " << qty << '\n';
        }
        else    /* if 3 values not read/handle error */
            std::cerr << "invalid format: '" << s << "'\n";
    }

В целом приведем пример, который будет иметь:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>

int main (int argc, char **argv) {

    if (argc < 2) { /* validate at least 1 argument for filename */
        std::cerr << "usage: " << argv[0] << " filename\n";
        return 1;
    }
    std::string s;
    std::ifstream f (argv[1]);  /* open file stream */

    if (!f.good()) { /* validate file stream state good */
        std::cerr << "error: file open failed.\n";
        return 1;
    }

    while (getline (f, s)) {        /* read each line into s */
        int qty;
        double price;
        std::string item, tmp;
        std::stringstream ss (s);   /* create stringstream from s */
        /* read all 3 values from stringstream into separate values */
        if (getline(ss,item,',') && ss >> price && getline(ss,tmp,',') && ss >> qty) {
            std::cout << "item: " << std::left << std::setw(10) << item 
                        << "  price: " << std::right << std::setw(5) 
                        << std::fixed << std::setprecision(1) << price 
                        << "  qty: " << qty << '\n';
        }
        else    /* if 3 values not read/handle error */
            std::cerr << "invalid format: '" << s << "'\n";
    }
}

Пример Использование / Вывод

$ ./bin/readartitems dat/antiquelist.txt
item: Painting    price: 150.1  qty: 10
item: Lamp        price: 100.2  qty: 10
item: Rug         price: 200.0  qty: 10
item: Clock       price: 100.0  qty: 10
item: Sculpture   price: 300.0  qty: 10
item: Photograph  price: 200.0  qty: 10
item: Pottery     price: 100.0  qty: 10
item: Watch       price: 300.0  qty: 10
item: Wedgwood    price:  70.0  qty: 10
item: Tiles       price: 500.0  qty: 10

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

1 голос
/ 15 марта 2020

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

Кроме того, OP является новым для C ++, и не будет его понимать. Но поскольку эти вопросы помечены C ++, а другие ответы очень C -Style-i sh, я хочу показать здесь более современное решение C ++ с объектно-ориентированным подходом.

И Я везде вижу злоупотребление std::getline для разделения строк, что невероятно, поскольку существуют специальные функции для разделения строк. Предполагаемое использование для std::getline - «получить линию» из потока. Как следует из названия. Люди возятся с этой функцией и ищут разделители и так далее, но, по моему скромному мнению, мы не должны этого делать.

Уже около 10 лет у нас есть специальная специальная функция C ++ для разбиения строк на токены. , специально разработанный для этой цели. std::sregex_token_iterator. И поскольку у нас есть такая выделенная функция, мы должны просто использовать ее.

Идея в том, что это концепция итератора. В C ++ у нас есть много контейнеров и всегда итераторов, чтобы перебирать похожие элементы в этих контейнерах. И строка с похожими элементами (токенами), разделенными разделителем, также может рассматриваться как такой контейнер. А с помощью std::sregex:token_iterator мы можем перебирать элементы / токены / подстроки строки, эффективно разбивая ее на части.

Этот итератор очень мощный, и вы можете делать с ним гораздо более сложные вещи. Но это слишком много для здесь. Важно то, что разбиение строки на токены - это одна строка. Например, определение переменной с использованием конструктора диапазона для итерации по токенам. См. Например:

    // The delimiter
    const std::regex delimiter{ "," };

    // Test data
    std::string csvData("d1,d2,d3,d4,d5");

    // Split the string
    std::vector<std::string> tokens(std::sregex_token_iterator(csvData.begin(), csvData.end(), delimiter, -1), {});

Ant, так как это итератор, вы можете использовать его со многими алгоритмами. Вы должны действительно изучить и использовать это. И для всех этих людей, имеющих большие опасения по поводу эффективности, обратите внимание, что в 90% случаев будет проанализировано только несколько строк. Нет проблем.

Итак, дальше. У нас есть объектно-ориентированный язык. Итак, давайте используем его и определяем класс для ваших данных с упомянутыми вами членами данных, а также дополнительными функциями или операторами для работы с этими элементами данных. Только класс должен знать о его внутренностях. Снаружи мы хотим использовать его, не зная реализации.

В приведенном ниже примере экстрактор и iserter будут перезаписаны, чтобы включить потоковые операции ввода-вывода. В экстракторе мы также будем использовать функцию regex и точно проверим, соответствует ли строка ввода нашим ожиданиям. Для этого мы используем std::regex, который точно определяет шаблон данных в строке CSV. Затем, если мы нашли совпадение, мы можем использовать данные. Таким образом, это не только разделение строки, но и проверка правильности ввода. Функция для этого: std::regex_match.

И, если вы посмотрите на main, чтение и синтаксический анализ всех данных CSV будут однострочными. То же самое для вывода данных.

Пожалуйста, смотрите:

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

const std::regex re{R"(^\s*?(\b\w+)\s*?,\s*?(\d+\.\d+)\s*?,\s*?(\d+)\s*?$)"};

// Proxy class for easy input and output
struct MyData {
    std::string name{};
    double price{};
    long quantity{};

    // Overwrite extractor operator for easier input
    friend std::istream& operator >> (std::istream& is, MyData& md) {

        // Read a complete line from your CSV file
        if (std::string line{}; std::getline(is, line)) {

            // Check, if the input string matches to pour expectation, so split and validate
            if (std::smatch match{}; regex_match(line, match, re)) {

                // Match is perfect. Assign the values to our internal data members
                md.name = match[1]; md.price = std::stod(match[2]); md.quantity = std::stol(match[3]);
            }
            else // No match. Set everything to default 
                md = {};
        }
        return is;
    }
    // Simple output of our data members
    friend std::ostream& operator << (std::ostream& os, const MyData& md) {
        return os << "Name: " << md.name << "\t\tPrice: " << md.price << "\tQuantity: " << md.quantity;
    }
};

int main() {

    // Open the csv File and check, if it could be opened and everything is ok
    if (std::ifstream csvStream("r:\\antiquelist.txt"); csvStream) {

        // Read and parse the complete source file
        std::vector myData(std::istream_iterator<MyData>(csvStream), {});

        // Show complete result to user
        std::copy(myData.begin(), myData.end(), std::ostream_iterator<MyData>(std::cout, "\n"));
    }
    else {
        std::cerr << "\n*** Error: Could not open input file\n";
    }
    return 0;
}

Пожалуйста, посмотрите здесь , чтобы лучше понять регулярное выражение

Как жаль что никто не будет читать это. , .

0 голосов
/ 10 марта 2020

Вам нужно получить новые строки данных через getline с параметром токенизатора по умолчанию, а затем снова проанализировать каждую строку данных с помощью getline, используя запятую ',' в качестве параметра токенизатора:

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
using namespace std;

struct Data {
    string m_name;
    float m_price;
    int m_quantity;
};

int main() {
    ifstream file("antiquelist.txt");
    string line;
    std::vector<Data> items;
    Data rowData;

    while (getline(file, line)) {
        stringstream splitter(line);

        string dataStr;

        getline(splitter, dataStr, ',');
        rowData.m_name = dataStr;

        getline(splitter, dataStr, ',');
        stringstream(dataStr) >> rowData.m_price;

        getline(splitter, dataStr);
        stringstream(dataStr) >> rowData.m_quantity;

        items.push_back(rowData);
    }

    return 0;
}
...