كيف يمكنني قراءة بيانات ملف CSV ومعالجتها في لغة C++؟[ينسخ]

StackOverflow https://stackoverflow.com/questions/415515

  •  03-07-2019
  •  | 
  •  

سؤال

هذا السؤال لديه بالفعل إجابة هنا:

لا يحتاج إلى شرح، لقد جربت جوجل وحصلت على الكثير من تبادل الخبراء المخيف، وبحثت هنا أيضًا دون جدوى.سيكون البرنامج التعليمي أو المثال عبر الإنترنت هو الأفضل.شكرا يا شباب.

هل كانت مفيدة؟

المحلول

إذا كان ما تفعله حقًا هو معالجة ملف CSV نفسه، فإن إجابة نيلسون منطقية.ومع ذلك، فإن شكوكي هي أن ملف CSV هو مجرد قطعة أثرية من المشكلة التي تحلها.في C++، ربما يعني ذلك أن لديك شيئًا مثل هذا كنموذج بياناتك:

struct Customer {
    int id;
    std::string first_name;
    std::string last_name;
    struct {
        std::string street;
        std::string unit;
    } address;
    char state[2];
    int zip;
};

وبالتالي، عندما تعمل مع مجموعة من البيانات، فمن المنطقي أن يكون لديك std::vector<Customer> أو std::set<Customer>.

مع أخذ ذلك في الاعتبار، فكر في التعامل مع ملف CSV الخاص بك كعمليتين:

// if you wanted to go nuts, you could use a forward iterator concept for both of these
class CSVReader {
public:
    CSVReader(const std::string &inputFile);
    bool hasNextLine();
    void readNextLine(std::vector<std::string> &fields);
private:
    /* secrets */
};
class CSVWriter {
public:
    CSVWriter(const std::string &outputFile);
    void writeNextLine(const std::vector<std::string> &fields);
private:
    /* more secrets */
};
void readCustomers(CSVReader &reader, std::vector<Customer> &customers);
void writeCustomers(CSVWriter &writer, const std::vector<Customer> &customers);

يمكنك قراءة وكتابة صف واحد في كل مرة، بدلاً من الاحتفاظ بتمثيل كامل للملف نفسه في الذاكرة.هناك بعض الفوائد الواضحة:

  1. يتم تمثيل بياناتك في نموذج يناسب مشكلتك (العملاء)، بدلاً من الحل الحالي (ملفات CSV).
  2. يمكنك إضافة محولات لتنسيقات البيانات الأخرى بشكل بسيط، مثل استيراد/تصدير SQL بالجملة، أو ملفات جداول بيانات Excel/OO، أو حتى HTML <table> استدعاء.
  3. من المحتمل أن تكون مساحة ذاكرتك أصغر (يعتمد على النسبي sizeof(Customer) ضد.عدد البايتات في صف واحد).
  4. CSVReader و CSVWriter يمكن إعادة استخدامها كأساس لنموذج داخل الذاكرة (مثل نموذج Nelson) دون فقدان الأداء أو الوظيفة.والعكس ليس صحيحا.

نصائح أخرى

مزيد من المعلومات ستكون مفيدة.

لكن الشكل الأبسط:

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

int main()
{
    std::ifstream  data("plop.csv");

    std::string line;
    while(std::getline(data,line))
    {
        std::stringstream  lineStream(line);
        std::string        cell;
        while(std::getline(lineStream,cell,','))
        {
            // You have a cell!!!!
        }
    }
 }

وانظر هذا السؤال أيضاً: محلل CSV في C++

يمكنك تجربة مكتبة Boost Tokenizer، ولا سيما مكتبة فاصل قائمة الهروب

لقد عملت مع الكثير من ملفات CSV في وقتي.أود أن أضيف النصيحة:

1 - اعتمادًا على المصدر (Excel، وما إلى ذلك)، قد يتم تضمين الفواصل أو علامات التبويب في الحقل.عادةً ما تكون القاعدة هي أنها ستكون "محمية" لأن الحقل سيكون محددًا بعلامات اقتباس مزدوجة، كما في "Boston, MA 02346".

2 - لن تقوم بعض المصادر بوضع علامة اقتباس مزدوجة لتحديد جميع حقول النص.مصادر أخرى سوف.والبعض الآخر سوف يحدد جميع الحقول، حتى الأرقام.

3 - عادةً ما يتم مضاعفة علامات الاقتباس المزدوجة المضمنة في الحقول التي تحتوي على علامات اقتباس مزدوجة (ويتم تحديد الحقل نفسه بعلامات اقتباس مزدوجة، كما في "George ""Babe"" Ruth).

4 - ستقوم بعض المصادر بتضمين CR/LFs (يعد برنامج Excel أحد هذه المصادر!).في بعض الأحيان سيكون مجرد CR.عادةً ما يتم تحديد الحقل بعلامات اقتباس مزدوجة، ولكن من الصعب جدًا التعامل مع هذا الموقف.

هذا تمرين جيد لنفسك لتعمل عليه :)

يجب عليك تقسيم مكتبتك إلى ثلاثة أجزاء

  • جارٍ تحميل ملف CSV
  • تمثيل الملف في الذاكرة بحيث يمكنك التعديل عليه وقراءته
  • حفظ ملف CSV مرة أخرى على القرص

إذن أنت تتطلع إلى كتابة فئة CSVDocument تحتوي على:

  • تحميل (ملف const char *) ؛
  • حفظ (ملف const char *) ؛
  • GetBody

