Forum PCLab.pl: [C] Explode wydarzeń do tablicy - Forum PCLab.pl

Skocz do zawartości

Uwaga! Uwaga! Zarejestruj się i odbierz bonus w Drakensang Online Dodaj obrazek

Otwarty

Ikona Ostatnio dodane tematy

Ikona Najnowsze pliki

Strona 1 z 1
  • Nie możesz rozpocząć nowego tematu
  • Nie możesz odpowiadać w tym temacie

[C] Explode wydarzeń do tablicy Oceń temat: -----

#1 Użytkownik jest niedostępny   zetsu.dono Ikona

  • Dyskutant
  • PipPip
  • Grupa: Forumowicze
  • Postów: 88
  • Dołączył: Cz, 27 Gru 07

Napisany 10 Luty 2012 - 11:05

Mam plik tekstowy w takim formacie:

N#N#N#NNNN#NN#NN#N#C
N#N#N#NNNN#NN#NN#N#C
...

Gdzie N oznacza cyfrę i C oznacza dowolną (no, powiedzmy do 100) liczbę znaków (liczba linii dowolna). Stwierdziłem, że zrobię 2 tablice, jedna przechowująca cyfry, druga znaki (opis). By lepiej zrozumieć, chodzi o wydarzenia (cyfry oznaczają godzinę, datę etc).
Napisałem algorytm, który dzieli je na linie jako osobne wydarzenia, a te z kolei dzieli na informacje (podzielone #):

int i,event=0,inf=0,znak=0;
int data[lines][6];
char data_s[lines][100];
	
	// EXPORT OF EVENTS INTO ARRAYS
	for(i=0; (all[i]=fgetc(fp)) != EOF; ++i)
		{
		if(all[i] != '#' && all[i] != '\n') // When next character of information comes
			{
			if(inf==7) // When information is description
				{
				data_s[event][znak] = all[i];
				}
			else
				{
				if(znak==0) // When first character comes
					{
					data[event][inf] = 0;
					}
				data[event][inf] = data[event][inf]*10+(int)all[i];
				}
			}
		if(all[i] == '#') // When new information starts
			{
			++inf;
			znak=0;
			}
		if(all[i] == '\n') // When new event starts
			{
			++event;
			inf=0;
			znak=0;
			}
		}


Jednak kod nie ma zamiaru działać - wyrzuca ogromne liczby. Uczę się dopiero programować i możliwe, że coś pomijam. Wcześniej wrzucałem wszystko do jednej tablicy trzywymiarowej, w której trzecią wartością był nr znaku informacji, ale potrzebuję wartości int więcej niż jednocyfrowych (jak np. rok), a nie ciągu czterech cyfr.

Proszę o pomoc.
Pozdrawiam

Edit: oczywiście plik tekstowy jest już otwarty

Ten post był edytowany przez zetsu.dono dnia: 10 Luty 2012 - 11:05


#2 Użytkownik jest niedostępny   ..::DAN::.. Ikona

  • Uzależniony od forum
  • PipPipPipPipPip
  • Grupa: Forumowicze
  • Postów: 1343
  • Dołączył: Cz, 26 Mar 09

Napisany 10 Luty 2012 - 12:26

ja bym to zrobił trochę inaczej, odczytywanie whilem dopóki != eof pliku i wczytywanie do jakiejś tymczasowej zmiennej, która byłaby potem jako parametr w funkcji, metodzie, która by parsowała co w tym jest.

P.S. odczytywanie linijka po linijce

Ten post był edytowany przez ..::DAN::.. dnia: 10 Luty 2012 - 12:54


#3 Użytkownik jest niedostępny   zetsu.dono Ikona

  • Dyskutant
  • PipPip
  • Grupa: Forumowicze
  • Postów: 88
  • Dołączył: Cz, 27 Gru 07

Napisany 10 Luty 2012 - 14:42

No dobrze, to też jest sposób, ale nijak nie rozwiązuje problemu działania tej funkcji.

#4 Użytkownik jest niedostępny   Bono[UG] Ikona

  • Wiecznie niewyspany...
  • Ikona
  • Grupa: Moderatorzy
  • Postów: 10157
  • Dołączył: Pt, 27 Wrz 02

Napisany 10 Luty 2012 - 15:32

Co mi się rzuca w oczy, to zerujesz zmienną znak, ale nigdzie nie nadajesz jej innej wartości.
Gdzie Ci te duże liczby wywala?

Jak korzystasz z jakiegoś środowiska, to zapoznaj się z narzędziami do podglądu zmiennych i uruchamianiu programu krok po kroku. Debugowania też trzeba się nauczyć :)

