CYFROWY BARON • PROGRAMOWANIE • Zobacz wątek - Szybki parser CSV

Szybki parser CSV

dział ogólny

Szybki parser CSV

Nowy postprzez Corvis » wtorek, 14 września 2010, 09:20

Witam,

Napisałem sobie parser plików CSV. Działa on tak, że tworzy wektor wektorów danych:

danePlikowe[linia1] = slowo[0],slowo[1],slowo[n];


KOD cpp:     UKRYJ  
        std::ifstream file;
        file.open(OpenDialog1->FileName.c_str());
        std::string line;
        std::string word;
        int linia = 0;
        std::vector<double> dane;
        while(std::getline(file,line)) {
                     std::stringstream strstr(line);
                word = "";
                kolumna = 0;
                dane.clear();
                while (getline(strstr,word, ',')) {
                        dane.push_back(atof(word.c_str()));
                }
                this->danePlikowe.push_back(dane);
                linia++;
        }
 


Problem w tym, że przy plikach 100 MB zaczyna to się wlec :( może mi ktoś doradzić jak to przyśpieszyć ?? Może użyć innych klas ??


Pozdrawiam
"Sukcesy trwają, dopóki ich ktoś nie spieprzy. Porażki są wieczne"

Dr Gregory House
Avatar użytkownika
Corvis
Programista I
Programista I
 
Posty: 880
Dołączył(a): sobota, 26 lipca 2008, 00:31
Podziękował : 80
Otrzymał podziękowań: 30
System operacyjny: WINDOWS 7 64-bity
Kompilator: Praca - C++ Builder XE2 ENTERPRISE - Update 4, Dom - C++ Builder XE4 - Uddate 1
Gadu Gadu: 0
    Windows VistaOpera

Re: Szybki parser CSV

Nowy postprzez Cyfrowy Baron » wtorek, 14 września 2010, 12:00

W serwisie Cyfrowy Baron w dziale: porady -> StringGrid zamieściłem poradę: Zapisywanie i odczytywanie zawartości obiektu StringGrid. Znajdziesz tam przykłąd funkcji odczytującej dane z pliku CSV - SetCellText i przepisującej go do tabeli. Nie wiem czy jest szybszy od Twojego, ale możesz to sprawdzić.

Pliki CSV jako separatora używają średnika nie przecinka
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez polymorphism » wtorek, 14 września 2010, 12:31

@Corvis: Trochę pod górę kombinujesz. Spróbuj tak:

KOD cpp:     UKRYJ  
int v;
char c;
vector<double> dane;

file.imbue(locale("C")); //<--- konieczne, jeśli oddzielasz liczby double przecinkiem.

vector<char> buff(1000000);

file.rdbuf()->pubsetbuf(&buff[0],buff.size()); //<--- ustawiamy większy wewnętrzny bufor, coby mniej odczytów z dysku było.

while(file)
{
        dane.clear();

        while(file >> v)
        {
                dane.push_back(v);
                while((isspace(c = file.rdbuf()->sgetc()) && c != '\n') || c == ',')
                {
                        file.rdbuf()->snextc();
                }

                if(c == '\n')break;
        }

        this->danePlikowe.push_back(dane);
}

10'000'000 wartości czyta mi w ~53 sekundy, czyli nie tak źle. Zresztą przy tak dużych plikach tekstowych nie spodziewaj się rewelacyjnych wyników.
C++ Reference - opis wszystkich klas STL-a i funkcji C.

Za ten post autor polymorphism otrzymał podziękowanie od:
Corvis
Avatar użytkownika
polymorphism
Doświadczony Programista ● Moderator
Doświadczony Programista ● Moderator
 
Posty: 2156
Dołączył(a): piątek, 19 grudnia 2008, 13:04
Podziękował : 0
Otrzymał podziękowań: 200
System operacyjny: Windows 8.1
Windows 10
Linux Mint 21.1
Kompilator: Visual Studio
Visual Studio Code
MSYS2 (MinGW, clang)
g++
clang
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez Corvis » wtorek, 14 września 2010, 12:40

Cyfrowy Baron napisał(a):Pliki CSV jako separatora używają średnika nie przecinka


hmm

http://pl.wikipedia.org/wiki/CSV_(format_pliku)

W sumie to co to za różnica jaki jest separator ? :)


Dzieki za rady zaraz sprawdze wszystko !!
"Sukcesy trwają, dopóki ich ktoś nie spieprzy. Porażki są wieczne"

