Zadání pro 10. cvičení —  25. dubna 2024

       Fronta — Queue

        abstraktní datový typ

(Alternativní informace k frontě.)

Fronta se obecně v programování vyskytuje méně často než zásobník. Kdežto v lidské sociální praxi je to obráceně: fronty u pokladen, v menze, na poště atd.
Už tady můžeme vidět jakousi dvojznačnost (sklenice poloplná - poloprázdná, byla dříve slepice či slepičí vejce atd.): začíná fronta u okénka? Z pohledu
toho, kdo u onoho okénka obsluhuje, to tak je. Či začíná tam, kam přichází zákazníci (a staví se do fronty)?. Končila by pochopitelně potom na druhé straně.
Z této nejasnosti vychází naše přesvědčení, že fronta má dva konce nebo dva začátky a záleží jen na světonázorovém vybavení, co z toho to je. Mé zkušenosti
mluví pro dva konce a toho se přidržíme. Budeme tedy psát, číst, mluvit o konci, do nějž se vkládá a o konci, z nějž se vyjímá. Zkráceně o vkládacím konci
a o vyjímacím konci.
O frontě tedy budeme mluvit jako o entitě, pro kterou platí, že jakožto první se výjme to, co bylo vloženo první (a že jakožto poslední se výjme to, co bylo
vloženo poslední. Anglickými zkratkami: FIFO, respective LILO.)
Takováto záležitost opět může mít povahu mechanickou, například u různých pásových výrob (u nichž se drobně může lišit okamžitá rychlost různých fází.
V programování, ale i obecně při mnoha příležitostech je právě ono „dodržení pořadí” důležité …
Například klávesnicový buffer — sotva bychom chtěli, aby se znaky do počítače dostávali v obráceném či libovolném pořádí.
Abstraktně: Nechť F je fronta, pak (jak už víme) Aby náš arsenál byl úplný, tak ještě musíme mít možnost ji inicialisovat a ptát se na to, zda je fronta prázdná, případně plná. Mnohdy se ještě přidává, operace zpřístupňující hodnotu, která je na řadě na vyjmuti ze zásobníku,
abychom nemuseli vyjímat, hned nepoužitelnou (chceme-li, například, jen porovnávat) a někde ji schovávat, poznačit si, že to
tak je - což je vše „náročnější”.
  Logická spojka ∧ (konjunkce) tady není komutativní ona a i ⇒ (implikace):
předepisuje pořadí vyhodnocovaných funkcí a prováděných operací
 
Axiomy  jePlná(F)      ~vlož(F, něco)
  inicialisuj(F)      jePrazdná(F)
  inicialisuj(F) ∧ vlož(FA)      vyjmi(F, něco) ∧ něco = A
  inicialisuj(F) ∧ vlož(FA)  ∧ ~jePlná(F) ∧ vlož(FB)      vyjmi(F, něco) ∧ něco = A  ∧ vyjmi(F, něco) ∧ něco = B
 inicialisuj(F) ∧ vlož(FA)      naVyjmuti(F, něco) ∧ něco = A
 inicialisuj(F) ∧ vlož(FB)  ∧ ~jePlná(F) ∧ vlož(FA)      naVyjmuti(F, něco) ∧ něco = B  ∧ naVyjmuti(F, něco) ∧ něco = B
 ~jePlná(F)  ∧ vlož(FA)      ~jePrazdná(F)
 jePrazdná(F)      ~vyjmi(F, něco)

Takovouto frontu opět lze realisovat mechanicky, elektr(on)icky či to úplně
anebo alespoň částečně „zašít” do procesoru či to implementovat softwareově - naprogramovat to.
To je vaše zadání.
V tělech předepsaných funkcích nic nevypisujte a netestujte, před vyjmutím (z fronty) se volající program musí
sám přesvědčit příslušnou funkcí, zda fronta není prázdná (a adekvátně k volajícímu programu na to reagovat)
a analogicky při vkládání do plné.

Proč u zásobníků a front různé implementace?
Implementace polem je rychlá, což je v některých případech nezbytné.
Jeden z těch případů je její použití v jádru operačních systémů.
Tehdy je ale nezbytné testovat plnost či prázdnost před vkládáním
respektive vyjímáním - jejich zanedbání je branou pro crackery.

Implementace spojovým seznamem nemá takový problém s přeplněním,
ale zase je časově náročná a musí se vyžadovat služba operačního systému,
což zejména při vlastním běhu operačního systému není dost dobře možné.

Dodržujte, prosím, české identifikátory funkcí.
Vaše různě implementované funkce by měly reagovat stejně.
Vaše zadání je jak implementace polem, tak spojovým seznamem:

Implementace polem:

Vkládá se a vyjímá ve všech případech jen po znacích.
 
Analogicky k zásobníku budeme mít indexy dva:
jeden pro vkládání a jeden pro vyjímání
  Když pole zakroutíme dostaneme:
 
 
Po inicialisaci Fronta je prázdná: nelze vyjímat vkládání vyjímání Po inicialisaci Fronta je prázdná: nelze vyjímat vkládání vyjímání
V E R I T A S Po vložení: „VERITAS” vkládání vyjímání V E R I T A S Po vložení: „VERITAS” vkládání vyjímání
V E R I T A S V I N C I T I Po dalším vložení: „ VINCIT I” Fronta je plná: nelze vkládat vkládání vyjímání V E R I T A S V I N C I T I Po dalším vložení: „ VINCIT I” Fronta je plná: nelze vkládat vkládání vyjímání
V E R I T A S V I N C I T I Vyjmeme osmkrát: „VERITAS ” Fronta je stále plná vkládání vyjímání V E R I T A S V I N C I T I Vyjmeme osmkrát: „VERITAS ” vkládání vyjímání
V E R I T A S V I N C I T I Vložit „N VINO ” nelze Fronta je stále plná vkládání vyjímání N V I N O S V I N C I T I Po vložení: „N VINO” vkládání vyjímání
 
 
Pokud bychom při každém výjmutí posunuli všechny uložené prvky o jedna doleva, ztratili bychom hlavní výhodu implementace polem:
Jednoduchost a rychlost.
  Jak vidíme na prvním a třetím obrázku (v tomto sloupci),
jak u prázdné tak u plné fronty ukazují v obou případech oba indexy na stejný znak a nejde rozlišit, který z těch dvou stavů nastal.
Je lepší uchovávat pouze jeden z oněch indexů a počet. A druhý dopočítávat.
 
 
  Zakroucení nemůžeme zrealisovat hardwareově.
Musíme ho simulovat pomocí zbytku po dělení.


Implementace spojovým seznamem:

Pascal fill="none" stroke="green" stroke-width="2" /> f Viète de Fermat Descartes

 je opět NULL.


typedef struct mmbr {
            valueType value;
            struct mmbr *next;
        } member;

typedef struct {
    member *a;
    member *b;
} queue; 

Při implementaci fronty spojovým seznamem, je důležité vědět, že přestože formálně vypadají stejně jako spojový seznam,
má opět fronta méně operací. Protože u fronty je zapotřebí pracovat s oběma konci. Fronta má ukazatele jak na začátek,
tak i na konec spojového seznamu. Na začátek i konec spojového seznamu se přidává snadno.
Je na vás, rozhodnout se, jestli se snadněji odebírá hlava nebo konec spojového seznamu.
Podle toho implementujte operace fronty.
Dále po vyjmutí vracíte operačnímu systému paměť (free)
a náležitě k tomu při vkládání musíte paměť naalokovat.
Operace fronty pracují buď jen s frontou anebo s frontou a hodnotou.
U této implementace (spojovým seznamem), test jePlny bude vždy vracet 0 (false),
protože předpokládáme, že pokud se program nezacyklí,
pak operační systém bude vždy mít dost paměti na to, aby ji mohl přidělit.




Hlavičkový soubor funkcí pro práci s frontou queue.h:
/**
 * Head file for queue operations
 *
 * @file queue.h 
 * @author   A.  Zlamal
 * @version 2020 04 17  
 * 
 */


/**
 *  Inicialize the given queue.
 *  
 *  Inicializuje zadanou frontu.
 *
 *  @param[in, out]  s  the address of the queue to inicialize.
 *                      adresa fronty, jenz se ma inicialisovat.
 *                      
 *  @param[in]  sz  the size of the queue to inicialize. 
 *                  IN CASE OF IMPLEMENTATION BY LINKED LIST DATA OF SIZE IS OMITTED.
 *                  velikost fronty, jez se ma inicialisovat.
 *                  V PRIPADE IMPLEMENTACE SPOJOVYM SEZNAMEM SE UDAJ VELIKOSTI OPOMIJI.
 */
void inicialisuj(queue *q, unsigned int sz);

/**
 *  Enqueue the value of the second parametr to the queue pointed by the first parametr.
 *  Vlozi hodnotu druheho parametru do fronty zadane prvním parametrem.   
 *
 *  @param[in, out]  s  the address of the queue, into which is to be enqueued.
 *                      adresa fronty, do niz se ma vlozit.
 *   
 *  @param[in]  pv  the value, which is to enqueueue.
 *                  hodnota jenz se se ma vlozit.
 *  IMPORTANT WARNING: REACTION ON THE ENQUEUE TO FULL QUEUE IS NOT DEFINED
 *                     AND WITH HIGH PROBABILITY CAUSES FATAL ERROR.
 *                     BEFORE ENQUEUE TEST THE QUEUE BY FUNCTION queueIsFull.
 *   (Remark: there is the posibility to join queueIsFull and enqueue
 *               int enqueueNew(queue q, valueType *pv){
 *                   if(queueIsFull(q){
 *                       return 1;
 *                   }
 *                   enqueue(q, pv);
 *                   return 0;
 *               }, but I decided for the introduced variant.)
 *  DURAZNE UPOZORNENI: REAKCE NA VLOZENI DO PLNE FRONTY NENI DEFINOVANA
 *                      A S VYSOKOU PRAVDEPODOBNOSTI POVEDE K OSUDOVE CHYBE.
 *                      PRED VKLADANIM OTESTUJTE FRONTU FUNKCI queueIsFull.
 *   (Poznamka: je mozne spojit queueIsFull a enqueue
 *               int enqueueNew(queue q, valueType *pv){
 *                   if(queueIsFull(q){
 *                       return 1;
 *                   }
 *                   enqueue(q, pv);
 *                   return 0;
 *               }, ale rozhodl jsem se pro predstavenou variantu.)
 */
void vloz(queue *q, valueType *pv);

/**
 *  Dequeue the value, which is on the dequeue end of the queue. 
 *  Vrati hodnotu, jez je na odebiracim konci zasobniku. 
 *
 *  @param[in, out]  s  the address of the queue, from which is to dequeueue the value.
 *                      adresa fronty, z niz se se ma odebrat hodnota.
 *  
 *  @param[out]  pv  the value, which is dequeued.
 *                    hodnota, ktera se ma odebrat.
 *
 *  IMPORTANT WARNING: REACTION ON THE DEQUEUEING FROM EMPTY QUEUE IS NOT DEFINED
 *                     AND WITH HIGH PROBABILITY CAUSES FATAL ERRORR.
 *                     BEFORE DEQUEUE TEST THE QUEUE BY FUNCTION queueIsEmpty.
 *   (Remark: there is the posibility to join queueIsEmpty and dequeue
 *               int dequeueNew(queue q, valueType *pv){
 *                   if(queueIsEmpty(q){
 *                       return 1;
 *                   }
 *                   dequeue(q, pv);
 *                   return 0;
 *               }, but I decided for the introduced variant.)
 *  DURAZNE UPOZORNENI: REAKCE NA ODKAZ NA VYJMUTI Z PRAZDNE FRONTY NENI DEFINOVANA
 *                      A S VYSOKOU PRAVDEPODOBNOSTI POVEDE K OSUDOVE CHYBE.
 *                      PRED VYBEREM OTESTUJTE FRONTU FUNKCI queueIsEmpty.
 *   (Poznamka: je mozne spojit queueIsEmpty a dequeue
 *               int enqueueNew(queue q, valueType *pv){
 *                   if(queueIsEmpty(q){
 *                       return 1;
 *                   }
 *                   dequeue(q, pv);
 *                   return 0;
 *               }, ale rozhodl jsem se pro predstavenou variantu.)
 */
void vyjmi(queue *q, valueType *pv);

/**
 *  Returns the the value, which is to dequeue.
 *  Vrati hodnotu, ktera se ma odebrat z fronty. 
 *
 *  @param[in, out]  s  the address of the queue, from which is to be copied the value from dequeueing end.
 *                      adresa zasobniku, z jehoz odebiraciho konce se ma zkopirovat hodnota.
 *  
 *  @param[out]  pv  the address, into which is to store the value, which is on the dequeueing end.
 *                   adresa, na niz se ma hodnota z jejiho odebiraciho konce ulozit.
 *
 *  IMPORTANT WARNING: REACTION ON THE REFERENCING THE TOP FROM EMPTY QUEUE IS NOT DEFINED
 *                     AND WITH HIGH PROBABILITY CAUSES FATAL ERROR.
 *                     BEFORE USING FRONT TEST THE QUEUE BY FUNCTION queueIsEmpty.
 *   (Remark: there is too the posibility to join queueIsEmpty and front
 *               int frontNew(queue q, valueType *pv){
 *                   if(queueIsEmpty(q){
 *                       return 1;
 *                   }
 *                   frontNew(q, pv);
 *                   return 0;
 *               }, but I decided for the introduced variant.)
 *  DURAZNE UPOZORNENI: REAKCE NA ODKAZ NA VRCHOL PRAZDNEHO FRONTY NENI DEFINOVANA
 *                      A S VYSOKOU PRAVDEPODOBNOSTI POVEDE K OSUDOVE CHYBE.
 *                      PRED REFERENCOVANIM ODEBIRACIHO KONCE OTESTUJTE ZASOBNIK FUNKCI queueIsEmpty.
 *   (Poznamka: je mozne spojit queueIsEmpty a front
 *               int frontNew(queue q, valueType *pv){
 *                   if(queueIsEmpty(q){
 *                       return 1;
 *                   }
 *                   front(q, pv);
 *                   return 0;
 *               }, ale rozhodl jsem se pro predstavenou variantu.)
 */
void naVyjmuti(queue *q, valueType *pv);

/**
 *  Tests if the queue is empty.
 *  Testuje zda je fronta prazdna. 
 *   
 *  @param[in, out]  s  the address of the queue.
 *                      adresa fronty.
 *   
 *  @return  non zero iff the queue is empty; otherwise zero.
 *           nenulovou hodnotu, jen je-li fronta prazdna; v opacnem pripade nulu. 
 */
int jePrazdna(queue *q);

/**
 *  Tests if the queue is full 
 *  Testuje zda je fronta plna.  
 *
 *  @param[in, out]  s  the address of the queue.
 *                      adresa fronty.
 *   
 *  @return  zero iff the stack isn't full; otherwise non zero.
 *           nulu jen neni-li zasobnik plny; v opacnem pripade nenulovou hodnotu.
 */
int jePlna(queue *q);

/**
 *  Free the memory allocated for the queue in case of implentation by linked list.
 *                  (The same effect as initialize, but without memory leaks tested by valgrind.)
 *  Uvolni pamet alokovanou pro frontu v pripade implementace spojovym seznamem.
 *                  (Stejny efekt jako inicialisuj, ale bez memory leaku zjistovanych valgrindem.)
 *   
 *  @param[in, out]  q  address of the queue.
 *                      adresa fronty.
 */
void uvolniPamet(queue *q);


Soubor pro demonstraci práce se zásobníkem main.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "definedTypeForArray.h"
#include "queue.h"

/**
 * Program to demonstrate functions for queue 
 * 
 * @file   main.c 
 * @author   A.  Zlamal
 * @version 2020 04 17  
 */
int main(){
    valueType y;
    queue f;
    int isf;
    char *formStr = "Fronta je plna: hodnota %s se uz nevesla.\n";  

    inicialisuj(&f, 8); 
    printf("Zadavejte na radky znakove retezce. Ta se budou vkladat do fronty.\n"
           "Zadavani se ukoncuje prazdnym radkem.\n");
    while ((fgets(y, TEXT_LENGTH, stdin) != NULL) && (*y != '\n') && !(isf = jePlna(&f))) {
        y[strlen(y) - 1] = '\0';
        vloz(&f, &y);
    }
    if (isf) {
        y[strlen(y) - 1] = '\0';
        printf(formStr, y);
    }
    printf("Konec vkladani do fronty a nejvyse dvakrat vyber z ni.\n");
    if (!jePrazdna(&f)) {
        naVyjmuti(&f, &y);
        printf("    %s :: ", y);
        vyjmi(&f, &y);
        printf("%s\n", y);
    }
    if (!jePrazdna(&f)) {
        naVyjmuti(&f, &y);
        printf("    %s :: ", y);
        vyjmi(&f, &y);
        printf("%s\n", y);
    }
    printf("Nasleduje dalsi vkladani do fronty a vyber vseho z ni:\n");
    while ((fgets(y, TEXT_LENGTH, stdin) != NULL) && (*y != '\n') && !(isf = jePlna(&f))) {
        y[strlen(y) - 1] = '\0';
        vloz(&f, &y);
    }
    if (isf) {
        y[strlen(y) - 1] = '\0';
        printf(formStr, y);
    }
    while (!jePrazdna(&f)) {
        naVyjmuti(&f, &y);
        printf("    %s :: ", y);
        vyjmi(&f, &y);
        printf("%s\n", y);
    }
    printf("\nNasleduje dalsi vkladani do fronty a opet vyber vseho z ni:\n");
    while ((fgets(y, TEXT_LENGTH, stdin) != NULL) && (*y != '\n') && !(isf = jePlna(&f))) {
        y[strlen(y) - 1] = '\0';
        y[strlen(y) - 1] = '\0';
        vloz(&f, &y);
    }
    if (isf) {
        printf(formStr, y);
    }
    while (!jePrazdna(&f)) {
        naVyjmuti(&f, &y);
        printf("    %s :: ", y);
        vyjmi(&f, &y);
        printf("%s\n", y);
    }
    uvolniPamet(&f);
    printf("Byla uvolnena pamet zasobniku: %s.\nKonec chodu programu.\n", f == NULL ? "NULL" : f->values[0]);
    return EXIT_SUCCESS;
}