Odczytywanie z pliku, separator i tablice

0

Witam! Piszę prosty program, w którym potrzebuję opanować obsługę plików. Mam taki plik dane.txt:

1;JanKowalski;3c
2;JanNowak;2d
3;AnnaNowak;4c

(danych będzie więcej)

I z tego potrzebuję zrobić tak, żeby w jakiejś pętli brało jedną linię tekstu i czytało po kolei do tablicy przykładowo ID[x], do momentu aż napotka średnik (;), wtedy kończy wpisywanie do tej tablicy i dane po średniki wpisuje do tablicy ImieNazwisko[x], znowu przerywa jak będzie średnik i wpisuje do tablicy klasa[x] i tak z każdą linijką po kolei, aż skończą się linię w pliku. Ważne tutaj jest, żeby numery tablicy, w które program wpisuje dane były takie samo jak id w pliku czyli np. dane Anny Nowak były w tablicach o id 3, czyli zamiast x powinno być 3. Nie wiem jak to rozgryźć, męczę się z tym cały dzień. Ktoś byłby w stanie pomóc?
Byłem na etapie, kiedy miałem kod:

int Id[3];
string ImieNazwisko[3];
string klasa[3];

plik>>Id[0]>>ImieNazwisko[0]>>klasa[0];
cout<<Id[0]<<"\t"<<ImieNazwisko[0]<<"\t"<<klasa[0]<<endl;

i działało to tak, że brało tylko pierwszą linię za pomocą getline() i faktycznie wpisywało w miejsca 0 w tych tablicach, ale średnik brało jako domyślny separator, a to właśnie chciałem zmienić, żebym mógł sobie ustalić, że raz to może być w jednej linii średnik a raz np. % czy coś innego. Trudno to wyjaśnić, ale mam nadzieję, że rozumiecie. Potrzebuję na końcu wyświetlić wszystkie te dane, ale to nie problem już będzie, jak wszystkie te dane będą po kolei ładnie posegregowane w tablicach.
Podsumowując: Jak ustawić swój separator, po którym przestanie wpisywać do jednej zmiennej/tablicy dane i zacznie do drugiej po średniku, aż napotka kolejny, a po enterze (czyli w nowej linii) znowu zacznie do pierwszej zmiennej i tak w kółko, aż skończą się dane?
Ważne! Bardzo proszę o pomoc

3
plik >> id[0];
plik.get();
std::getline(plik, ImieNazwisko[0], ';');
std::gelinie(plik, klasa[0]);
0

A jak wybrać swój separator?

0

Nie bardzo rozumiem o co ci chodzi z tym separatorem. Możesz to dokładniej opisać?
Teraz separator jest na stałe w plik, więc nie wiem po co go zmieniać.
Zresztą nie jest pomijany przy pobieraniu danych z pliku, ale działa jak terminator.

3

A nie chodzi przypadkiem o coś takiego:

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

struct student
{
    int id;
    string name_surname;
    string classroom;
};

vector<string> split(const string& phrase, char separator)
{
    vector<string> result;

    stringstream ss(phrase);
    string token;
    while (getline(ss, token, separator))
        result.push_back(token);

    return result;
}

int main()
{
    vector<student> students;
    fstream file("test.txt", ios::in);
    if(file.is_open())
    {
        string line = "";
        while(getline(file,line))
        {
            vector<string> splited = split(line,';');
            student pupil;
            pupil.id = stoi(splited[0]);
            pupil.name_surname = splited[1];
            pupil.classroom = splited[2];

            students.push_back(pupil);
        }
        file.close();
    }

    for(size_t i=0;i<students.size();++i)
        cout << students[i].id << " " << students[i].name_surname << " " << students[i].classroom << "\n";
    return 0;
}

Jeżeli kod jest sprzed C++11 to lepsza będzie taka funkcja:

void split(const string& phrase, vector<string>& result, char separator)
{
    stringstream ss(phrase);
    string token;
    while (getline(ss, token, separator))
        result.push_back(token);
}