Ten post był edytowany przez Bono[UG] dnia: 10 Luty 2012 - 15:37


#5 Użytkownik jest niedostępny   mejt Ikona

  • Gaduła
  • PipPipPip
  • Grupa: Forumowicze
  • Postów: 175
  • Dołączył: Cz, 16 Wrz 10

Napisany 10 Luty 2012 - 15:45

Zobacz postzetsu.dono, o 10 Luty 2012 - 11:05, napisał(a):

N#N#N#NNNN#NN#NN#N#C

Gdzie N oznacza cyfrę i C oznacza dowolną (no, powiedzmy do 100) liczbę znaków (liczba linii dowolna). Stwierdziłem, że zrobię 2 tablice, jedna przechowująca cyfry, druga znaki (opis). By lepiej zrozumieć, chodzi o wydarzenia (cyfry oznaczają godzinę, datę etc).
Napisałem algorytm, który dzieli je na linie jako osobne wydarzenia, a te z kolei dzieli na informacje (podzielone #):

...

Jednak kod nie ma zamiaru działać - wyrzuca ogromne liczby. Uczę się dopiero programować i możliwe, że coś pomijam. Wcześniej wrzucałem wszystko do jednej tablicy trzywymiarowej, w której trzecią wartością był nr znaku informacji, ale potrzebuję wartości int więcej niż jednocyfrowych (jak np. rok), a nie ciągu czterech cyfr.



Jeżeli dobrze zrozumiałem, to opis po ostatnim znaku # może być wielolinijkowy(wtedy nie da się teog czytać liniami). Chyba że "liczba linii dowolna" oznacza, że każde zdarzenie mieści się w jednej linii i liczba takich własnie linii może być dowolna w obrębie pliku. Wtedy faktycznie lepiej jest czytać plik linia po linii i zrobić "tokenizację" w oparciu o znak #. To parsowanie możesz robić ręcznie, w pętli, tak jak zaproponowałeś, albo użyć funkcji z nagłówka <cstdio> -> http://www.cplusplus.../cstdio/sscanf/

Proponuję Ci też unikać przechowywania danych w dwóch kontenerach/tablicach w oparciu o założenie, że będą miały tę samą ilość elementów i że będą w jakiś tam sposób zsynchronizowane. Jest to do zrobienia, ale powinno się takiego podejścia unikać, bo jest BARDZO podatne na błędy. Dlatego lepiej reprezentować pojedyncze wydarzenie za pomocą obiektu klasy, która będzie to wydarzenie kompleksowo opisywać, np.:

class Wydarzenie {
public:
	Wydarzenie(const std::string& linijka) {
		//wczytaj liczby zgodnie z zadanym formatem
		sscanf(linijka.c_str(), FORMAT_LINII, &godzina, &minuta, &sekunda, &rok, &miesiac, &dzien, &numer);

		//wyszukaj ostatnie wystapienie znaku # w zmiennej linijka -> znajdz metodę ktora wyszukuje od tylu http://www.cplusplus.com/reference/string/string/
		size_t pozycja = ...;

		//skopiuj wszystko od znalezionego znaku do konca(max 100 znakow) -> znajdz metode ktora kopiuje kawalek stringa http://www.cplusplus.com/reference/string/string/
		if(pozycja != std::string::npos) {
			opis = ...;
		}
	}

	int godzina;
	int minuta;
	int sekunda;
	int rok;
	int miesiac;
	int dzien;
	int numer;
	std::string opis;

	static const char* FORMAT_LINII;
	static const char SEPARATOR;
};

const char* Wydarzenie::FORMAT_LINII = ...;
const char Wydarzenie::SEPARATOR = '#';


Musisz tylko odpowiednio zdefiniować FORMAT_LINII, zeby pasował do funkcji sscanf. W funkcji main czy jakiejś osobnej funkcji wczytuj linie pliku do zmiennej typu std::string i na podstawie tego stringa twórz obiekty typu Wydarzenie, które możesz dalej przechowywać w pojedynczej tablicy lub kontenerze, np. std::vector. Spróbuj to rozwiązać w ten sposób, a jak nie rozumiesz czegoś to pisz, postaram się wyjaśnić :)

