Pytanie w sprawie csv, c++, standard-library – Szybkie, proste analizowanie CSV w C ++

8

Próbuję przeanalizować prosty plik CSV z danymi w formacie takim jak:

20.5,20.5,20.5,0.794145,4.05286,0.792519,1
20.5,30.5,20.5,0.753669,3.91888,0.749897,1
20.5,40.5,20.5,0.701055,3.80348,0.695326,1

Tak, bardzo prosty i stały plik formatu. Przechowuję każdą kolumnę tych danych do wektora STL. Jako taki próbowałem pozostać w C ++ przy użyciu standardowej biblioteki, a moja implementacja w pętli wygląda tak:

string field;
getline(file,line);
stringstream ssline(line);

getline( ssline, field, ',' );
stringstream fs1(field);
fs1 >> cent_x.at(n);

getline( ssline, field, ',' );
stringstream fs2(field);
fs2 >> cent_y.at(n);

getline( ssline, field, ',' );
stringstream fs3(field);
fs3 >> cent_z.at(n);

getline( ssline, field, ',' );
stringstream fs4(field);
fs4 >> u.at(n);

getline( ssline, field, ',' );
stringstream fs5(field);
fs5 >> v.at(n);

getline( ssline, field, ',' );
stringstream fs6(field);
fs6 >> w.at(n);

Problem polega na tym, że jest to bardzo powolne (jest ponad milion wierszy na plik danych) i wydaje mi się, że jest trochę nieeleganckie. Czy istnieje szybsze podejście przy użyciu standardowej biblioteki, czy też powinienem użyć funkcji stdio? Wydaje mi się, że cały ten blok kodu sprowadziłby się do pojedynczego wywołania fscanf.

Z góry dziękuję!

Twoja odpowiedź

3   odpowiedź
8

Używanie 7 strumieni strumieniowych, gdy możesz to zrobić za pomocą jednego, na pewno nie pomoże. wydajność. Spróbuj tego:

string line;
getline(file, line);

istringstream ss(line);  // note we use istringstream, we don't need the o part of stringstream

char c1, c2, c3, c4, c5;  // to eat the commas

ss >> cent_x.at(n) >> c1 >>
      cent_y.at(n) >> c2 >>
      cent_z.at(n) >> c3 >>
      u.at(n) >> c4 >>
      v.at(n) >> c5 >>
      w.at(n);

Jeśli znasz liczbę linii w pliku, możesz zmienić rozmiar wektorów przed odczytaniem, a następnie użyćoperator[] zamiastat(). W ten sposób unikniesz sprawdzania granic, a tym samym uzyskasz niewielką wydajność.

Idealny! Działa dużo, dużo lepiej. Dzięki za podpowiedź o znakach do jedzenia przecinków! Kyle Lynch
@KyleLynch: Poważnie radziłbym, abyś sprawdziłchar zostały zainicjowane przecinkami. Powinieneś również sprawdzić, czy strumień jest poprawny LUB ustawić flagi wyjątków, aby być ostrzeżonym w przypadku złego wyjścia. Matthieu M.
Co jest potrzebne do strumienia wejściowego? Dlaczego nie ma go w kodzie? Tomáš Zato
drobiazg: wystarczyłoby jeden znak do jedzenia przecinków IceFire
2

Uważam, że głównym wąskim gardłem (pomijając nie buforowane we / wy oparte na getline ()) jest parsowanie łańcucha. Ponieważ masz symbol „,” jako ogranicznik, możesz wykonać liniowe skanowanie ciągu i zastąpić wszystkie „,” przez „0” (znacznik końca łańcucha, zero-terminator).

Coś takiego:

// tmp array for the line part values
double parts[MAX_PARTS];

while(getline(file, line))
{
    size_t len = line.length();
    size_t j;

    if(line.empty()) { continue; }

    const char* last_start = &line[0];
    int num_parts = 0;

    while(j < len)
    {
        if(line[j] == ',')
        {
           line[j] = '\0';

           if(num_parts == MAX_PARTS) { break; }

           parts[num_parts] = atof(last_start);
           j++;
           num_parts++;
           last_start = &line[j];
        }
        j++;
    }

    /// do whatever you need with the parts[] array
 }
1

Nie wiem, czy będzie to szybsze niż zaakceptowana odpowiedź, ale równie dobrze mogę je opublikować, jeśli chcesz spróbować. Możesz załadować całą zawartość pliku za pomocą pojedynczego wywołania odczytu, znając rozmiar pliku za pomocą niektórychmagia fseek. Będzie to znacznie szybsze niż wielokrotne wywołania odczytu.

Następnie możesz zrobić coś takiego, aby przeanalizować swój ciąg:

//Delimited string to vector
vector<string> dstov(string& str, string delimiter)
{
  //Vector to populate
  vector<string> ret;
  //Current position in str
  size_t pos = 0;
  //While the the string from point pos contains the delimiter
  while(str.substr(pos).find(delimiter) != string::npos)
  {
    //Insert the substring from pos to the start of the found delimiter to the vector
    ret.push_back(str.substr(pos, str.substr(pos).find(delimiter)));
    //Move the pos past this found section and the found delimiter so the search can continue
    pos += str.substr(pos).find(delimiter) + delimiter.size();
  }
  //Push back the final element in str when str contains no more delimiters
  ret.push_back(str.substr(pos));
  return ret;
}

string rawfiledata;

//This call will parse the raw data into a vector containing lines of
//20.5,30.5,20.5,0.753669,3.91888,0.749897,1 by treating the newline
//as the delimiter
vector<string> lines = dstov(rawfiledata, "\n");

//You can then iterate over the lines and parse them into variables and do whatever you need with them.
for(size_t itr = 0; itr < lines.size(); ++itr)
  vector<string> line_variables = dstov(lines[itr], ",");

Powiązane pytania