#include #include #include #define MAX_DELKA_SLOVA 100 #define MAX_VSTUP 10000 #define MAX_VYSTUP 10000 #define MAX_UZLU 1000000 /** * Vzorový program pro 27-5-5 Kniha přání a stížností * Předpokládaný formát vstupu: * na prvním řádku číslo N udávající počet zakázaných slov * na každém z následujících N řádků jedno slovo (předpokládáme slova délky < 100) * na posledním řádku vstupní text * všechna slova jsou oddělená mezerami, tvořená pouze malými písmeny anglické abecedy * * Program si načte zakázaná slova a vybuduje z nich upravenou Aho-Corasickovou (nebudou * v ní zkratky, naopak v ní bude informace, kam se přesunout po smazání slova). * Následně prochází text, příčemž vyhledává zakázaná slova a případně je maže. **/ /** * Struktura pro jednotlivé uzly (vrcholy) Aho-Corasickové * konec_slova není jen booleovský příznak, ale informace, jak dlouhé slovo ve vrcholu končí * ukazatel na otce usnadňuje výpočet zpětných hran, ale šlo by to bez něj * rozsahy 255 pro pole dopředných a mazacích hran jsou zbytečně velké, ale dostačující a pohodlné */ struct uzel { int konec_slova; unsigned char znak; struct uzel *otec; struct uzel *zpetna_hrana; struct uzel *dopredne_hrany[255]; struct uzel *mazaci_hrany[255]; }; // Technikálie pro uvolnění alokované paměti void uvolni_strom(struct uzel *koren) { for (unsigned char c='a'; c<='z'; c++) { if (koren->dopredne_hrany[c]) { uvolni_strom(koren->dopredne_hrany[c]); } } free(koren); } int main(void) { struct uzel *aktualni; // Proměnná, kterou budeme průběžně používat při procházení stromu // Načteme počet zakázaných slov int n; scanf("%d", &n); // ... a načteme jednotlivá zakázaná slova char **slova = calloc(n, sizeof(char *)); for (int i=0; izpetna_hrana = koren; koren->otec = koren; // Vyrobíme dopředné hrany for (int i=0; idopredne_hrany[znak] = uzel; uzel->otec = aktualni; uzel->znak = znak; aktualni = uzel; } aktualni->konec_slova = strlen(slova[i]); } // Zpětné hrany počítáme pomocí fronty, což ve stromě funguje stejně jako procházení po vrstvách // Z podstaty konstrukce stromu navíc i-tá vrstva odpovídá i-tým znakům slov struct uzel **fronta = calloc(MAX_UZLU, sizeof(struct uzel *)); int zacatek_fronty = 0; int konec_fronty = 1; fronta[zacatek_fronty] = koren; while (zacatek_fronty != konec_fronty) { aktualni = fronta[zacatek_fronty++]; // Bude potřeba zpracovat všechny existující syny for (unsigned char znak='a'; znak<='z'; znak++) { if (aktualni->dopredne_hrany[znak]) { fronta[konec_fronty++] = aktualni->dopredne_hrany[znak]; } } // Samotná konstrukce zpětných hran struct uzel *cil = aktualni->otec->zpetna_hrana; unsigned char znak = aktualni->znak; while (!cil->dopredne_hrany[znak] && cil != koren) { cil = cil->zpetna_hrana; } if (cil->dopredne_hrany[znak] && cil->dopredne_hrany[znak] != aktualni) { aktualni->zpetna_hrana = cil->dopredne_hrany[znak]; } else { aktualni->zpetna_hrana = koren; } } free(fronta); // Mazací hrany opět spočítáme pomocí fronty fronta = calloc(MAX_UZLU, sizeof(struct uzel *)); zacatek_fronty = 0; konec_fronty = 1; fronta[zacatek_fronty] = koren; while (zacatek_fronty != konec_fronty) { aktualni = fronta[zacatek_fronty++]; for (unsigned char znak='a'; znak<='z'; znak++) { if (aktualni->dopredne_hrany[znak]) { aktualni->mazaci_hrany[znak] = aktualni->dopredne_hrany[znak]; fronta[konec_fronty++] = aktualni->dopredne_hrany[znak]; } else { if (aktualni->zpetna_hrana->dopredne_hrany[znak]) { aktualni->mazaci_hrany[znak] = aktualni->zpetna_hrana->dopredne_hrany[znak]; } else { aktualni->mazaci_hrany[znak] = koren; } } } } free(fronta); unsigned char znak; aktualni = koren; char vystup[MAX_VYSTUP]; struct uzel *pamet_stavu[MAX_VSTUP]; unsigned int delka_vystupu = 0; while (scanf("%c", &znak) != EOF) { // Každý znak nejprve uložíme na výstup, později ho možná smažeme vystup[delka_vystupu] = znak; while (!aktualni->dopredne_hrany[znak] && aktualni != koren) { aktualni = aktualni->zpetna_hrana; } if (aktualni->dopredne_hrany[znak]) { aktualni = aktualni->dopredne_hrany[znak]; } // Tady si ukládáme stav, ve kterém jsme na té které pozici byli pamet_stavu[delka_vystupu] = aktualni; if (aktualni->konec_slova) { // Vrátíme se ve výstupu o počet znaků nalezeného slova, tj. časem přepíšeme daný počet znaků delka_vystupu -= aktualni->konec_slova; // Vrátíme se do stavu, ve kterém jsme byli před načtením nalezeného slova aktualni = pamet_stavu[delka_vystupu]; } delka_vystupu++; } vystup[delka_vystupu] = 0; // Technikálie, která v podstatě nastavuje délku výstupu printf("%s", vystup); // Technikálie k uvolnění alokované paměti for (int i=0; i