#6 Użytkownik jest niedostępny   zetsu.dono Ikona

  • Dyskutant
  • PipPip
  • Grupa: Forumowicze
  • Postów: 88
  • Dołączył: Cz, 27 Gru 07

Napisany 10 Luty 2012 - 20:56

Problem jest taki, że muszę użyć ANSI C, nie C++, a w nim nie ma klas :/ Co do układu pliku: jedna linia - jedno wydarzenie, ale ostatnia informacja w linijce (czyli opis wydarzenia) może mieć różną ilość znaków (ale nadal w 1 linii).
A, prawda, nie inkrementuję zmiennej znak, mój błąd. Na końcu pętli dodałem ++znak; ale nadal po wyświetleniu np. data[1][0] wywala mi 2686916.

#7 Użytkownik jest niedostępny   mejt Ikona

  • Gaduła
  • PipPipPip
  • Grupa: Forumowicze
  • Postów: 175
  • Dołączył: Cz, 16 Wrz 10

Napisany 10 Luty 2012 - 22:48

A faktiko, rozpędziłem się i nie zwróciłem uwagi, że jest w temacie [C]. Dziś już mi sie nie chce myśleć, więc jutro może coś pokombinuję. Co do dużych liczb - możesz mieć po porostu śmieci w niezainicjalizowanej tablicy. Zrób na niej memset i ustaw wszędzie zera, albo jakąś określoną wartość, żebyś wiedział, że nic poprawnego się do nich nie zapisało - wtedy wyjdzie, że masz błąd w algorytmie. Chociaż i tak najlepiej by było wpiąć się z debuggerem ;)

#8 Użytkownik jest niedostępny   hot_stuff Ikona

  • Orator
  • PipPipPipPip
  • Grupa: Forumowicze
  • Postów: 515
  • Dołączył: Pn, 12 Cze 06

Napisany 11 Luty 2012 - 00:42

Heh.. przeciez sscanf to funkcja zapozyczona z C i jej skladnia powinna byc identyczna tak jak ta z C a to wlasnie ona stanowi rozwiazanie problemu. Czyli de facto jak najbardziej mozna skorzystac z opisu zamieszczonego w linku ktory podal mejt.

Ps. Zastrzegam, ze nie zaglebialem sie za bardzo w problem a tylko pobieznie zerknalem, wiec moglem cos zle zinterpretowac i napisac.

Ten post był edytowany przez hot_stuff dnia: 11 Luty 2012 - 00:42


#9 Użytkownik jest niedostępny   d2uriel Ikona

  • Orator
  • PipPipPipPip
  • Grupa: Forumowicze
  • Postów: 735
  • Dołączył: Pt, 21 Wrz 07

Napisany 11 Luty 2012 - 09:10

data[event][inf] = data[event][inf]*10+(int)all[i];

Tak jak mejt napisał, wyzeruj całą tablicę najpierw - to pierwsza rzecz. Po drugie, w linijce powyżej nie podoba mi się sama końcówka. Tablica all zawiera cały wczytany plik jak tablica znaków char *, zgadza się? Rzutowanie na (int) w sposób w jaki to robisz spowoduje, że dla liczby 5 otrzymasz 53.

Śpię jeszcze i może czegoś w kodzie nie widzę, ale chyba tutaj właśnie jest błąd. Chyba, że w ANSI C właśnie w taki sposób konwertuje się znak do typu liczbowego.

Ten post był edytowany przez d2uriel dnia: 11 Luty 2012 - 12:31


#10 Użytkownik jest niedostępny   zetsu.dono Ikona

  • Dyskutant
  • PipPip
  • Grupa: Forumowicze
  • Postów: 88
  • Dołączył: Cz, 27 Gru 07

Napisany 11 Luty 2012 - 10:48

Co do zerowania tablicy, robi to na bierząco ten fragment:

if(znak==0) // When first character comes
{
data[event][inf] = 0;
}


Więc w momencie działania na data[event][inf] jest ona równa zero. Przynajmniej w założeniu.
Jeśli chodzi o rzutowanie, przyznam się, że nie wiem jak to powinno być, czy mogę wsadzić do tablicy int liczby typu char i wykonywać na nich operacje matematyczne?

#11 Użytkownik jest niedostępny   d2uriel Ikona

  • Orator
  • PipPipPipPip
  • Grupa: Forumowicze
  • Postów: 735
  • Dołączył: Pt, 21 Wrz 07

Napisany 11 Luty 2012 - 12:30

