Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[C++] Odczyt listy jednokierunkowej z pliku do struktury

Ostatnio zmodyfikowano 2016-12-30 13:46
Autor Wiadomość
labamba63
Temat założony przez niniejszego użytkownika
[C++] Odczyt listy jednokierunkowej z pliku do struktury
» 2016-12-29 14:31:03
Cześć, dopiero zaczynam przygodę z programowaniem - zabrałem się za pewien projekt, z którym już walczę kilka dni ;) Chciałbym prosić o pomoc w rozwiązaniu problemu.

Program w pierwszym etapie generuje pliki wynikowe za każdy miesiąc, nastepujące pliki wyglądają w takiej formie:

Plik: styczen.txt
Zestawienie za styczen
01-01-2016
4095, Kowalski, 160, wynagrodzenie
500, Tarnowski, 12, usluga
2590, Nowak, 120, wynagrodzenie

Plik: luty.txt
Zestawienie za luty
01-02-2016
3800, Kowalski, 150, wynagrodzenie
500, Tarnowski, 10, usluga
2990, Nowak, 131, wynagrodzenie

Struktura miała postać:
int kwota;
string nazwisko;
int czas_pracy;
string rodzaj;

Plik ten został wygenerowany przy pomocy listy jednokierunkowej, z dodaniem na początku pliku informacji:
w 1 wierszu: Za jaki miesiąc jest to zestawienie
w 2 wierszu: Data

Teraz muszę te pliki odczytać i wpisać z niego kilka informacji do struktury, chciałbym aby program z pliku utworzył mi nowe zestawienia dla poszczególnych pracownikow czyli na przykład powstanie coś takiego:

Plik: Kowalski.txt

01-01-2016 Zestawienie za styczen 4095
01-02-2016 Zestawienie za luty 3800

Z wygenerowaniem do pliku sobie poradzę tylko jak wypełnić tą strukturę danymi z pliku.
Zatrzymałem się już na samym początku problemu, ponieważ o ile łatwo jest odczytać plik, gdy każdą daną mamy w innym wierszu to jak sobie poradzić z czymś, gdzie dane są wypisane w jednym wierszu i oddzielone przecinkami ze spacją, jak pomijać przecinki...

Póki co tak wygląda początek mojego kodu:
C/C++
#include <iostream>
#include <string>
#include <fstream>

using namespace std;

struct pracownik
{
    string data;
    string zestawienie;
    int kwota;
    pracownik * next;
};

int main()
{
    fstream plik;
    plik.open( "styczen.txt", ios::in );
   
    if( plik.good() == false )
    {
        cout << "Wystapil problem z plikiem\n";
        system( "pause" );
        return 1;
    }
   
    string linia;
    int nr_linii = 1;
   
    string zestawienie, data;
   
    while( getline( plik, linia ) )
    {
        switch( nr_linii )
        {
        case 1: zestawienie = linia; break;
        case 2: data = linia; break;
        case 3: // ?? Co tutaj wpisac
        }
       
        nr_linii++;
    }

Po case 3 wydaje mi się, że przydałaby się jakaś pętla, która wracałaby do nr_linii 2 a później ją inkrementowała znów do 3, przy czym wartości w case 3 powinny się zmieniać wraz z przejściem pętli.
P-155643
michal11
» 2016-12-29 16:35:47
Nie czytałem kodu, ale najprościej będzie pewnie wczytywać wyraz z przecinkiem i jeżeli ostatni znak stringa to właśnie przecinek to go usuwać. Możesz też pobawić się getline i wczytywać całą linijkę i odpowiednio ją parsować ale tutaj moim zdaniem za dużo roboty by było.

Edit.

Ok, widzę, że wczytujesz linię getlinem, w takim razie możesz skorzystać ze stringstream aby odpowiednio ją sparsować, mniej więcej tak
C/C++
int kwota;
std::string nazwisko;
int czas_pracy;
std::string rodzaj;
char comma;

std::istringstream iss( linia );
iss >> kwota >> comma >> nazwisko >> czas_pracy >> comma >> rodzaj;
nazwisko.erase( nazwisko.length() - 1 );

albo od razu przerabiać linię na twoją strukturę lub zwrócić interesująca cię wartość.

C/C++
int ParseLine( std: string line )
{
    int kwota;
    std::string nazwisko;
    int czas_pracy;
    std::string rodzaj;
    char comma;
   
    std::istringstream iss( linia );
    iss >> kwota >> comma >> nazwisko >> czas_pracy >> comma >> rodzaj;
    nazwisko.erase( nazwisko.length() - 1 );
   
    return kwota; // bo tylko to cię interesuje z pliku
}

swoją drogą takie tworzenie listy
C/C++
struct pracownik
{
        //
        pracownik * next;
};

jest moim zdaniem niepotrzebne i błędogenne, jeżeli już chcesz to robić ręcznie to skorzystaj ze smart pointerów a najlepiej w ogóle z jakiegoś standardowego kontenera std::list<> lub std::vector ( w twoim przypadku pewnie vector).

P-155653
labamba63
Temat założony przez niniejszego użytkownika
» 2016-12-29 16:59:40
Dzięki, z przecinkiem poradziłem sobie w ten sposób:

C/C++
fstream plik;
plik.open( "styczen.txt", ios::in );

if( plik.good() == false )
{
    cout << "Wystapil problem z plikiem\n";
    system( "pause" );
    return 1;
}
string linia;
int licznik = 0;

while( !plik.eof() )
{
    getline( plik, linia, ',' );
    cout << linia << endl;
    licznik++;
}

zmieniłem nieco pętlę while teraz już zamiast getline sprawdza po prostu czy plik się nie skończył, getline jest wrzucony do pętli i ma warunek zapisywania do napotkania przecinka: "getline(plik, linia, ',')"

cout dodałem testowo, żeby zobaczyć jak są wychwytywane zmienne, po odpaleniu programu wygląda to tak, że plik, który ma formę:
Zestawienie za styczen
01-01-2016
4095, Kowalski, 160, wynagrodzenie
500, Tarnowski, 12, usluga
2590, Nowak, 120, wynagrodzenie

Został przedstawiony w takiej formie:
Zestawienie za styczen
01-01-2016
4095
 Kowalski
 160
 wynagrodzenie
500
 Tarnowski
 12
 usluga
2590
 Nowak
 120
 wynagrodzenie

Mianowicie zostały jeszcze spacje po przecinkach, których program nie usunął i nie wiem jak się tego pozbyć za bardzo. W pierwszym wierszu mam zmienną, która ma spacje i tam nie chciałbym ich kasować, natomiast wszystkie występujące po przecinkach są mi zbędne jakieś pomysły? ;) I jak mogę to teraz wpisać w strukturę.

