Programování 2

K vstupnímu testu

Několik poznámek k Vaším pracem, které jste mi během posledního týdne poslali. Nejprve vzorové postačující řešení:

#include <stdio.h>
#include <math.h>
#include <locale.h> // kvůli češtině

int main(int argc, char **argv)
{
	setlocale(LC_ALL,"UTF-8"); // počeštění
	double a,b,c,d;

	// nebudeme resit vstupy;

	a=0;
	b=26;
	c=4;
	if (a==0) {
		if (b==0) {
			if (c==0) {
				printf("Toto není rovnice, je to rovnost\n");
				return 0;  // nema smysl pokračovat ve výpočtu
			} else {
				printf("Toto není rovnice, je to nerovnost\n");
				return 0;
			}
		} else {
			printf("Lineární rovnice: x=%f\n",(-c/b));
			return 0;
		}
	} else {
		d=b*b-4*a*c; // teprve tady má smysl něco počítat
		if (d>=0) {
			printf("Pravý kořen: %f\n",((-b+sqrt(d))/2/a)); // i tak lze dělit
			printf("Levý kořen: %f\n",((-b-sqrt(d))/2/a));
			return 0;
		}
	}
	printf("Nemá řešení v oboru reálných čísel\n"); // je to divné, ale až sem se v jiné situaci nedostanete
	return -1; // je to vlastně chybový výstup

}

Sledujte další instrukce. Na Facebooku můžeme diskutovat, paralelně poběží pracovní chat (asi phpfreechat nebo tox, to se ještě uvidí) a samozřejmě videa.

Test druhý a prakticky závěrečný

psáno předtím, než jsem četl vaše práce

Dovolil jsem si drobnou provokaci: neuvedl jsem, jaká data máte zpracovávat a ani jsem se neodkazoval na vaše předchozí schopnosti a znalosti. V podstatě jsem chtěl především otestovat vaši invenci a schopnost improvizace. Následuje vzorové řešení:

Využil jsem své rozsáhlé hudební sbírky, vyseparoval jsem metadata z nahrávek a sestavil soubor v podobě sady záznamů o šesti položkách:

Title;Artist;Album;Year;Length;Genre
...In The Doghouse;Dog Eat Dog;All Boro Kings;1994;349;Pop
(Everything I Do) I Do It For You;Bryan Adams;The Best Of Me;1999;394;Rock
(I Just) Died in Your Arms;Cutting Crew;The Best of Cutting Crew;2005;264;Pop
(I'll Never Be) Maria Magdalena;Sandra;Lost and Found: 1979-1987;1985;234;Pop
(Let Me Be Your) Teddy Bear;Elvis Presley;Elv1s 30 #1 Hits;2002;109;Rock & Roll
(Now And Then There's) A Fool;Elvis Presley;Elv1s 30 #1 Hits;2002;162;Rock & Roll
(Tag);Frankie Goes To Hollywood;Welcome to the Pleasuredome;1984;35;New Wave
(White Man) In Hammersmith Pal;Clash;The Story Of The Clash - Volum;1988;242;Rock

a tak dále a tak podobně. Mihli jste vzít v podstatě libovolná CSV data nebo excelovou tabulku do CSV exportovanou. Zde je právě kvůli Excelu a jeho omezenosti jako separátor použit středník, tomu odpovídá i řešení.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

typedef struct mp3 {
	char nazev[255];
	char umelec[255];
	char album[255];
	unsigned int rok;
	unsigned int delka;
	char zanr[50];
	struct mp3 *dalsi;
} MP3;

Takto je definovaná centrální struktura. Všimněte si, že je jen kopií záznamu v CSV plus odkaz zdánlivě na sebe samou, ve skutečnosti prostě prázdný ukazatel.

FILE * vstup; FILE * vystup; char * radek; size_t delka;
char * token;
int i;
MP3 * jeden, * prvni, * zobraz;

int main(int argc, char **argv)
{
	size_t delkaradku=0;
	setlocale(LC_ALL,"cs_CZ.UTF-8");
	vstup=fopen("cviceni.csv","r");
	fseek(vstup,0L,SEEK_END);
	delka=ftell(vstup);
	fseek(vstup,0L,SEEK_SET);
	if ((radek=(char *) malloc(delka))==NULL) { printf("Malo pameti\n"); return -1; }

Proč alokovat tolik: je to krajní varianta, kdy je v souboru jediný řádek.

	if ((prvni=(MP3 *) malloc(sizeof(MP3)))==NULL) { printf("Malo pameti\n"); return -1; }
	jeden=prvni; // toto je identita, ne přiřazení!
	while (delkaradku<2) {
		if (fgets(radek,delka,vstup)==NULL) return -1;
		delkaradku=strlen(radek);
	}

Trochu komplikované, ale účinné: požere prázdné řádky na začátku souboru (pokud existují) a nespustí vlastní načítání dřív, než najde prcní validní data.

	if (radek[strlen(radek)-1]=='\n') radek[strlen(radek)-1]='\0'; // vymaže konec řádku -- řetězec je pole plné délky vyplněné zprava nulami
	token=strtok(radek,";");
	for (i=0;i<6;i++) {
		if (token == NULL) break;
		switch (i) {
			case 0: strcpy(prvni->nazev,token); break;
			case 1: strcpy(prvni->umelec,token); break;
			case 2: strcpy(prvni->album,token); break;
			case 3: prvni->rok=atoi(token); break;
			case 4: prvni->delka=atoi(token); break;
			default:strcpy(prvni->zanr,token); break;
		}
		token=strtok(NULL,";");
	}

Toto je druhý chyták a test uvažování: pokud má záznam pevnou délku, netřeba doplňovat prázdné položky. Tím si ušetříte spoustu práce. Jinak by se samozřejmě musela tokenizace dělat jinak, například do inicializovaného pole řetězců a teprve z něj kus po kuse testovat, zda není některá položka prázdná. V CSV to máte ošetřeno vlastní strukturou.

	prvni->dalsi=NULL;
	while (fgets(radek,delka,vstup)) {
		if (strlen(radek)==1) continue;
		if ((jeden->dalsi=(MP3 *) malloc(sizeof(MP3)))==NULL) { printf("Malo pameti\n"); return -1; }
		jeden=jeden->dalsi; // TOTO JE TO ZÁSADNÍ MÍSTO! tím se přejde na další záznam
		if (radek[strlen(radek)-1]=='\n') radek[strlen(radek)-1]='\0';
		token=strtok(radek,";");
		//printf ("na adrese %p: \n\n",jeden);
		for (i=0;i<6;i++) {
			if (token == NULL) break;
			switch (i) {
				case 0: strcpy(jeden->nazev,token); break;
				case 1: strcpy(jeden->umelec,token); break;
				case 2: strcpy(jeden->album,token); break;
				case 3: jeden->rok=atoi(token); break;
				case 4: jeden->delka=atoi(token); break;
				default:strcpy(jeden->zanr,token); break;
			}
			token=strtok(NULL,";");
		}
		jeden->dalsi=NULL;
	}

Tohle je poslední látka: je třeba mít deklarován první záznam a běžný záznam. pokud první ztratíte, ztratili jste hlavu hada a nenajdete ani jeden záznam. Obsah proměnné prvni je naprosto zásadní.

V tomto okamžiku je většina práce hotova: soubor byl po řádcích načten, řádky tokenizovány, tokeny zapsány do složek struktury, struktura uložena do paměti. Teď ji jen vypíšeme a binárně uložíme:

	free(radek); // je to tak lepší, původní soubor může být opravdu veliký
	fclose(vstup);
	vystup=fopen("cviceni.bin","wb");
	zobraz=prvni;
	while (zobraz->dalsi!=NULL) {
		fwrite(zobraz,1,sizeof(*zobraz),vystup);

Tady je prosím celý binární zápis. Jak bylo řečeno posledně: binární čtení a zápis se provádí párem příkazů fread/fwrite, kterým se zadává

  1. odkaz, tedy ukazatel na začátek paměti, kterou chci uložit (v našem případě na strukturu),
  2. rozměr ukládaného bloku – zde 1 bajt,
  3. délku bloku, tedy velikost datového typu, v němž je struktura definována, a
  4. výstupní soubor otevřený v režimu „wb“, tedy pro binární zápis.

Tedy: žádné konverze do dvojkové soustavy a podobné zbytečnosti, jen prostý dump všech fragmentů paměti, v nichž se nachází uložená data.

		fflush(vystup);
		printf("adresa: %p\nnázev: %s\numělec: %s\nalbum: %s\nrok: %d\ndélka: %d\nžánr: %s\nadresa dalšího záznamu: %p\n\n",zobraz,zobraz->nazev,zobraz->umelec,zobraz->album,zobraz->rok,zobraz->delka,zobraz->zanr,zobraz->dalsi);
		zobraz=zobraz->dalsi;
	}
	fclose(vystup);
	return 0;
}

Tento způsob ukládání textových dat je silně nehospodárný, jak lze vidět z fragmentu hexdumpu výstupního souboru:

2f1940 00 00 00 00 00 00 00 00 00 00 00 00 00 00 54 68  >..............Th<
2f1950 65 20 47 72 69 6e 63 68 20 57 68 6f 20 53 74 6f  >e Grinch Who Sto<
2f1960 6c 65 20 43 68 72 69 73 74 6d 61 73 00 00 00 00  >le Christmas....<
2f1970 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1980 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1990 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f19a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f19b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f19c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f19d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f19e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f19f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a50 d0 07 00 00 f7 00 00 00 53 6f 75 6e 64 74 72 61  >........Soundtra<
2f1a60 63 6b 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >ck..............<
2f1a70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1a90 50 c3 ca b9 14 56 00 00 57 68 65 72 65 20 44 69  >P....V..Where Di<
2f1aa0 64 20 59 6f 75 20 53 6c 65 65 70 20 4c 61 73 74  >d You Sleep Last<
2f1ab0 20 4e 69 67 68 74 00 00 00 00 00 00 00 00 00 00  > Night..........<
2f1ac0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
2f1ad0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<

Všimněte si těch nul: to jsou ty nadbytečné délky jednotlivých řetězců ve struktuře. Pokud deklarujete 255 znaků, obsadí se 255 bajtů a jen se zleva vyplní textem, zbytek jsou nuly. To je to, co se děje při alokaci. Binární dumpy ovšem mají jednu podstatnou výhodu: dají se takřka v reálném čase dekomponovat na jednotlivé záznamy prostým uložením do paměti. To s textovými daty neuděláte.