Owszem, do tablicy typu int możesz wrzucać char'y, ale będzie to wtedy tak, jak napisałem wyżej. W celku konwersji skorzystaj z funkcji sprintf().

#12 Użytkownik jest niedostępny   mejt Ikona

  • Gaduła
  • PipPipPip
  • Grupa: Forumowicze
  • Postów: 175
  • Dołączył: Cz, 16 Wrz 10

Napisany 11 Luty 2012 - 12:52

Ale widzę, że wciąż chcesz robić to znak po znaku. Nie chcesz spróbować parsowania pliku liniami? Możesz wtedy użyć funkcji fscanf i czytać za pomocą odpowiedniego formatu od razu z pliku. Szkielet będzie identyczny, jak konstruktor tej klasy, którą wyżej napisałem. Poza tym do opisu wydarzeń możesz w C uzyć struktur i w zasadzie jedynie pole opis będziesz musiał zrobić na jakimś tekstowym łańcuchu.

#13 Użytkownik jest niedostępny   zetsu.dono Ikona

  • Dyskutant
  • PipPip
  • Grupa: Forumowicze
  • Postów: 88
  • Dołączył: Cz, 27 Gru 07

Napisany 11 Luty 2012 - 14:03

Pewnie dałoby się tak jak piszesz, ale jak pisałem jestem początkujący i nie wiem, jak się do tego zabrać. Ale rozwiązałem problem funkcją atoi(), cały kod:

int nr, i, event=0, znak=0, inf=0, lines=1, choice;
	int a,b;
	char all[5000];
	
	FILE *fp;
	
	// SPRAWDZENIE DOSTĘPU DO PLIKU
    if((fp=fopen("db.txt","r"))==NULL) {
      printf("Cannot open file.\n");
      exit(1);
    }
    
    // LICZENIE ILOŚCI LINII
	while(EOF != (nr=getc(fp)))
	    if (nr=='\n')
	        ++lines;
	
	int data[lines][6];
	char data_s[lines][100];
	char temp[10];
	
	// ZEROWANIE TABLICY 'data' i 'temp'
	for(a=0; a<=lines; ++a)
		{
		for(b=0; b<=6; ++b)
			{
			data[a][b] = 0;
			}
		}
	for(a=0; a<=10; ++a)
		{
		temp[a] = ".";
		}
	a=0; b=0;
	
	// RE-OTWARCIE PLIKU
	fclose(fp);
	if((fp=fopen("db.txt","r"))==NULL) {
      printf("Cannot open file.\n");
      exit(1);
    }
	
	// EXPORT OF EVENTS INTO ARRAYS
	for(i=0; (all[i]=fgetc(fp)) != EOF; ++i)
		{
		if(all[i] == '\n')
			{
			++event;
			inf=0;
			znak=0;
			}
		if(all[i] == '#')
			{
			data[event][inf] = atoi(temp);
			++inf;
			znak=0;
			for(a=0; a<=10; ++a)
				{
				temp[a] = '.';
				}
			a=0;
			}
		if(all[i] != '\n' && all[i] != '#')
			{
			if(inf==7)
				{
				data_s[event][znak] = all[i];
				}
			else
				{
				temp[znak] = all[i];
				}
			++znak;
			}
		}


Pytanie moje jeszcze brzmi, by uprościć kod, da się 'kursor' ustawić znowu na początek pliku, żeby go nie zamykać i otwierać ponownie?

Ten post był edytowany przez zetsu.dono dnia: 11 Luty 2012 - 14:05


#14 Użytkownik jest niedostępny   mejt Ikona

  • Gaduła
  • PipPipPip
  • Grupa: Forumowicze
  • Postów: 175
  • Dołączył: Cz, 16 Wrz 10

Napisany 11 Luty 2012 - 14:25

To możesz zrobić za pomocą funkcji fseek http://www.cplusplus...y/cstdio/fseek/ podając zerowy offset.

#15 Użytkownik jest niedostępny   zetsu.dono Ikona

  • Dyskutant
  • PipPip
  • Grupa: Forumowicze
  • Postów: 88
  • Dołączył: Cz, 27 Gru 07

Napisany 11 Luty 2012 - 16:19

