IJC: DU2
Jazyk C DU2 26.3.2024
----------------------------------------------------------------
Domácí úkol č.2
Termín odevzdání: 24.4.2024 (Max. 15 bodů)
1) (max 5b)
a) V jazyku C napište program "tail.c", který ze zadaného
vstupního souboru vytiskne posledních 10 řádků. Není-li
zadán vstupní soubor, čte ze stdin. Je-li programu zadán
parametr -n číslo, bude se tisknout tolik posledních řádků,
kolik je zadáno parametrem 'číslo' (bez znaménka).
Případná chybová hlášení tiskněte do stderr. Příklady:
tail soubor
tail -n 20
// Program počítá četnost slov ve vstupním textu,
// slovo je cokoli oddělené "bílým znakem"
#include
#include
#include
int main() {
using namespace std;
unordered_map m; // asociativní pole
// mapuje klíč/string na hodnotu/int
string word;
while (cin >> word) // čtení slova (jako scanf "%s", ale bezpečné)
m[word]++; // počítání výskytů slova (zvýší hodnotu pro
// zadaný klíč/slovo pokud záznam existuje,
// jinak vytvoří nový záznam s hodnotou 0 a
// tu operace ++ zvýší na 1)
for (auto &mi: m) // pro všechny prvky kontejneru m
cout << mi.first << "\t" << mi.second << "\n";
// klíč/slovo hodnota/počet
// prvky kontejneru typu "map" jsou dvojice (klíč,hodnota)
}
Výstupy programů musí být pro stejný vstup stejné (kromě pořadí a příliš
dlouhých slov).
Výsledný program se musí jmenovat "wordcount.c".
Implementujte tabulku s rozptýlenými položkami (hash table) - viz dále.
Veškeré operace s tabulkou budou v samostatné knihovně (vytvořte statickou
i dynamickou/sdílenou verzi). V knihovně musí být prakticky každá funkce ve
zvláštním modulu -- to například umožní případnou výměnu htab_hash_function() ve vašem
staticky sestaveném programu. (V dynamicky sestaveném programu je to možné vždy.)
Vyzkoušejte si to: definujte svoji verzi htab_hash_function() v programu
s podmíněným překladem -- použijte #ifdef MY_HASH_FUN_TEST.
Knihovna s tabulkou se musí jmenovat
"libhtab.a" (na Windows je možné i "htab.lib") pro statickou variantu,
"libhtab.so" (na Windows je možné i "htab.dll") pro sdílenou variantu
a rozhraní "htab.h".
Podmínky:
- Implementace musí být dynamická (malloc/free) a musíte zvládnout
správu paměti v C (použijte valgrind nebo jiný podobný nástroj).
- Vhodná rozptylovací funkce pro řetězce je podle literatury
(http://www.cse.yorku.ca/~oz/hash.html - varianta sdbm):
size_t htab_hash_function(const char *str) {
uint32_t h=0; // musí mít 32 bitů
const unsigned char *p;
for(p=(const unsigned char*)str; *p!='\0'; p++)
h = 65599*h + *p;
return h;
}
její výsledek modulo arr_size určuje index do tabulky:
index = (htab_hash_function("mystring") % arr_size);
Zkuste použít i jiné podobné funkce a porovnejte efektivitu.
- Tabulka je (pro knihovnu privátní) struktura obsahující pole seznamů,
jeho velikost a počet položek tabulky v následujícím pořadí:
+----------+
| size | // aktuální počet záznamů [(key,data),next]
+----------+
| arr_size | // počet položek následujícího pole ukazatelů
+---+------+
|ptr|-->[(key,data),next]-->[(key,data),next]-->[(key,data),next]--|
+---+
|ptr|--|
+---+
|ptr|-->[(key,data),next]-->[(key,data),next]--|
+---+
|ptr|--|
+---+
Položka .arr_size je velikost následujícího pole ukazatelů (použijte
C99: "flexible array member"). Paměť pro strukturu se dynamicky alokuje
tak velká, aby se do ní vešly i všechny položky pole.
V programu zvolte vhodnou velikost pole a v komentáři zdůvodněte vaše
rozhodnutí.
(V obrázku platí velikost .arr_size==4 a počet položek .size==5.)
Rozhraní knihovny obsahuje jen _neúplnou_deklaraci_ struktury, definice
je uživateli knihovny skryta (jde o formu zapouzdření - "encapsulation").
- Napište funkce podle následujícího hlavičkového souboru (API):
==================================================================
// htab.h -- rozhraní knihovny htab (řešení IJC-DU2)
// Licence: žádná (Public domain)
// následující řádky zabrání násobnému vložení:
#ifndef HTAB_H__
#define HTAB_H__
#include // size_t
#include // bool
// Tabulka:
struct htab; // neúplná deklarace struktury - uživatel nevidí obsah
typedef struct htab htab_t; // typedef podle zadání
// Typy:
typedef const char * htab_key_t; // typ klíče
typedef int htab_value_t; // typ hodnoty
// Dvojice dat v tabulce:
typedef struct htab_pair {
htab_key_t key; // klíč
htab_value_t value; // asociovaná hodnota
} htab_pair_t; // typedef podle zadání
// Rozptylovací (hash) funkce (stejná pro všechny tabulky v programu)
// Pokud si v programu definujete stejnou funkci, použije se ta vaše.
size_t htab_hash_function(htab_key_t str);
// Funkce pro práci s tabulkou:
htab_t *htab_init(const size_t n); // konstruktor tabulky
size_t htab_size(const htab_t * t); // počet záznamů v tabulce
size_t htab_bucket_count(const htab_t * t); // velikost pole
htab_pair_t * htab_find(const htab_t * t, htab_key_t key); // hledání
htab_pair_t * htab_lookup_add(htab_t * t, htab_key_t key);
bool htab_erase(htab_t * t, htab_key_t key); // ruší zadaný záznam
// for_each: projde všechny záznamy a zavolá na ně funkci f
// Pozor: f nesmí měnit klíč .key ani přidávat/rušit položky
void htab_for_each(const htab_t * t, void (*f)(htab_pair_t *data));
void htab_clear(htab_t * t); // ruší všechny záznamy
void htab_free(htab_t * t); // destruktor tabulky
// výpočet a tisk statistik délky seznamů (min,max,avg) do stderr:
void htab_statistics(const htab_t * t);
#endif // HTAB_H__
==================================================================
Hlavičkový soubor můžete celý převzít (je "Public domain").
- Stručný popis základních funkcí:
t=htab_init(num) konstruktor: vytvoření a inicializace tabulky
num = počet prvků pole (.arr_size)
size_t s=htab_size(t) vrátí počet prvků tabulky (.size)
size_t n=htab_bucket_count(t) vrátí počet prvků pole (.arr_size)
ptr=htab_find(t,key) vyhledávání - viz dále
ptr=htab_lookup_add(t,key) vyhledávání+přidání - viz dále
b=htab_erase(t,key) zrušení záznamu se zadaným klíčem (úspěch:true)
htab_for_each(t,funkce) projde všechny záznamy, na každý zavolá funkci
(pozor na možné změny tabulky!)
htab_clear(t) zrušení všech položek, tabulka zůstane prázdná
htab_free(t) destruktor: zrušení tabulky (volá htab_clear())
htab_statistics(t) tiskne statistiky délky seznamů do stderr
(ilustruje kvalitu hash_function)
(použijte v programu #ifdef STATISTICS)
kde t je ukazatel na tabulku (typu htab_t *),
b je typu bool,
ptr je ukazatel na záznam (položku tabulky {klíč,hodnota}),
- Vhodně zvolte typy parametrů funkcí (včetně použití const).
- Záznam [(key,value),next] je typu
struct htab_item
a obsahuje položky:
next ... ukazatel na další záznam
struct htab_pair ... veřejná struktura s položkami:
key ..... ukazatel na dynamicky alokovaný řetězec,
value ... asociovaná data = počet výskytů
Tento záznam je definován v privátním hlavičkovém souboru pro všechny
moduly tabulky a není dostupný při použití knihovny ("Opaque data type").
Uživatel používá ukazatel na vnořenou strukturu htab_pair_t.
- Funkce
htab_pair_t *htab_find(htab_t *t, htab_key_t key);
V tabulce t vyhledá záznam odpovídající řetězci key a
- pokud jej nalezne, vrátí ukazatel na záznam
- pokud nenalezne, vrátí NULL
- Funkce
htab_pair_t *htab_lookup_add(htab_t *t, htab_key_t key);
V tabulce t vyhledá záznam odpovídající řetězci key a
- pokud jej nalezne, vrátí ukazatel na záznam
- pokud nenalezne, automaticky přidá záznam a vrátí ukazatel
Poznámka1: Dobře promyslete chování této funkce k parametru key.
Poznámka2: podobně se chová C++ operator[] pro std::unordered_map
- Když htab_init nebo htab_lookup_add nemohou alokovat paměť,
vrací NULL (a uživatel musí testovat výsledek těchto operací)
Poznámka: C++ na to používá výjimky ("exceptions").
Napište funkci
int read_word(char *s, int max, FILE *f);
která čte jedno slovo ze souboru f do zadaného pole znaků
a vrátí délku slova (z delších slov načte prvních max-1 znaků,
a zbytek přeskočí). Funkce vrací EOF, pokud je konec souboru.
Umístěte ji do zvláštního modulu "io.c" (nepatří do knihovny).
Poznámka: Slovo je souvislá posloupnost znaků oddělená isspace znaky.
Omezení: řešení v C bude tisknout jinak uspořádaný výstup
a je povoleno použít implementační limit na maximální
délku slova (např. 255 znaků), delší slova se ZKRÁTÍ a program
při prvním delším slovu vytiskne varování na stderr (max 1 varování).
Poznámka: Vhodný soubor pro testování je například seznam slov
v souboru /usr/share/dict/words
nebo texty z http://www.gutenberg.org/
případně výsledek příkazu: "seq 1000000 2000000|shuf"
(10b)
Použijte implicitní lokalizaci (= nevolat setlocale()). Zamyslete se nad tím,
co by pro váš kód znamenalo použití UTF-8 při zapnuté lokalizaci s tímto
dnes běžně používaným kódováním.
Napište soubor Makefile tak, aby příkaz make vytvořil programy
"tail", "wordcount", "wordcount-dynamic" a knihovny "libhtab.a",
"libhtab.so" (nebo "htab.dll" atd.).
Program "wordcount" musí být staticky sestaven s knihovnou "libhtab.a".
Program "wordcount-dynamic" musí být sestaven s knihovnou "libhtab.so".
Tento program otestujte se stejnými vstupy jako u staticky sestavené verze.
Porovnejte efektivitu obou (C i C++) implementací (viz např. příkaz time)
a zamyslete se nad výsledky (pozor na vliv vyrovnávacích paměťí, velikosti
vstupního souboru atd.)
Také si zkuste překlad s optimalizací i bez ní (-O2, -O0) a porovnejte
efektivitu pro vhodný vstup.
Poznámky:
- pro testy wordcount-dynamic na linuxu budete potřebovat nastavit
LD_LIBRARY_PATH="." (viz "man ld.so" a odpovídající přednáška)
- Čtěte pokyny pro vypracování domácích úkolů (viz dále)
----------------------------------------------------------------
Obecné pokyny pro vypracování domácích úkolů (rev 25.3.2024)
* Pro úkoly v jazyce C používejte ISO C11 (soubory *.c)
Pro úkoly v jazyce C++ používejte ISO C++17 (soubory *.cc)
Použití nepřenositelných konstrukcí není dovoleno.
* Úkoly zkontrolujte překladačem například takto:
gcc -g -std=c11 -pedantic -Wall -Wextra priklad1.c
g++ -std=c++17 -pedantic -Wall priklad.cc
Místo gcc můžete použít i jiný překladač.
V souvislosti s tím napište do poznámky na začátku
souboru jméno překladače, kterým byl program testován
(implicitní je verze GNU C instalovaná na serveru merlin).
Pro ověření správnosti paměťových operací zkuste extra parametry pro gcc
(Makefile: CFLAGS += -fsanitize=address, LDFLAGS += -fsanitize=address).
* Programy pište, pokud je to možné, do jednoho zdrojového
souboru. Dodržujte předepsaná jména souborů.
* Na začátek každého souboru napište poznámku, která bude
obsahovat jméno, fakultu, označení příkladu a datum.
* Úkoly je nutné zabalit programem zip takto:
zip xunkno99.zip *.c *.cc *.h Makefile
Jméno xunkno99 nahradíte vlastním. ZIP neobsahuje adresáře.
Každý si zkontroluje obsah ZIP archivu jeho rozbalením v prázdném adresáři
a napsáním "make run".
* Řešení se odevzdává elektronicky v ISVUT (velikost souboru je omezena)
* Posílejte pouze nezbytně nutné soubory -- ne *.EXE !
* Úkoly neodevzdané v termínu budou za 0 bodů.
* Opsané úkoly budou hodnoceny 0 bodů pro všechny zůčastněné
a to bez výjimky (+bonus v podobě návštěvy u disciplinární komise).
Poslední modifikace:
Pokud naleznete na této stránce chybu, oznamte to dopisem na adresu
peringer AT fit.vutbr.cz