Edit: Takie tworzenie listy nie przypadkowe, w projekcie nie mogę używać kontenerów bibliotecznych czyli właśnie na przykład vector.
P-155655
michal11
» 2016-12-29 18:28:30
Miałeś dobry kod to go popsułeś, napisałem ci, że możesz kombinować z getline ale to jest za dużo zabawy z tymi spacjami i przecinkami. Napisałem ci coś takiego
C/C++
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>

//====================================================================================================================

struct Pracownik
{
    std::string data;
    std::string zestawienie;
    int kwota;
    Pracownik * next;
};

struct FileData
{
    int Amount;
    std::string Name;
    int WorkTime;
    std::string Description;
};

FileData ParseFileLine( const std::string & line )
{
    int kwota;
    std::string nazwisko;
    int czas_pracy;
    std::string rodzaj;
    char comma;
   
    std::istringstream iss( line );
    iss >> kwota >> comma >> nazwisko >> czas_pracy >> comma >> rodzaj;
    nazwisko.erase( nazwisko.length() - 1 );
   
    return FileData { kwota, nazwisko, czas_pracy, rodzaj };
}

std::ostream & operator <<( std::ostream & out, const FileData & data )
{
    out << data.Amount << "\t" << data.Name << "\t" << data.WorkTime << "\t" << data.Description;
    return out;
}

//====================================================================================================================
int main()
{
    using std::cout;
    using std::endl;
    using std::cin;
   
    std::fstream plik;
    plik.open( "styczen.txt", std::ios::in );
   
    if( !plik.good() )
    {
        cout << "Wystapil problem z plikiem\n";
        system( "pause" );
        return 1;
    }
   
    std::string zestawienie;
    std::getline( plik, zestawienie );
   
    std::string data;
    std::getline( plik, data );
   
    std::string line;
    line.reserve( 150 );
   
    while( std::getline( plik, line ) )
    {
        FileData PracownikData = ParseFileLine( line );
        Pracownik nowy { data, zestawienie, PracownikData.Amount };
    }
   
   
    return 0;
}

nazwy zmiennych są pomieszane bo częściowo pisałem sam a częściowo kopiowałem z poprzednich postów. Myślę, że to jest dobra baza do dalszej rozbudowy twojego programu.
P-155657
mokrowski
» 2016-12-29 20:42:20
Zainteresuj się istringstream. Możesz wczytać string do istringstream i operatorem>> wyłuskiwać dane do określonych typów.

Wiem że jak sam się uczyłem, to przydawały się takie przykłady. Pisałem maksymalnie łopatologicznie i z premedytacją nie rozwiązywałem wszystkich problemów...
C/C++
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <cstdlib>

using namespace std;