Wtedy wartość nie zostanie skopiowana przy zwracaniu, a przekazana referencją:
https://stackoverflow.com/questions/22655059/why-it-is-ok-to-return-vector-from-function

@carlosmay std::getline(plik, ImieNazwisko[0], ';'); - trochę nie najlepszy pomysł, bo zmieniasz z automatu znak końca linii w efekcie czego ostatni split będzie wyglądać np. tak: 2d\n.

PS: XXI wiek i żebym musiał do C++ splita szukać po internetach to naprawdę granda. Standardy rozwijają z kosmosu, a splita się jeszcze nie dorobili w std.

0

Oj, chyba zapomniałem dodać, że jestem początkujący i takich vektorów i innych formułek jeszcze nie miałem :/
Pomińmy ten separator, uznajmy, że zostanie ten średnik cały czas. To co napisałeś @carlosmay działa dokładnie tak jak napisałem swoje, jest tylko inaczej napisane, w sumie ładniej :)
Problem w tym właśnie, że nie działa dla kolejnych linijek tekstu, tylko dla pierwszej. Dodatkowo nie wiem jak zrobić, żeby w miejsce tablicy wstawić taką zmienną, która będzie odpowiadała numerowi w dane.txt, czyli to co tłumaczyłem: dane Anny Nowak z ID 3 były w tablicach na miejscu [3].

//Edit: Jak napiszę kolejne linie tekstu to działa dla kolejnych linii, np.:

	plik >> Id[1];
	plik.get();
	getline(plik, ImieNazwisko[1], ';');
	getline(plik, klasa[1], '\n');

i dodałem "\n" o którym mówiliście, choć nie wiem czy to konieczne. Wyświetliłem tablice z miejscami [0] jak i [1] i wszystko wpisuje. Teraz tylko problem, żeby zrobić tak, że wpisuje to w pętli a na miejsca [x], gdzie x to Id.

0

Dla takich tablic jakie stosujesz musisz z góry wiedzieć ile jest linii tekstu i zapisać ten rozmiar w kodzie.
Najwygodniej będzie wczytać całą linie i analizować stringsteamem.
Problem wczytywania kolejnych linii to zastosowanie pętli, której licznik będzie jednocześnie indeksem tablic.

for(int i = 0; i < nLines; ++i) { // gdzie nLines jest stałą wartością w kodzie
    plik >> Id[i];
    plik.get();
    getline(plik, ImieNazwisko[i], ';');
    getline(plik, klasa[i], '\n');
}

Coś w tym stylu.

0

Krótko możesz mi opisać co robi to stringstream?
No dokładnie o taką funkcję mi chodziło, tylko jak zamiast nLines sprawdzić, jaka linia jest ostatnia? Coś z length nie da się pokombinować?

0

Jeśli masz zamiar pracować na zwykłych tablicach, to ilość linii musi być z góry ustalona.
Jeśli tego nie wiadomo, to zostało ci rozwiązanie z vectorem (dynamiczną tablicą).
Tutaj nie musisz wiedzieć ile jest linii tekstu. Wczytujesz póki coś jest w pliku.

0