Dr Gregory House
Avatar użytkownika
Corvis
Programista I
Programista I
 
Posty: 880
Dołączył(a): sobota, 26 lipca 2008, 00:31
Podziękował : 80
Otrzymał podziękowań: 30
System operacyjny: WINDOWS 7 64-bity
Kompilator: Praca - C++ Builder XE2 ENTERPRISE - Update 4, Dom - C++ Builder XE4 - Uddate 1
Gadu Gadu: 0
    Windows VistaOpera

Re: Szybki parser CSV

Nowy postprzez Cyfrowy Baron » wtorek, 14 września 2010, 14:05

Ten średnik mi wyszedł gdy zapisałem plik Excela w formacie CSV. Po otwarciu w Notatniku są średniki nie przecinki. Rodzaj separatora jest jednak kwestią umowną, ja np. tworząc pliki álá CSV jako separatora używam na ogół znaku | lub #.

Co właściwie chcesz osiągnąć przez to parsowanie pliku CSV, czyli co chcesz zrobić z tymi danymi? Przepisać je do jakieś tabeli czy co?! Mam pewną koncepcję na szybki odczyta danych z pliku, ale muszę wiedzieć gdzie, jak, w czym, do czego maja być zapisane.

Czy chcesz je zapisać jako listę, czyli wszystkie rekordy w jednej kolumnie?

Wrzuć w załączniku jakiś plik CSV do testów, gdyż nie chce mi się go tworzyć od podstaw.
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez Corvis » wtorek, 14 września 2010, 14:09

127042,1127020,0,127040,127038,127043,127038,127031


to sa moje dane musze je wyseparować i pare operacji na niach wykonac, dodawanie, mnożenie, dzielenie.

Użytkownik wybiera tylko kolumny z których chce korzystać.

Niewiem co lepiej zrobić wpierw wyseparować dane do wektora i potem je przeliczać. Czy odrazu separować i przeliczać.
"Sukcesy trwają, dopóki ich ktoś nie spieprzy. Porażki są wieczne"

Dr Gregory House
Avatar użytkownika
Corvis
Programista I
Programista I
 
Posty: 880
Dołączył(a): sobota, 26 lipca 2008, 00:31
Podziękował : 80
Otrzymał podziękowań: 30
System operacyjny: WINDOWS 7 64-bity
Kompilator: Praca - C++ Builder XE2 ENTERPRISE - Update 4, Dom - C++ Builder XE4 - Uddate 1
Gadu Gadu: 0
    Windows VistaOpera

Re: Szybki parser CSV

Nowy postprzez Cyfrowy Baron » wtorek, 14 września 2010, 14:12

Jak rozumiem masz linię zawierającą 8 rekordów, czyli jak piszesz kolumn. Użytkownik wybiera jedną kolumnę, ale ze wszystkich linii, czyli np. bierze 4 rekord, co jest równoznaczne z czwartą kolumną i czyta ten rekord z każdej linii - TAK?
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez polymorphism » wtorek, 14 września 2010, 14:23

Corvis napisał(a):Niewiem co lepiej zrobić wpierw wyseparować dane do wektora i potem je przeliczać. Czy odrazu separować i przeliczać.

Jeśli da się to zrobić w sposób sekwencyjny, czyli bez cofania się tudzież skakania po całym pliku, to oczywiście opcja druga jest optymalniejsza.
C++ Reference - opis wszystkich klas STL-a i funkcji C.
Avatar użytkownika
polymorphism
Doświadczony Programista ● Moderator
Doświadczony Programista ● Moderator
 
Posty: 2156
Dołączył(a): piątek, 19 grudnia 2008, 13:04
Podziękował : 0
Otrzymał podziękowań: 200
System operacyjny: Windows 8.1
Windows 10
Linux Mint 21.1
Kompilator: Visual Studio
Visual Studio Code
MSYS2 (MinGW, clang)
g++
clang
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez Corvis » wtorek, 14 września 2010, 15:12

Baron:

TAK

ale i można wybrać 1 lub 2 kolumny.
"Sukcesy trwają, dopóki ich ktoś nie spieprzy. Porażki są wieczne"

Dr Gregory House
Avatar użytkownika
Corvis
Programista I
Programista I
 