using rekord_pracownika_t = struct {
    int kwota; // Chyba lepiej unsigned. Masz zamiar wpisywać z minusem pożyczki/zaliczki? :-)
    string nazwisko;
    int czas_pracy; // To samo.. ujemny? Hmm...
    string rodzaj;
};

using rekordy_z_pliku_t = struct {
    string nazwa;
    string data; // Także kontrowersyjne bo lepiej przechowywać jako czas a nie string...
    vector < rekord_pracownika_t > dane; // Tu kontener wszystkich pracowników z danego zestawienia
};

// Tu masz przykład polimorfizmu. Funkcje nazywają się tak samo a kompilator wybierze odpowiednią
// funkcję w zależności od typu 2-giego argumentu i odpowiednio go wczyta.
void czytaj_dane( istringstream & wejscie, string & dane );
void czytaj_dane( istringstream & wejscie, int & dane );

rekordy_z_pliku_t czytaj_rekord( ifstream & plik );
void wyswietl_rekordy( const rekordy_z_pliku_t & rekordy );

int main() {
    string pliki_miesiecy[] = { "styczen.txt", "luty.txt" };
    rekordy_z_pliku_t rekordy;
   
    for( const auto & plik_nazwa: pliki_miesiecy ) {
        // Po opuszczeniu zasięgu, następuje zniszczenie obiektu plik. ifstream w trakcie niszczenia
        // zamyka poprawnie plik.
        auto plik = ifstream( plik_nazwa );
        // Nie trzeba pytać .good(). Plik ma operator bool. Na jedno wychodzi...
        if( not plik ) {
            cerr << "Problem z dostępem do pliku " << plik_nazwa << ".\n";
            exit( EXIT_FAILURE );
        }
        rekordy = czytaj_rekord( plik );
        wyswietl_rekordy( rekordy );
    }
   
    return EXIT_SUCCESS;
}

void czytaj_dane( istringstream & wejscie, string & dane ) {
    getline( wejscie, dane, ',' );
}

void czytaj_dane( istringstream & wejscie, int & dane ) {
    string dane_str;
    getline( wejscie, dane_str, ',' );
    // Jeśli stoi się nie powiedzie, rzuca wyjątkiem. Tu go nie przechwytuję.
    // Lepiej żeby aplikacja się załamała.
    dane = stoi( dane_str );
}

rekordy_z_pliku_t czytaj_rekord( ifstream & plik ) {
    rekordy_z_pliku_t rekordy_plik;
    string linia;
    istringstream sstream;
    getline( plik, rekordy_plik.nazwa );
    getline( plik, rekordy_plik.data );
   
    while( getline( plik, linia ) ) {
        sstream.str( linia );
        rekord_pracownika_t rekord;
        czytaj_dane( sstream, rekord.kwota );
        czytaj_dane( sstream, rekord.nazwisko );
        czytaj_dane( sstream, rekord.czas_pracy );
        czytaj_dane( sstream, rekord.rodzaj );
        sstream.clear(); // Nie będziemy nic więcej czytali. Po ostatnim rekordzie może być jeszcze
        // jakiś "śmietnik".
        rekordy_plik.dane.push_back( rekord );
    }
   
    return rekordy_plik;
}

void wyswietl_rekordy( const rekordy_z_pliku_t & rekordy ) {
    // Tu sprawdzę co przeczytałem :-)
    cout << "Rekordy z pliku " << plik_nazwa
    << "\n" << string( 40, '=' )
    << "\nNazwa: " << rekordy.nazwa
    << "\nData:  " << rekordy.data
    << "\nDane z rekordów:\n";
    for( const auto & pracownik: rekordy.dane ) {
        cout << " Nazwisko:    " << pracownik.nazwisko
        << "\n Kwota:      " << pracownik.kwota
        << "\n Czas pracy: " << pracownik.czas_pracy
        << "\n Rodzaj:     " << pracownik.rodzaj << endl;
        cout << string( 40, '-' ) << endl;
    }
}
P-155661
labamba63
Temat założony przez niniejszego użytkownika
» 2016-12-30 00:43:41
6 godzinek pracy i coś tam się udało napisać ;)

Obecnie mam problem z posortowaniem - macie jakieś pomysły, tylko proszę o jak najprostsze podstawowe rozwiązania, gdyż dopiero raczkuję i np. istringstream podany wyżej był mi nie znany, ale poradziłem sobie jakoś na podstawowych komendach.
Póki co program odczytuje zadane zestawienia za kolejne miesiące, tworzy nowy plik, króry ma następującą postać:

Plik: Kowalski.txt

Kowalski - zestawienie

2016-01-01 Zestawienie za styczen 4095
2016-03-01 Zestawienie za marzec 2044
2016-02-01 Zestawienie za luty 3800
2015-06-01 Zestawienie za czerwiec 1800

Teraz ten plik chciałbym posortować według daty - tj. otrzymać taką postać w zawartości:

Kowalski - zestawienie

2015-06-01 Zestawienie za czerwiec 1800
2016-01-01 Zestawienie za styczen 4095
2016-02-10 Zestawienie za luty 3800
2016-03-01 Zestawienie za marzec 2044

Teraz pytanie w jaki sposób mogę posortować takiego stringa z datą?
Int'y można by posortować za pomocą utworzenia jakiejś pomocniczej zmiennej i porównywania z poprzednią, ale stringi jak się sortuje zmienne tego typu?
Uprzedzając pytanie dlaczego date mam w stringu - W projekcie było wymagane, aby data została zapisana w formacie RRRR-MM-DD, taka forma przyszła mi na myśl żeby zastosować stringa.

P-155677
michal11
» 2016-12-30 01:34:44
Masz szczęście, że data jest w takim właśnie formacie, wystarczy zwykły operator< dla stringa.

C/C++
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
//====================================================================================================================

struct SomeStruct
{
    std::string Data;
    int sth;
    float sth2;
   
    SomeStruct( const char * str )
        : Data( str )
    { }
};

std::ostream & operator <<( std::ostream & out, const std::vector < SomeStruct >& dates )
{
    for( const SomeStruct & date: dates )
    {
        out << date.Data << "\n";
    }
    return out;
}


//====================================================================================================================

int main()
{
    using std::cout;
    using std::endl;
    using std::cin;
   
    std::vector < SomeStruct > dates { "2016-01-01", "2016-02-01", "2016-05-01", "2016-09-01", "2016-11-01", "2016-07-01", "2015-06-01" };
   
    std::random_shuffle( dates.begin(), dates.end() );
   
    cout << dates << endl;
    cout << "sorting" << "\n\n";
    std::sort( dates.begin(), dates.end(),[]( const SomeStruct & lhs, const SomeStruct & rhs ) { return lhs.Data < rhs.Data; } );
   
    cout << dates << endl;
   
    return 0;
}

Możesz też od razu napisać sobie operator porównania dla swojej struktury i nie nie bawić się w lambdy
C/C++
struct SomeStruct
{
    std::string Data;
    int sth;
    float sth2;
};

bool operator <( const SomeStruct & lhs, const SomeStruct & rhs )
{
    return lhs.Data < rhs.Data;
}

//...

std::sort( dates.begin(), dates.end() );

jeżeli chcesz skorzystać z sort to pewnie będziesz też musiał napisać swoje iteratory ale to nie jest konieczne, możesz użyć swojej funkcji, ważne jest, że możesz to sobie posortować używając stringowego operatora. Aha i sortuj dane przed zapisaniem do pliku.

@mokrowski

Mam pytanie, dlaczego napisałeś coś takiego
using rekord_pracownika_t = struct
 a nie zwyczajnie
struct rekord_pracownika
 ?
P-155679
mokrowski
» 2016-12-30 13:04:59
@michal11 wolę napisać using aby zdefiniować alias typu niż wiele razy w trakcie użycia wpisywać słowo kluczowe struct. W tym przypadku zamiast using mógłbym użyć typedef ale using jest nowocześniejsze i poddaje się łatwemu szablonowaniu.

@labamba63 jeśli chcesz sortować, potrzebujesz iteratora o dostępnie swobodnym. Lista tego nie ma a vector ma :-) Tu masz przykład....
C/C++
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>

using namespace std;

bool sort_predykat( vector < string >& v1, vector < string >& v2 ) {
    // Sortujemy wg. 1 pola...
    return v1[ 0 ] > v2[ 0 ];
}

int main() {
    // Będzie linearnie i na piechotę oraz bez C++11
    string dane_str = "140 Ala kot\n250 Franek pies\n250 Zenek chomik\n10 Agata patyczak";
    string linia;
    istringstream sstream;
    sstream.str( dane_str );
    vector < vector < string >> rekordy( 4, vector < string >( 3 ) );;
   
    for( size_t i = 0; i < rekordy.size(); ++i ) {
        vector < string > & wiersz = rekordy[ i ];
        getline( sstream, linia );
        istringstream linia_strumien;
        linia_strumien.str( linia );
        linia_strumien >> wiersz[ 0 ];
        linia_strumien >> wiersz[ 1 ];
        linia_strumien >> wiersz[ 2 ];
    }
    // Teraz sortowanie..
    sort( rekordy.begin(), rekordy.end(), sort_predykat );
   
    for( size_t i = 0; i < rekordy.size(); ++i ) {
        vector < string >& vec = rekordy[ i ];
        cout << vec[ 1 ] << " ma zwierzątko o nazwie " << vec[ 2 ] << " które kosztuje " << vec[ 0 ] << "PLN.\n";
    }
}
P-155688
« 1 » 2
  Strona 1 z 2 Następna strona