Zaraz stracę cierpliwość, piszę dalej kod, wszystko było ok. Teraz już nie jest. Ten kod powyżej już mi nie działa - analizowałem co się stało - i co? otórz wyświetlam liczbę linii zaraz po pętli, która ją liczy. Wywala 1 (w pliku jest 9), mimo, że wcześniej działało. Dochodzę do wniosku, że może źle otworzony plik. Ale nie widzę błędu. Może ktoś z was zna rozwiązanie tego problemu?

#16 Użytkownik jest niedostępny   d2uriel Ikona

  • Orator
  • PipPipPipPip
  • Grupa: Forumowicze
  • Postów: 735
  • Dołączył: Pt, 21 Wrz 07

Napisany 11 Luty 2012 - 16:28

Hym, może zamiast zwykłego przypisania spróbuj z memcpy()? Czyli zamiast:
data_s[event][znak] = all[i];

zrób:
memcpy(data_s[event][znak], all[i], sizeof(all[i]));

Albo coś w ten deseń. To samo z drugim przypisaniem.

Ten post był edytowany przez d2uriel dnia: 11 Luty 2012 - 16:28


#17 Użytkownik jest niedostępny   Bono[UG] Ikona

  • Wiecznie niewyspany...
  • Ikona
  • Grupa: Moderatorzy
  • Postów: 10157
  • Dołączył: Pt, 27 Wrz 02

Napisany 12 Luty 2012 - 11:57

Zobacz postzetsu.dono, o 10 Luty 2012 - 20:56, napisał(a):

Problem jest taki, że muszę użyć ANSI C, nie C++, a w nim nie ma klas :/ Co do układu pliku: jedna linia - jedno wydarzenie, ale ostatnia informacja w linijce (czyli opis wydarzenia) może mieć różną ilość znaków (ale nadal w 1 linii).

Ale struktury są ;)

Wracając do zliczania linii. Co zrobiłeś między poprawnym działaniem, a złym działaniem programu?
Plik tekstowy utworzony pod linuxem czy windą? Program kompilowany i uruchamiany pod win/linux? Te systemy różnie oznaczają przejście do następnej linii (win 2 znaki, linux 1 znak) i być może w tym problem.
Sprawdź czy pętla się poprawnie wykonuje np. poprzez wypisywanie czytanych znaków.

#18 Użytkownik jest niedostępny   zetsu.dono Ikona

  • Dyskutant
  • PipPip
  • Grupa: Forumowicze
  • Postów: 88
  • Dołączył: Cz, 27 Gru 07

Napisany 12 Luty 2012 - 12:49

Znalazłem błąd, zmieniony tryb dostępu na nadpisywanie o_O ale jestem głupi :E miałem otwarty plik db.txt z zawartością, dlatego myślałem, że coś w nim jest. A tymczasem został on zmieniony, po otwarciu go ponownie - pusto.

Ten post był edytowany przez zetsu.dono dnia: 12 Luty 2012 - 12:56


#19 Użytkownik jest niedostępny   Bono[UG] Ikona

  • Wiecznie niewyspany...
  • Ikona
  • Grupa: Moderatorzy
  • Postów: 10157
  • Dołączył: Pt, 27 Wrz 02

Napisany 12 Luty 2012 - 13:34

To skoro działa, to teraz można zająć się optymalizacją.
1. Po co czytasz plik 2 razy? W momencie zliczania linii od razu zapisuj do tablicy te znaki i zliczaj ich ilość. Potem przeiterujesz po tablicy zamiast znowu czytać plik.

2. Ify sprawdzające jaki jest znak chyba lepiej zamienić na switcha. Według mnie będzie czytelniej. Ewentualnie jak chcesz powalczyć o wydajność, to dodaj else przy tych ifach. Obecnie jak sprawdzisz, czy jest "\n", to przy spełnieniu warunku i tak sprawdzasz kolejne, które nie mają prawa wystąpić - strata czasu.

3. Struktury zamiast 2 tablic.

#20 Użytkownik jest niedostępny   mejt Ikona

  • Gaduła
  • PipPipPip
  • Grupa: Forumowicze
  • Postów: 175
  • Dołączył: Cz, 16 Wrz 10

Napisany 12 Luty 2012 - 14:09

Te struktury koniecznie wprowadź, trzymanie czegoś w 2 tablicach które w założeniu są zsynchronizowane to BARDZO ALE TO BARDZO zły wzorzec.

Strona 1 z 1
  • Nie możesz rozpocząć nowego tematu
  • Nie możesz odpowiadać w tym temacie

1 Użytkowników czyta ten temat
0 użytkowników, 1 gości, 0 anonimowych