بحيث يمكنك استخدام مكتبتك مثل هذا:

CSVDocument doc;
doc.Load("file.csv");
CSVDocumentBody* body = doc.GetBody();

CSVDocumentRow* header = body->GetRow(0);
for (int i = 0; i < header->GetFieldCount(); i++)
{
    CSVDocumentField* col = header->GetField(i);
    cout << col->GetText() << "\t";
}

for (int i = 1; i < body->GetRowCount(); i++) // i = 1 so we skip the header
{
    CSVDocumentRow* row = body->GetRow(i);
    for (int p = 0; p < row->GetFieldCount(); p++)
    {
        cout << row->GetField(p)->GetText() << "\t";
    }
    cout << "\n";
}

body->GetRecord(10)->SetText("hello world");

CSVDocumentRow* lastRow = body->AddRow();
lastRow->AddField()->SetText("Hey there");
lastRow->AddField()->SetText("Hey there column 2");

doc->Save("file.csv");

مما يعطينا الواجهات التالية:

class CSVDocument
{
public:
    void Load(const char* file);
    void Save(const char* file);

    CSVDocumentBody* GetBody();
};

class CSVDocumentBody
{
public:
    int GetRowCount();
    CSVDocumentRow* GetRow(int index);
    CSVDocumentRow* AddRow();
};

class CSVDocumentRow
{
public:
    int GetFieldCount();
    CSVDocumentField* GetField(int index);
    CSVDocumentField* AddField(int index);
};

class CSVDocumentField
{
public:
    const char* GetText();
    void GetText(const char* text);
};

الآن عليك فقط ملء الفراغات من هنا :)

صدقني عندما أقول هذا - إن استثمار وقتك في تعلم كيفية إنشاء المكتبات، وخاصة تلك التي تتعامل مع تحميل البيانات ومعالجتها وحفظها، لن يزيل اعتمادك على وجود مثل هذه المكتبات فحسب، بل سيجعلك أيضًا حول مبرمج أفضل.

:)

يحرر

لا أعرف مقدار ما تعرفه بالفعل عن معالجة السلسلة والتحليل؛لذلك إذا واجهتك مشكلة سأكون سعيدًا بالمساعدة.

إليك بعض التعليمات البرمجية التي يمكنك استخدامها.يتم تخزين البيانات من ملف CSV داخل مجموعة من الصفوف.كل صف عبارة عن مجموعة من السلاسل.أتمنى أن يساعدك هذا.

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
typedef std::string String;
typedef std::vector<String> CSVRow;
typedef CSVRow::const_iterator CSVRowCI;
typedef std::vector<CSVRow> CSVDatabase;
typedef CSVDatabase::const_iterator CSVDatabaseCI;
void readCSV(std::istream &input, CSVDatabase &db);
void display(const CSVRow&);
void display(const CSVDatabase&);
int main(){
  std::fstream file("file.csv", std::ios::in);
  if(!file.is_open()){
    std::cout << "File not found!\n";
    return 1;
  }
  CSVDatabase db;
  readCSV(file, db);
  display(db);
}
void readCSV(std::istream &input, CSVDatabase &db){
  String csvLine;
  // read every line from the stream
  while( std::getline(input, csvLine) ){
    std::istringstream csvStream(csvLine);
    CSVRow csvRow;
    String csvCol;
    // read every element from the line that is seperated by commas
    // and put it into the vector or strings
    while( std::getline(csvStream, csvCol, ',') )
      csvRow.push_back(csvCol);
    db.push_back(csvRow);
  }
}
void display(const CSVRow& row){
  if(!row.size())
    return;
  CSVRowCI i=row.begin();
  std::cout<<*(i++);
  for(;i != row.end();++i)
    std::cout<<','<<*i;
}
void display(const CSVDatabase& db){
  if(!db.size())
    return;
  CSVDatabaseCI i=db.begin();
  for(; i != db.end(); ++i){
    display(*i);
    std::cout<<std::endl;
  }
}

استخدام رمز التعزيز لتحليل السجلات, انظر هنا لمزيد من التفاصيل.

ifstream in(data.c_str());
if (!in.is_open()) return 1;

typedef tokenizer< escaped_list_separator<char> > Tokenizer;

vector< string > vec;
string line;

while (getline(in,line))
{
    Tokenizer tok(line);
    vec.assign(tok.begin(),tok.end());

    /// do something with the record
    if (vec.size() < 3) continue;

    copy(vec.begin(), vec.end(),
         ostream_iterator<string>(cout, "|"));

    cout << "\n----------------------" << endl;
}

ينظر الى 'ممارسة البرمجة(TPOP) بواسطة كيرنيغان وبايك.ويتضمن مثالاً لتحليل ملفات CSV في كل من C وC++.ولكن سيكون من المفيد قراءة الكتاب حتى لو لم تستخدم الكود.

(عنوان URL السابق: http://cm.bell-labs.com/cm/cs/tpop/)

لقد وجدت هذا النهج المثير للاهتمام:

فائدة هيكل CSV إلى C

يقتبس:CSVtoC هو برنامج يأخذ ملف CSV أو ملف قيم مفصولة بفاصلة كمدخل ويتخلص منه كبنية C.

وبطبيعة الحال، لا يمكنك إجراء تغييرات على ملف CSV، ولكن إذا كنت تحتاج فقط إلى الوصول للقراءة فقط في الذاكرة إلى البيانات، فقد ينجح ذلك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top