Posty: 880
Dołączył(a): sobota, 26 lipca 2008, 00:31
Podziękował : 80
Otrzymał podziękowań: 30
System operacyjny: WINDOWS 7 64-bity
Kompilator: Praca - C++ Builder XE2 ENTERPRISE - Update 4, Dom - C++ Builder XE4 - Uddate 1
Gadu Gadu: 0
    Windows VistaOpera

Re: Szybki parser CSV

Nowy postprzez Cyfrowy Baron » wtorek, 14 września 2010, 15:35

Czyli w zasadzie to nie musisz rozdzielać wszystkich rekordów, z każdej linii, lecz wystarczy wyciągnąć jeden rekord z linii. Wrzuć w załączniku przykładowy plik, bym miał na czym przeprowadzać testy.
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez Corvis » wtorek, 14 września 2010, 16:12

Załącznik
Nie masz wystarczających uprawnień, aby zobaczyć pliki załączone do tego postu.
"Sukcesy trwają, dopóki ich ktoś nie spieprzy. Porażki są wieczne"

Dr Gregory House
Avatar użytkownika
Corvis
Programista I
Programista I
 
Posty: 880
Dołączył(a): sobota, 26 lipca 2008, 00:31
Podziękował : 80
Otrzymał podziękowań: 30
System operacyjny: WINDOWS 7 64-bity
Kompilator: Praca - C++ Builder XE2 ENTERPRISE - Update 4, Dom - C++ Builder XE4 - Uddate 1
Gadu Gadu: 0
    Windows VistaOpera

Re: Szybki parser CSV

Nowy postprzez Cyfrowy Baron » wtorek, 14 września 2010, 16:24

Sam utrudniasz sobie zadanie. W Twoim pliku linia ma zakończenie LF. To pewnie dlatego, że stosujesz zapis binarny. Normalnie to pliki CSV mają jako znak końca linii CR LF. Zobaczę co można z tym zrobić. Chodziło mi jednak o duży plik rzędu 10 MB o czym wspominałeś, gdyż taki mały to sam mogę sobie wygenerować.
Poza tym widzę tam tylko trzy kolumny. Czy liczba kolumn jest stała, czy zmienna?
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez Corvis » wtorek, 14 września 2010, 17:17

Liczba kolumn jest zmienna. Plik muszę ci przyciąć bo nie mam 10 mb mam po 200 - 300 coś przygotuje.
"Sukcesy trwają, dopóki ich ktoś nie spieprzy. Porażki są wieczne"

Dr Gregory House
Avatar użytkownika
Corvis
Programista I
Programista I
 
Posty: 880
Dołączył(a): sobota, 26 lipca 2008, 00:31
Podziękował : 80
Otrzymał podziękowań: 30
System operacyjny: WINDOWS 7 64-bity
Kompilator: Praca - C++ Builder XE2 ENTERPRISE - Update 4, Dom - C++ Builder XE4 - Uddate 1
Gadu Gadu: 0
    Windows VistaOpera

Re: Szybki parser CSV

Nowy postprzez Cyfrowy Baron » wtorek, 14 września 2010, 17:34

Już sobie jakoś poradziłem. Stworzyłem plik o rozmiarze 2 MB (100 000 wierzy po 3 kolumny) i kod podsumował mi go łącznie z czasem potrzebnym na wczytanie, w 1.041 sekundy. Teoretycznie więc plik o rozmiarze 100 MB (100 / 2 * 1.041) powinien obliczyć w 52 sekundy. Wydaje mi się jednak, że ten czas przeliczania nie będzie rósł tak proporcjonalnie, ale to trzeba by sprawdzić.

Swój kod oparłem na gotowej klasie TStringList, gdyż udostępnia gotowe funkcje, które umożliwiają sumowanie wybranej kolumny i nie jest to uzależnione od liczby kolumn. Kod jednak sumuje tylko jedną kolumnę na raz, ale można go przerobić tak by sumował kilka kolumn na raz.

Zasada działania kodu jest mniej więcej taka. Do jednego obiektu typu TStringList - tutaj ListaCSV wczytywany jest plik, drugiemu obiektowi typu TStringList - tutaj tempList jako symbol łamania linii ustawiam przecinek. Następnie wewnątrz pętli pobieram po kolei każdą linię tekstu z ListaCSV i przepisuję ją do temList. Ponieważ końcem linii dla tempList jest przecinek, to po pobraniu tekstu do tej listy jest on łamany i powstaje druga lista zawierająca tekst podzielony na wiersze, czyli ten zapis z ListCSV:

100802,001042,100000


zostaje zamieniony na ten zapis w liście tempList:

100802
001042
100000


Dalej wiersze listy tempList traktuję jak kolumny i pobieram wartość tylko z interesującego mnie wiersza, czyli kolumny. Dalej sprawa jest prosta, dodaję do siebie kolejne wartości, pomijam puste linie i operację powtarzam dla każdego wiersza listy ListaCSV.

Nie wiem czy mój kod jest szybszy od tego podanego przez polymorphism, trzeba by je porównać na konkretnym pliku, jeżeli to zrobisz daj znać, który kod działa szybciej.

KOD cpp:     UKRYJ  
#include <memory>

double __fastcall GetTotal(String fName, int column) // column liczone o 0
{
 std::auto_ptr<TStringList> ListCSV(new TStringList);
 std::auto_ptr<TStringList> tempList(new TStringList);
 tempList->LineBreak = ",";
 ListCSV->LoadFromFile(fName);

 double Result = 0;

 for(int i = 0; i < ListCSV->Count - 1; i++)
 {
  String text = ListCSV->Strings[i];
  if(text.Length() < 1) continue;

  tempList->SetText(text.t_str());
  if(tempList->Count <= column) continue;

  double value = tempList->Strings[column].ToDouble();

  Result += value;
  tempList->Clear();
 };

 return Result;
}


Metoda testowania:

KOD cpp:     UKRYJ  
#include <time.h>
#include <stdio.h>

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 clock_t start, end;
 start = clock();
 char Buf[255];

 Caption = (String)GetTotal(fileName, 0);


 end = clock();
 sprintf(Buf, "Czas wykonywania zadania: %f", (end - start) / CLK_TCK);
 ShowMessage((AnsiString)Buf);
}

Za ten post autor Cyfrowy Baron otrzymał podziękowanie od:
Corvis
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    Windows XPFirefox

Re: Szybki parser CSV

Nowy postprzez polymorphism » czwartek, 16 września 2010, 12:28

Dokonałem małej optymalizacji poprzedniego kodu:
KOD cpp:     UKRYJ  
...

typedef num_get<char,char*>     num_get_type;

char cell_text[100];
char* p2 = cell_text + sizeof(cell_text);

const num_get_type &ng = use_facet<num_get_type >(file.getloc());

char c = 0;

while(c != EOF)
{
        c = 0;

        while(c != '\n')
        {
                char* p1 = cell_text;

                while(p1 < p2 &&
                        (*p1 = file.rdbuf()->sbumpc()) != EOF &&
                        *p1 != ',' &&
                        *p1 != '\n') ++p1;
                       
                if(p1 == p2)throw std::overflow_error("cell is too big!");
                c = *p1;
                if(p1 == cell_text)break;

                char *p3 = cell_text;
                while(p3 != p1 && isspace(*p3))++p3;

                ios_base::iostate err = 0;
                ng.get(p3,p1,file,err,v);
                if(err & (ios::failbit | ios::badbit))
                {
                    throw invalid_argument("");
                }

                dane.push_back(v);
        }
}

W wersji release 1'000'000 rekordów po 10 kolumn (76,1MB) czyta w ~7.6 sekundy (2x2.5GHz), czyli całkiem ładnie ;) Co ciekawe wersja z operatorem >> jest o 32% wolniejsza (też korzysta z klasy num_get).
C++ Reference - opis wszystkich klas STL-a i funkcji C.

Za ten post autor polymorphism otrzymał podziękowanie od:
Corvis
Avatar użytkownika
polymorphism
Doświadczony Programista ● Moderator
Doświadczony Programista ● Moderator
 
Posty: 2156
Dołączył(a): piątek, 19 grudnia 2008, 13:04
Podziękował : 0
Otrzymał podziękowań: 200
System operacyjny: Windows 8.1
Windows 10
Linux Mint 21.1
Kompilator: Visual Studio
Visual Studio Code
MSYS2 (MinGW, clang)
g++
clang
Gadu Gadu: 0
    Windows XPFirefox

Następna strona

  • Podobne tematy
    Odpowiedzi
    Wyświetlone
    Ostatni post

Powrót do Ogólne problemy z programowaniem

Kto przegląda forum

Użytkownicy przeglądający ten dział: Brak zalogowanych użytkowników i 10 gości

cron