Dzięki za informację. Program właśnie sam prosi, żeby podać rozmiar tablicy, nie może być puste.. :( W takim razie co z tym zrobić?

1

Zostaje ci vector i analiza pobranej linii stringstreamem.
Najlepiej wrzuć kod jaki już masz.

@grzesiek51114

plik >> id[0]; // pobranie liczby
plik.get(); // pobranie średnika
std::getline(plik, ImieNazwisko[0], ';'); // wczytanie łańcuch do średnika
std::gelinie(plik, klasa[0]); // wczytanie łańcucha do końca linii
0

To mój kod:


{
	ifstream plik;
	CzyPlikIstnieje(); //sprawdza tylko czy plik istnieje, nic wiecej
	plik.open("dane.txt", ios::in);
	
	int Id[100];
	string ImieNazwisko[100];
	string klasa[100];
	
	plik >> Id[0];
	plik.get();
	getline(plik, ImieNazwisko[0], ';');
	getline(plik, klasa[0], '\n');
	
	plik >> Id[1];
	plik.get();
	getline(plik, ImieNazwisko[1], ';');
	getline(plik, klasa[1], '\n');
	
	
	cout<<Id[0]<<"\t"<<ImieNazwisko[0]<<"\t"<<klasa[0]<<endl;
	cout<<Id[1]<<"\t"<<ImieNazwisko[1]<<"\t"<<klasa[1]<<endl;
	
	
	plik.close();
	
	
}

Podasz przykład jak miałbym zrobić tutaj ten vektor i te stringstream, cokolwiek to robi?

1
CzyPlikIstnieje(); //sprawdza tylko czy plik istnieje, nic wiecej

Jeśli ta funkcja coś sprawdza, powinna zwracać informację o stanie.
np. bool CzyPlikIstnieje(){} i wykorzystać to w kodzie

if(CzyPlikIstnieje()) {
    // robimy coś z plikiem 
} 
0

Ale wracając do programu, właściwie działa tak jak chciałem, wpisuje co ma wpisywać w odpowiednie indeksy tablicy i wyświetla. Problem mam z tą ilością linii, bo właśnie plik ma działać tak, że ilość danych będzie zmieniana, więc i ilość linii się zmieni, ale to już jutro spróbuję jakoś rozwiązać :P Pięknie dziękuję za pomoc panowie!

0

Ja odnośnie nadal tego pliku. Mam tym razem problem z usuwaniem danych z pliku tekstowego. Mam działającą funkcję, która otwiera plik i wczytuje dane do tablic pod odpowiednie miejsca. Potrzebuję jednak teraz zrobić taką funkcję, która będzie usuwać dane z pliku o podanym ID ucznia (czyli tym samym numerze linii, ponieważ się pokrywają). Będę musiał jeszcze potem napisać funkcję, w której będę mógł dodawać uczniów, ale to się odezwę potem najwyżej. Chcę, aby użytkownik wprowadził ID osoby, którą chce usunąć i program tą osobę usunął, czyli np. jeżeli użytkownik będzie chciał usunąć osobę o ID 4 to program usunie 4 linię w pliku dane.txt, tym samym usuwając wczytane dane do tablic o miejscu 4 czyli Imie[4], ID[4], Nazwisko[4], klasa[4]. Nie mam nawet pojęcia jak się za to zabrać, komend za bardzo też nie znam :( Proszę jeszcze raz o pomoc!

0

Kasuj zawartość pliku i zapisuj zmienioną całość. Gdyby dane były równej długości, można by obliczać przesunięcie wskaźnika plikowego na odpowiednią pozycję,
jednak w tym przypadku, gdzie używasz std::string (mają różną długość), nie da się w prosty sposób obliczyć tych przesunięć. Będzie to nieopłacalne.

0

Nie mogę kasować danych w pliku, chcę żeby dopisało kolejnego ucznia. A masz może pomysł jak zrobić, żeby po podaniu przed użytkownika ID (które np. nie będzie pokrywać się z miejscem tablicy przykładowo uczeń o ID 4, w tablicy pod miejsce [7]), usuwało tego, kto ma 7 w tablicy ID[]? No i chodzi mi też ogólnie o samą komendę, którą usunę konkretnie podaną linię, albo tę wczytaną właśnie przez getline czy coś podobnego.

0
Nie mogę kasować danych w pliku, chcę żeby dopisało kolejnego ucznia.
1. Wczytujesz zawartość pliku do kontenra. 2. Dodajesz, usuwasz lub modyfikujesz dane ucznia (kontener). 3. Zapisujesz kontener do pliku.

1 użytkowników online, w tym zalogowanych: 0, gości: 1