Programovanie (1) v C/C++
1-INF-127, ZS 2024/25

Úvod · Pravidlá · Prednášky · Softvér · Testovač
· Kontaktujte nás pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Prosíme študentov, aby si pravidelne čítali e-maily na @uniba.sk adrese alebo aby si tieto emaily preposielali na adresu, ktorú pravidelne čítajú.


Prednáška 19: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(55 medziľahlých úprav od 2 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
 
== Oznamy ==
 
== Oznamy ==
* Na budúci týždeň bude semestrálna písomka.
+
 
* Tretiu domácu úlohu treba odovzdať ''do 6. decembra, 22:00''.
+
Prednášky
* V piatok budú doplnkové cvičenia, online.
+
* Tento týždeň a budúci pondelok ešte prednášky v normálnom režime.
 +
* Budúcu stredu 6.12. v prvej polovici prednášky informácie k skúške a rady k skúškovému všeobecne, potom doberieme posledné povinné učivo.
 +
* Posledný týždeň semestra v pondelok 11.12. nepovinná prednáška o nepreberaných črtách jazykov C a C++, v stredu 13.12. prednáška pravdepodobne nebude.
 +
 
 +
Cvičenia a úlohy
 +
* Cvičenia bežia normálne každý utorok, piatkové cvičenia už iba 2x.
 +
* Zajtrajšia rozcvička bude o vyfarbovaní.
 +
* Odporúčame získať 5 bodov z cvičení, v opačnom prípade sú pre vás v piatok povinné cvičenia.
 +
* Budúci utorok bude teoretická rozcvička na papieri.
 +
* Tretiu domácu úlohu treba odovzdať do budúceho utorka 5.12. 22:00.
 +
 
 +
Semestrálny test
 +
* V stredu 13.12. o 18:10.
 +
* Treba získať aspoň 50% bodov.
 +
* Opravný termín v januári.
 +
* Môžete si priniesť ťahák v rozsahu 1 listu A4.
 +
* Na semestrálnom teste budú podobné typy príkladov, aké poznáte z teoretických cvičení, napríklad napíšte funkciu, ktorá robí zadanú činnosť, doplňte chýbajúce časti funkcie, zistite, čo funkcia robí.
 +
* Vyskytnú sa ale aj príklady, kde je úlohou napísať, ako bude na nejakom vstupe fungovať algoritmus alebo dátová štruktúra z prednášky. Ukážky takýchto príkladov nájdete na stránke [[Zimný semester, semestrálny test]]
 +
* Môžete si pozrieť aj [[:Media:Pisomka-pokrocili2020.pdf|ukážkový test pre pokročilých]], ktorý má podobné typy príkladov ako semestrálny test.
 +
 
 +
Termíny skúšok
 +
* Piatok 15.12. 13:10
 +
* Piatok 12.1. 9:00
 +
* Piatok 19.1. 9:00
 +
* Piatok 26.1. 9:00, hlavne 1. opravný termín
 +
* Piatok 9.2. 9:00, hlavne 2. opravný termín
 +
 
 +
Na termíny sa bude dať prihlasovať od stredy 29.11. 20:00
 +
* Kapacita termínov bude obmedzená, prihláste sa teda radšej skôr, neskôr to môžete zmeniť.
 +
* Hláste sa iba na jeden termín!
 +
* Ak vidíte konflikt niektorého termínu s hromadnou skúškou alebo písomkou z iného predmetu, dajte mi prosím vedieť čím skôr, najneskôr v stredu.
 +
* Decembrový termín odporúčame hlavne študentom, ktorým programovanie nerobí problémy.
 +
* Viac informácií o skúške je na stránke [[Zimný semester, skúška]], spolu cez to prejdeme budúcu stredu.
 +
* Na testovač tento týždeň pridáme zopár tréningových príkladov na skúšku.
 +
 
 +
 
 +
Začneme s dokončením [[Prednáška_18#Vyfarbovanie_s_pou.C5.BEit.C3.ADm_radu|vyfarbovania z minulej prednášky]]
  
 
==Aritmetické výrazy==
 
==Aritmetické výrazy==
  
 
Nejaký čas sa teraz budeme venovať spracovaniu aritmetických výrazov pozostávajúcich z reálnych čísel, operácií <tt>+,-,*,/</tt> a zátvoriek <tt>(,)</tt>.
 
Nejaký čas sa teraz budeme venovať spracovaniu aritmetických výrazov pozostávajúcich z reálnych čísel, operácií <tt>+,-,*,/</tt> a zátvoriek <tt>(,)</tt>.
Prakticky najdôležitejšou úlohou tu je vyhodnotenie daného výrazu; napríklad pre výraz
+
Hlavnou úlohou je vyhodnotenie daného výrazu; napríklad pre výraz
 
<pre>
 
<pre>
(65 3 * 5) / (2 + 3)
+
(65 - 3 * 5) / (2 + 3)
 
</pre>
 
</pre>
chceme vedieť povedať, že jeho hodnota je 10. K tejto úlohe sa však dostaneme až časom. Najprv zavedieme a zľahka preskúmame dva alternatívne spôsoby zápisu aritmetických výrazov, z ktorých jeden nám vyhodnocovanie výrazov značne uľahčí.
+
chceme vedieť povedať, že jeho hodnota je 10. Najprv ale zavedieme dva alternatívne spôsoby zápisu aritmetických výrazov, z ktorých jeden nám vyhodnocovanie výrazov značne uľahčí.
  
 
===Infixová notácia===
 
===Infixová notácia===
Bežný spôsob zápisu aritmetických výrazov v matematike sa zvykne nazývať aj ''infixovou notáciou'' (prípadne ''infixovou formou'' alebo ''infixovým zápisom''). Toto pomenovanie odkazuje na skutočnosť, že binárne operátory (ako napríklad <tt>+,-,*,/</tt>) sa v tejto notácii píšu medzi svojimi dvoma operandmi. Poradie vykonávania jednotlivých operácií sa pritom riadi zátvorkami a prioritou operácií.  
+
Bežný spôsob zápisu aritmetických výrazov v matematike sa zvykne nazývať aj '''infixovou notáciou'''. Toto pomenovanie odkazuje na skutočnosť, že binárne operátory (ako napríklad <tt>+,-,*,/</tt>) sa v tejto notácii píšu medzi svojimi dvoma operandmi. Poradie vykonávania jednotlivých operácií sa riadi zátvorkami a prioritou operácií.  
  
 
Napríklad  
 
Napríklad  
Riadok 24: Riadok 60:
 
=== Prefixová notácia ===
 
=== Prefixová notácia ===
  
Pri takzvanej ''prefixovej notácii'' (často podľa jej pôvodcu Jana Łukasiewicza nazývanej aj ''[https://en.wikipedia.org/wiki/Polish_notation poľskou notáciou]'') sa každý operátor v aritmetickom výraze zapisuje pred svojimi dvoma operandmi.  
+
Pri takzvanej '''prefixovej notácii''' (často podľa jej pôvodcu Jana Łukasiewicza nazývanej aj ''[https://en.wikipedia.org/wiki/Polish_notation poľskou notáciou]'') sa každý operátor v aritmetickom výraze zapisuje pred svojimi dvoma operandmi.  
  
 
Napríklad výraz <tt>(65 – 3 * 5) / (2 + 3)</tt> má prefixový zápis
 
Napríklad výraz <tt>(65 – 3 * 5) / (2 + 3)</tt> má prefixový zápis
Riadok 30: Riadok 66:
 
/ - 65 * 3 5 + 2 3
 
/ - 65 * 3 5 + 2 3
 
</pre>
 
</pre>
 +
 +
Zaujímavosť: programovací jazyk [https://en.wikipedia.org/wiki/Lisp_(programming_language) Lisp] a jeho varianty využívajú prefixovú notáciu na všetky výrazy, píšu však aj zátvorky, napríklad  <tt>(+ 1 2)</tt>.
  
 
=== Postfixová notácia ===
 
=== Postfixová notácia ===
  
Pri ''postfixovej notácii'' (často nazývanej aj ''[https://en.wikipedia.org/wiki/Reverse_Polish_notation obrátenou poľskou notáciou]'') je situácia opačná: operátor sa zapisuje bezprostredne za svojimi dvoma operandmi.
+
Pri '''postfixovej notácii''' (často nazývanej aj ''[https://en.wikipedia.org/wiki/Reverse_Polish_notation obrátenou poľskou notáciou]'') je situácia opačná: operátor sa zapisuje bezprostredne za svojimi dvoma operandmi.
  
 
Výraz <tt>(65 – 3 * 5) / (2 + 3)</tt> má teda postfixový zápis
 
Výraz <tt>(65 – 3 * 5) / (2 + 3)</tt> má teda postfixový zápis
Riadok 40: Riadok 78:
 
</pre>
 
</pre>
  
Postfixová a prefixová notácia sú pre človeka o niečo ťažšie čitateľné, než bežná notácia infixová (čo môže byť aj otázkou zvyku). Uvidíme však, že najmä výrazy v postfixovej notácii sa dajú pomerne jednoducho vyhodnocovať. Postfixová a prefixová notácia majú oproti infixovej notácii ešte jednu výhodu &ndash; nepotrebujú zátvorky.
+
* Postfixová a prefixová notácia sú pre človeka o niečo ťažšie čitateľné, než bežná infixová notácia (čo môže byť aj otázkou zvyku).  
 +
* Uvidíme však, že výrazy v postfixovej notácii sa dajú jednoducho vyhodnocovať.  
 +
* Výhodou postfixovej a prefixovej notácie oproti infixovej je aj to, že nepotrebujú zátvorky.
 +
 
 +
=== Unárne mínus===
 +
 
 +
* Operátory +, * a  / sú vždy '''binárne''', čo znamená, že sa aplikujú sa na dva operandy.
 +
* Operátor - môže byť binárny aj '''unárny''' (unárny znamená, že sa aplikuje na jeden operand).
 +
** Príklad s binárnym mínus: <tt>1 - 2</tt>.
 +
** Príklad s unárnym mínus: <tt>2 * -(2 + 3)</tt>.
 +
* Nevýhodou postfixovej a prefixovej notácie je, že bez zátvoriek v nich nie je možné rozpoznať binárne a unárne mínus.
 +
** Prefixový výraz <tt>- - 2 3</tt> by napríklad mohol zodpovedať ako výrazu <tt>-2 - 3</tt> aj výrazu <tt>-(2 - 3)</tt>.
 +
* Na vyriešenie tohto problému budeme na unárne mínus používať znamienko <tt>~</tt> (iba interne v našom programe, nepoužíva sa tak všeobecne). V infixových výrazoch budú unárne mínus automaticky rozpoznané a nahradené <tt>~</tt>.
 +
 
 +
== Predspracovanie výrazu ==
 +
 
 +
Používateľ zadá výraz ako text v niektorej notácii. Aby sa nám s ním lepšie pracovalo, prevedieme ho najskôr na postupnosť symbolov (angl. '''token'''), pričom každý symbol bude buď číslo reprezentované v premennej typu <tt>double</tt> alebo znak reprezentujúci operátor alebo zátvorku.
 +
 
 +
Jednotlivé symboly budeme ukladať do záznamov typu <tt>token</tt>
 +
<syntaxhighlight lang="C++">
 +
struct token {
 +
    char op; 
 +
    double val;
 +
};
 +
</syntaxhighlight>
 +
* Ak štruktúra obsahuje číslo, <tt>op</tt> bude medzera a samotné číslo bude v položke <tt>val</tt>.
 +
* Ak štruktúra reprezentuje iný symbol, tento symbol bude v položke <tt>op</tt> a položka <tt>val</tt> sa nebude používať.
 +
 
 +
Postupnosť symbolov uložíme do štruktúry <tt>tokenSequence</tt>, ktorá obsahuje pole položiek typu <tt>token</tt> a tiež koľko položiek bolo do poľa uložených.
 +
<syntaxhighlight lang="C++">
 +
struct tokenSequence {
 +
    token * items;  // pole tokenov
 +
    int size;      // veľkosť alokovaného poľa
 +
    int length;    // počet tokenov uložených v poli
 +
};
 +
</syntaxhighlight>
 +
 
 +
Preklad výrazu na postupnosť symbolov realizuje funkcia <tt>tokenize</tt>.
 +
* Na načítanie čísla používame funkciu <tt>sscanf</tt>, ktorá vyzerá podobne ako <tt>scanf</tt>, ale načítava zo zadaného reťazca
 +
** Ako reťazec zadáme <tt>&(str[strPos])</tt>, čo spôsobí, že sa začne načítavať od pozície <tt>strPos</tt>, mohli by sme písať aj <tt>str+strPos</tt>.
 +
** Formátovací znak <tt>%n</tt> nám do premennej <tt>skip</tt> uloží počet znakov, ktoré sa pri načítaní čísla použili, tento o tento počet potom posunieme premennú <tt>strPos</tt>.
 +
* Kód token <tt>newToken = {str[strPos], 0}</tt> inicializuje položku <tt>op</tt> na znak <tt>str[strPos]</tt> a položku <tt>val</tt> na nulu.
 +
* Pomocné funkcie <tt>init</tt> a <tt>addToken</tt> pracujú s postupnosťou symbolov, nájdete ich v [[#Program_na_pr.C3.A1cu_s_v.C3.BDrazmi|programe na konci prednášky]].
 +
 
 +
<syntaxhighlight lang="C++">
 +
void tokenize(char * str, tokenSequence & tokens) {
 +
    init(tokens, strlen(str)); // inicializujeme prázdnu postupnosť
  
Nevýhodou naopak je, že tieto notácie nie sú konštruované na používanie unárneho operátora <tt>-</tt>. Ľahko napríklad vidieť, že prefixový výraz <tt>- - 2 3</tt> by mohol zodpovedať ako výrazu <tt>-2 - 3</tt>, tak aj výrazu <tt>-(2 - 3)</tt>. ''V nasledujúcom teda budeme predpokladať, že výrazy neobsahujú unárne mínus.'' Tento nedostatok nie je nijak závažný &ndash; každý infixový podvýraz typu <tt>-cokolvek</tt> možno totiž ekvivalentne prepísať ako <tt>(0 - 1) * cokolvek</tt>. (Alternatívne by sme v prefixových a postfixových výrazoch mohli pre unárne mínus zaviesť nejaký nový symbol.)
+
    int strPos = 0;  // pozícia v rámci reťazca
 +
    while (str[strPos] != 0) {  // kým nie sme na konci str
 +
        if (isspace(str[strPos])) {  // preskakujeme biele znaky
 +
            strPos++;
 +
        } else if (isdigit(str[strPos]) || str[strPos] == '.') {
 +
            // keď nájdeme cifru alebo bodku (začiatok čísla)
 +
            double val;
 +
            int skip;
 +
            // načítame toto číslo pomocou sscanf,  
 +
            // do skip uložíme počet znakov čísla
 +
            sscanf(&(str[strPos]), "%lf%n", &val, &skip);
 +
            // vytvoríme a uložíme token
 +
            token newToken = {' ', val};
 +
            addToken(tokens, newToken);
 +
            // preskočíme všetky znaky čísla
 +
            strPos += skip;
 +
        } else {
 +
            // spracovanie zátvoriek alebo operátora
 +
            assert(strchr("+-/*()~", str[strPos]) != NULL);
 +
            // vytvoríme a uložíme token
 +
            token newToken = {str[strPos], 0};
 +
            addToken(tokens, newToken);
 +
            strPos++;
 +
        }
 +
    }
 +
}
 +
</syntaxhighlight>
  
 
== Vyhodnocovanie aritmetických výrazov v postfixovej notácii ==
 
== Vyhodnocovanie aritmetických výrazov v postfixovej notácii ==
  
Na vyhodnocovanie výrazov v postfixovej forme budeme používať zásobník, do ktorého budeme postupne vkladať jednotlivé operandy. Využijeme pritom vlastnosť, že operátor má v postfixovom zápise obidva operandy pred sebou. Keď teda narazíme na operátor, jeho operandy sme už prečítali a spracovali. Ide navyše ''o posledné dve prečítané alebo vypočítané hodnoty''.
+
* Budeme používať '''zásobník''', do ktorého budeme vkladať '''čísla''' čakajúce na spracovanie.
 +
* Operátor má v postfixovom zápise obidva operandy pred sebou. Keď narazíme na operátor, jeho operandy sme už prečítali a spracovali. Ide navyše o '''posledné dve''' prečítané alebo vypočítané hodnoty.
  
 
Výraz budeme postupne čítať zľava doprava:
 
Výraz budeme postupne čítať zľava doprava:
* Keď pritom narazíme na operand, vložíme ho na zásobník.  
+
* Keď narazíme na číslo, vložíme ho na zásobník.  
 
* Keď narazíme na operátor:
 
* Keď narazíme na operátor:
 
** Vyberieme zo zásobníka jeho dva operandy.  
 
** Vyberieme zo zásobníka jeho dva operandy.  
 
** Prvé z týchto čísel je pritom druhým operandom a druhé z nich je prvým operandom.
 
** Prvé z týchto čísel je pritom druhým operandom a druhé z nich je prvým operandom.
 
** Vykonáme s týmito operandmi danú operáciu a výsledok tejto operácie vložíme naspäť na zásobník.  
 
** Vykonáme s týmito operandmi danú operáciu a výsledok tejto operácie vložíme naspäť na zásobník.  
* Tento postup opakujeme, až kým neprídeme na koniec výrazu. V tom okamihu by sme mali mať na zásobníku jediný prvok &ndash; výslednú hodnotu výrazu.  
+
* Pri unárnom mínus vyberáme zo zásobníka iba jeden operand.
 +
* Tento postup opakujeme, až kým neprídeme na koniec výrazu. V tom okamihu by sme mali mať na zásobníku jediný prvok, výslednú hodnotu výrazu.  
 +
 
 +
Technická poznámka:
 +
* Keďže vo [[#Program_na_pr.C3.A1cu_s_v.C3.BDrazmi|výslednom programe]] budeme potrebovať zásobníky čísel aj znakov, používame v ňom zásobník položiek typu <tt>token</tt>. Ak by sme však robili čisto vyhodnocovanie postfixovej notácie, stačil by nám zásobník s prvkami typu <tt>double</tt>.
 +
 
 +
 
 +
'''Cvičenie:'''
 +
* Odsimulujme činnosť tohto algoritmu na našom vstupe <tt>65 3 5 * - 2 3 + /</tt>
 +
* Aké všelijaké prípady môžu nastať, keď na vstupe nemáme správny výraz v postfixovej notácii?
 +
 
 +
 
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
#include <cstdio>
+
typedef token dataType;
#include <cctype>
+
// Nasleduje kód pre zásobník tokenov
#include <cassert>
+
// ...
using namespace std;
 
  
typedef double dataType;
 
  
 +
/** Funkcia aplikuje operátor uložený v opToken na čísla
 +
* uložené v num1 a num2, výsledok uloží ako číslo do result.
 +
* Unárny operátor sa aplikuje iba na num1. */
 +
void applyOp(token opToken, token num1, token num2, token & result) {
 +
    result.op = ' ';
 +
    switch (opToken.op) {
 +
    case '~':
 +
        result.val = -num1.val;
 +
        break;
 +
    case '+':
 +
        result.val = num1.val + num2.val;
 +
        break;
 +
    case '-':
 +
        result.val = num1.val - num2.val;
 +
        break;
 +
    case '*':
 +
        result.val = num1.val * num2.val;
 +
        break;
 +
    case '/':
 +
        result.val = num1.val / num2.val;
 +
        break;
 +
    default: // iné operátory nepovoľujeme.
 +
        assert(false);
 +
    }
 +
}
  
/* Sem pride definicia struktury pre zasobnik a funkcii poskytovanych zasobnikom. */
+
/** Funkcia vyhodnotí a vráti hodnotou výrazu v postfixovom tvare. */
 +
double evaluatePostfix(tokenSequence & tokens) {
 +
    // zásobník, do ktorého ukladáme čísla
 +
    stack numberStack;
 +
    init(numberStack);
  
 
+
    for (int i = 0; i < tokens.length; i++) {
int main(void) {
+
         // aktuálny token zo vstupu
    FILE *fr = stdin;
+
         token curToken = tokens.items[i];
       
+
         if (curToken.op == ' ') {
    stack s;
+
             // čísla rovno ukladáme na zásobník
    init(s);
+
             push(numberStack, curToken);
   
 
    while (true) {
 
         int c = getc(fr);
 
         while (c != EOF && c != '\n' && isspace(c)) {
 
            c = getc(fr);
 
        }
 
         if (c == EOF || c == '\n') {
 
             break;
 
        } else if (isdigit(c)) {
 
            ungetc(c, fr);
 
            double num;
 
            fscanf(fr, "%lf", &num);
 
             push(s, num);
 
 
         } else {
 
         } else {
             double num2 = pop(s);        // druhy operand vyberieme ako prvy ...
+
             // spracovanie operátora
             double num1 = pop(s);         // ... a prvy operand ako druhy
+
             token num1, num2, result;
             switch (c) {
+
            // najskôr vyberieme 1 alebo 2 čísla zo zásobníka
                 case '+':
+
             if (curToken.op == '~') {
                    push(s, num1 + num2);
+
                 num1 = pop(numberStack);
                    break;
+
            } else {
                 case '-':
+
                 num2 = pop(numberStack);
                    push(s, num1 - num2);
+
                 num1 = pop(numberStack);
                    break;
 
                 case '*':
 
                    push(s, num1 * num2);
 
                    break;   
 
                case '/':
 
                    push(s, num1 / num2);
 
                    break;   
 
 
             }
 
             }
 +
            // na operandy aplikujeme operátor
 +
            applyOp(curToken, num1, num2, result);
 +
            // výsledné číslo uložíme na zásobník
 +
            push(numberStack, result);
 
         }
 
         }
 
     }
 
     }
      
+
     // zo zásobníka vyberieme výsledné číslo
     printf("%f\n", pop(s));
+
     token result = pop(numberStack);
     assert(isEmpty(s));
+
    // skontrolujeme, že zásobník je prázdny a výsledok je číslo
 
+
     assert(isEmpty(numberStack) && result.op == ' ');
     destroy(s);
+
    // uvoľníme pamäť zásobníka
     return 0;
+
     destroy(numberStack);
 +
     return result.val;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Nasledujúca alternatívna implementácia vyhodnocovania postfixových výrazov sa od predchádzajúcej líši tým, že vstupný výraz najprv uloží do reťazca, ktorý následne vyhodnotí prostredníctvom funkcie <tt>evaluatePostfix</tt>. Drobnou nevýhodou tohto prístupu je, že dĺžka vstupného reťazca je obmedzená konštantou; funkcia vyhodnocujúca výraz uložený v reťazci sa nám však neskôr zíde.
+
== Prevod výrazu z infixovej do postfixovej notácie ==
 +
 
 +
* Pre bežnú prax je dôležitejšie vyhodnocovanie výrazov v klasickej infixovej notácii.
 +
* Túto úlohu teraz vyriešime tak, že napíšeme funkciu, ktorá preloží aritmetický výraz z infixovej do postfixovej notácie.
 +
* Následne ho môžeme vyhodnotiť algoritmom popísaným vyššie.
 +
 
 +
=== Popis algoritmu ===
 +
 
 +
Uvažujme najprv výrazy bez zátvoriek a bez unárnych mínusov.
 +
* Pozostávajú teda z čísel a binárnych operátorov <tt>+, -, *, /</tt>, kde <tt>*</tt> a <tt>/</tt> majú vyššiu prioritu ako <tt>+</tt> a <tt>-</tt>.  
 +
* Všetky tieto operátory sú navyše zľava asociatívne, t.j. napr. <tt>30 / 6 / 5</tt> je to isté ako  <tt>(30 / 6) / 5</tt>.  
  
<syntaxhighlight lang="C++">
+
Na prevod takéhoto výrazu do postfixového tvaru ním stačí prejsť zľava doprava a všimnúť si dve skutočnosti:
#include <cstdio>
+
* Poradie jednotlivých čísel v postfixovom výraze je rovnaké, ako v pôvodnom infixovom výraze.
#include <cctype>
+
* Napríklad výraz <tt>1 + 2 - 3 * 4 + 5</tt> má postfixový tvar <tt>1 2 + 3 4 * - 5 +</tt>.
#include <cassert>
+
* Pri prechádzaní vstupným infixovým výrazom budeme teda čísla priamo pridávať do výstupného postfixového výrazu.
using namespace std;
+
* Každý operátor treba presunúť spomedzi jeho dvoch operandov za jeho druhý operand.
 +
* Ak teda vo vstupnom infixovom výraze narazíme na operátor, nepridáme ho hneď do výstupného výrazu, ale uložíme ho pre neskoršie pridanie na správnej pozícii.
 +
* Uložený operátor treba vypísať za jeho druhým operandom. Ak pritom aj samotný operand obsahuje nejaké ďalšie operátory, určite musia byť vypísané skôr. Operátory teda budeme ukladať na zásobník.
 +
* Keď vo vstupnom infixovom výraze narazíme na operátor, je možné, že tesne pred ním končí operand jedného alebo niekoľkých operátorov uložených na zásobníku. Vďaka ľavej asociatívnosti pritom ide o práve všetky operátory na vrchu zásobníka, ktoré majú vyššiu alebo rovnakú prioritu, ako nájdený operátor. Všetky tieto operátory teda postupne vyberieme zo zásobníka a vypíšeme ich do výstupného reťazca. Až následne na zásobník vložíme nájdený operátor.
 +
* Na konci vstupného reťazca vypíšeme všetky operátory, ktoré zostali na zásobníku.
 +
 
 +
Z technických dôvodov budeme na spodku zásobníka uchovávať &bdquo;umelé dno&rdquo; <tt>#</tt>, ktoré budeme chápať ako symbol s nižšou prioritou ako všetky operátory.
 +
* Situáciu, keď pri vyberaní operátorov zo zásobníka narazíme na jeho dno tak budeme môcť riešiť konzistentne so situáciou, keď  narazíme na operátor s nižšou prioritou: v oboch prípadoch chceme s vyberaním prestať. 
 +
 
 +
Na vstupnom výraze <tt>1 + 2 - 3 * 4 + 5</tt> bude práve popísaný algoritmus pracovať nasledovne:
 +
<pre>
 +
Vstupný symbol  Výstupný výraz    Zásobník (dno vľavo)
 +
-------------------------------------------------------------------------------------------------------
 +
          1                      #            pridaj 1 na výstup
 +
                1                  #
 +
 
 +
          +    1                  #            # má nižšiu prioritu ako +, nevyberaj ho
 +
                1                  # +          vlož + na zásobník
 +
 
 +
          2    1                  # +          pridaj 2 na výstup
 +
                1 2                # +
 +
 
 +
          -    1 2                # +          + má rovnakú prioritu ako +, vyber + a pridaj na výstup
 +
                1 2 +              #            # má nižšiu prioritu ako +, nevyberaj ho
 +
                1 2 +              #            vlož - na zásobník
 +
                1 2 +              # - 
 +
 
 +
          3    1 2 +              # -          pridaj 3 na výstup
 +
                1 2 + 3            # -
 +
 
 +
          *    1 2 + 3            # -          - má nižšiu prioritu ako *, nevyberaj ho
 +
                1 2 + 3            # - *        vlož * na zásobník
 +
 
 +
          4    1 2 + 3            # - *        pridaj 4 na výstup
 +
                1 2 + 3 4          # - *                   
 +
 
 +
          +    1 2 + 3 4          # - *        * má vyššiu prioritu ako +, vyber * a vypíš
 +
                1 2 + 3 4 *        # -          - má rovnakú prioritu ako +, vyber - a vypíš
 +
                1 2 + 3 4 * -      #            # má nižšiu prioritu ako +, nevyberaj ho
 +
                1 2 + 3 4 * -      # +          vlož - na zásobník
 +
 
 +
          5    1 2 + 3 4 * -      # +          pridaj 5 na výstup
 +
                1 2 + 3 4 * - 5    # +               
 +
 
 +
    [koniec]    1 2 + 3 4 * - 5    # +          vyber operátory na zásobníku a pridaj na výstup
 +
                1 2 + 3 4 * - 5 +  #            <-- VÝSLEDNÝ POSTFIXOVÝ VÝRAZ
 +
</pre>  
 +
 
 +
Pridanie zátvoriek:
 +
* Keď vo vstupnom infixovom reťazci narazíme na ľavú zátvorku, vložíme ju do zásobníka. Podobne ako pri <tt>#</tt> ju budeme považovať za symbol s nižšou prioritou, než všetky binárne operátory (argument operátora spred tejto zátvorky totiž určite nemôže končiť v jej vnútri).
 +
* Keď narazíme na pravú zátvorku, potrebujeme vypísať všetky doposiaľ nevypísané operátory uzatvorené touto zátvorkou. Preto vyberieme a vypíšeme na výstup všetky operátory zo zásobníka až po prvú ľavú zátvorku (ktorú zo zásobníka taktiež vyberieme).
 +
 
 +
Pridanie unárneho mínus:
 +
* Unárne mínus má vyššiu prioritu ako binárne operátory. Predpokladáme, že ho máme zapísané ako ~.
 +
* Je však sprava asociatívne, t.j. --2 je to isté ako -(-2) a v postfixovom tvare to bude 2 ~ ~
 +
* Preto ak je aktuálny operátor unárne mínus (~), nevyhadzujeme iné unárne mínus zo zásobníka. Toto je v programe dosiahnuté tak, že nové unárne mínus ("pravé") má vyššiu prioritu ako to, čo už je na zásobníku ("ľavé").
  
typedef double dataType;
+
=== Implementácia ===
  
 +
<syntaxhighlight lang="C++">
 +
/** Pomocná funkcia, ktorá vráti prioritu operátora.
 +
* Argument right určuje, či ide o pravý z dvoch porovnávaných operátorov */
 +
int priority(char op, bool right) {
 +
    if (op == '#' || op == '(') return 0;
 +
    if (op == '-' || op == '+') return 1;
 +
    if (op == '*' || op == '/') return 2;
 +
    if (op == '~' && !right) return 3;
 +
    if (op == '~' && right) return 4;
 +
    assert(false); // sem by sme sa nemali dostať
 +
}
  
/* Sem pride definicia struktury pre zasobnik a funkcii poskytovanych zasobnikom. */
+
/** Skonvertuje infixový výraz infix do postfixovej formy */
 +
void infixToPostfix(tokenSequence & infix, tokenSequence & postfix) {
 +
    // inicializuje výslednú postupnosť tokenov
 +
    init(postfix, infix.length);
  
 +
    // vytvoríme zásobník operátorov a na spodok dáme #
 +
    stack opStack;
 +
    init(opStack);
 +
    token curToken = {'#', 0};
 +
    push(opStack, curToken);
  
double evaluatePostfix(char *expr) {
+
     for (int i = 0; i < infix.length; i++) {
    stack s;
+
         // aktuálny token zo vstupu
     init(s);
+
        curToken = infix.items[i];
   
+
        if (curToken.op == ' ') {
    int i = 0;
+
             // čísla rovno skopírujeme do výstupu
    while (true) {
+
            addToken(postfix, curToken);
         while (expr[i] != 0 && expr[i] != '\n' && isspace(expr[i])) {
+
         } else if (curToken.op == '(') {
             i++;
+
             // začiatok zátvorky uložíme na zásobník
         }
+
            push(opStack, curToken);
        if (expr[i] == 0 || expr[i] == '\n') {
+
         } else if (curToken.op == ')') {
             break;
+
             // koniec zátvorky:
         } else if (isdigit(expr[i])) {
+
             // na výstup pridáme zo zásobníka operátory,
             double num;
+
             // ktoré boli v zátvorke
             sscanf(&expr[i], "%lf", &num);
+
            token popped = pop(opStack);
             push(s, num);
+
             while (popped.op != '(') {
             while (isdigit(expr[i]) || expr[i] == '.') {
+
                 addToken(postfix, popped);
                 i++;
+
                popped = pop(opStack);
             }  
+
             }
 
         } else {
 
         } else {
             double num2 = pop(s);        // druhy operand vyberieme ako prvy ...
+
             // spracovanie operátora:
             double num1 = pop(s);        // ... a prvy operand ako druhy
+
             // na výstup pridáme zo zásobníka operátory s vyššou prioritou
             switch (expr[i]) {
+
             int p = priority(curToken.op, true);
                case '+':
+
            while (priority(peek(opStack).op, false) >= p) {
                    push(s, num1 + num2);
+
                 token popped = pop(opStack);
                    break;
+
                 addToken(postfix, popped);
                case '-':
 
                    push(s, num1 - num2);
 
                    break;
 
                 case '*':
 
                    push(s, num1 * num2);
 
                    break;   
 
                 case '/':
 
                    push(s, num1 / num2);
 
                    break;   
 
 
             }
 
             }
             i++;
+
             // aktuálny operátor dáme na zásobník
 +
            push(opStack, curToken);
 
         }
 
         }
 
     }
 
     }
    double result = pop(s);
 
    assert(isEmpty(s));
 
    destroy(s);
 
   
 
    return result;
 
}
 
  
int main(void) {
+
    // zvyšné operátory presunieme zo zásobníka na výstup
    FILE *fr = stdin;
+
    while (peek(opStack).op != '#') {
       
+
        token popped = pop(opStack);
    char expr[100];   
+
        addToken(postfix, popped);
    fgets(expr, 100, fr);
+
     }
    printf("%f\n", evaluatePostfix(expr));  
+
     destroy(opStack);
      
 
     return 0;
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Prevod výrazu z infixovej notácie do postfixovej ==
+
''Cvičenie'': Rozšírte program vyššie o operáciu umocňovania <tt>^</tt> s prioritou vyššou ako <tt>*</tt> (dajte si pritom pozor na fakt, že umocňovanie je na rozdiel od operácií <tt>+, -, *, /</tt> sprava asociatívne).
  
Oveľa zaujímavejšou a dôležitejšou úlohou, než vyhodnocovanie postfixových výrazov, je vyhodnocovanie výrazov v klasickej infixovej notácii. V nasledujúcom túto úlohu vyriešime tým, že napíšeme program realizujúci ''prevod'' aritmetického výrazu z infixovej notácie do postfixovej. Výraz v infixovej notácii teda budeme môcť vyhodnotiť tak, že ho najprv prevedieme do postfixovej notácie a tento postfixový výraz následne vyhodnotíme algoritmom popísaným vyššie. (Elegantnejšie prístupy k vyhodnocovaniu aritmetických výrazov vyžadujú istú dávku teórie, s ktorou sa typický študent informatiky zoznámi vo vyšších ročníkoch štúdia.)
 
  
=== Algoritmus pre infixové výrazy neobsahujúce unárne mínus ===
+
== Predspracovanie unárneho mínus ==
  
Začneme tým, že prevod do postfixovej notácie realizujeme pre infixové výrazy ''neobsahujúce unárny operátor mínus'' &ndash; ako sme už totiž videli, prítomnosť unárneho mínusu vo výslednom postfixovom výraze by mohla viesť k nejednoznačnostiam.
+
* Pre prevodom infixového výrazu do postfixového tvaru potrebujeme v infixovom výraze nahradiť unárne mínus znakom <tt>~</tt>.
 +
* Robí to funkcia <tt>fixUnaryMinus</tt>.
 +
* Mínus je v korektnom infixovom výraze unárne práve vtedy, keď nenasleduje za číslom, ani za pravou zátvorkou, čo testuje pomocná funkcia <tt>isOperand</tt>.
 +
 
 +
<syntaxhighlight lang="C++">
 +
/** Pomocná funkcia, ktorá vráti, či token je koncom operandu v infixovej notácii */
 +
bool isOperand(token curToken) {
 +
    return curToken.op == ' ' || curToken.op == ')';
 +
}
  
Uvažujme najprv ''výrazy bez zátvoriek''. Tie pozostávajú z čísel a binárnych operátorov <tt>+, -, *, /</tt>, kde <tt>*</tt> a <tt>/</tt> majú vyššiu prioritu (precedenciu) ako <tt>+</tt> a <tt>-</tt>. Všetky tieto operátory sú navyše zľava asociatívne. Na prevod takéhoto jednoduchého výrazu do postfixového tvaru ním teda stačí prejsť zľava doprava a všimnúť si dve skutočnosti:
+
/** Funkcia, ktorá v infixovom výraze zmení unárne mínus na ~ */
* Poradie jednotlivých čísel v postfixovom výraze je rovnaké, ako v pôvodnom infixovom výraze.
+
void fixUnaryMinus(tokenSequence & infix) {
** Napríklad výraz <tt>1 + 2 + 3 * 4 - 5</tt> má postfixový tvar <tt>1 2 + 3 4 * + 5 -</tt>.  
+
    for (int i = 0; i < infix.length; i++) {
** Z toho vyplýva, že pri prechádzaní vstupným infixovým výrazom možno čísla priamo vypisovať do výstupného postfixového výrazu bez toho, aby nás ďalej zaujímali.
+
        if(infix.items[i].op == '-'  
* Každý operátor treba presunúť spomedzi jeho dvoch argumentov ''za jeho druhý argument''.
+
          && (i == 0 || !isOperand(infix.items[i - 1]))) {
** Ak teda vo vstupnom infixovom výraze narazíme na operátor, nevypíšeme ho hneď do výstupného výrazu, ale uložíme ho pre neskorší výpis na správnej pozícii.
+
            infix.items[i].op = '~';
** Uložený operátor potom treba vypísať za jeho druhým argumentom. Ak pritom aj samotný tento argument obsahuje nejaké ďalšie operátory, určite musia byť vypísané skôr. Operátory teda budeme ukladať na zásobníku.
+
        }
** Zakaždým, keď vo vstupnom infixovom výraze narazíme na operátor, je možné, že tesne pred ním končí argument jedného alebo niekoľkých operátorov uložených na zásobníku. Vďaka ľavej asociatívnosti pritom ide o ''práve všetky operátory na vrchu zásobníka, ktoré majú vyššiu alebo rovnakú prioritu'', ako nájdený operátor. Všetky tieto operátory teda postupne vyberieme zo zásobníka a vypíšeme ich do výstupného reťazca. Až následne na zásobník vložíme nájdený operátor.
+
    }
** Podobnú činnosť treba vykonať aj na konci vstupného reťazca &ndash; v takom prípade musíme vypísať ''všetky'' operátory na zásobníku.
+
}
 +
</syntaxhighlight>
  
Z technických dôvodov budeme na spodku zásobníka uchovávať &bdquo;umelé dno&rdquo; <tt>#</tt>, ktoré budeme chápať ako symbol s nižšou prioritou ako všetky operátory. Situáciu, keď pri vyberaní operátorov zo zásobníka narazíme na jeho dno tak budeme môcť riešiť konzistentne so situáciou, keď  narazíme na operátor s nižšou prioritou &ndash; v oboch prípadoch chceme s vyberaním prestať. 
+
==Záverečné poznámky==
  
Na vstupnom výraze <tt>1 + 2 + 3 * 4 - 5</tt> bude práve popísaný algoritmus pracovať nasledovne:
+
* Infixové výrazy vieme vyhodnotiť tak, že najskôr ich prevedieme na postfixové a tie potom vyhodnotíme.
<pre>
+
** Pri vyhodnocovaní postfixovej formy používame zásobník čísel (operandov).
Krok    Vstupný symbol    Výstupný reťazec    Zásobník (dno naľavo)     
+
** Pri prevode z infixovej formy na postfixovú používame zásobník operátorov.
------------------------------------------------------------------------------------------------------------------------
+
* Obidva procesy sa dajú vykonávať aj súčasne (počas prechodu výrazom používame jeden zásobník čísel a jeden zásobník operátorov).
  1                  1                          #                        vypíš číslo 1 na výstup
+
** Toto je pre zaujímavosť implementované vo funkcii <tt>evaluateInfix</tt> v [[#Program_na_pr.C3.A1cu_s_v.C3.BDrazmi|priloženom programe]].
                            1                    #
+
* Všeobecnejšie a elegantnejšie prístupy k analýze a vyhodnocovaniu výrazov ale aj celých programov uvidíte na predmete Kompilátory (Mgr. štúdium), ktorý využíva poznatky z predmetu Formálne jazyky a automaty (semester 2Z).
  2                  +    1                    #                        # má nižšiu prioritu ako +, nevyberaj ho
+
*  
                      +    1                    #                        vlož + na zásobník
 
                            1                    # +
 
  3                  2    1                    # +                      vypíš číslo 2 na výstup
 
                            1 2                  # +
 
  4                  +    1 2                  # +                      + má rovnakú prioritu ako +, vyber ho a vypíš
 
                      +    1 2 +                #                        # má nižšiu prioritu ako +, nevyberaj ho
 
                      +    1 2 +                #                        vlož + na zásobník
 
                            1 2 +                # + 
 
  5                  3    1 2 +                # +                      vypíš číslo 3 na výstup
 
                            1 2 + 3              # +
 
  6                  *     1 2 + 3              # +                      + má nižšiu prioritu ako *, nevyberaj ho
 
                      *     1 2 + 3              # +                      vlož * na zásobník
 
                            1 2 + 3              # + *    
 
  7                  4    1 2 + 3              # + *                     vypíš číslo 4 na výstup
 
                            1 2 + 3 4            # + *                   
 
  8                  -    1 2 + 3 4            # + *                     * má vyššiu prioritu ako -, vyber ho a vypíš
 
                      -    1 2 + 3 4 *          # +                      + má rovnakú prioritu ako -, vyber ho a vypíš
 
                      -    1 2 + 3 4 * +        #                        # má nižšiu prioritu ako -, nevyberaj ho
 
                      -    1 2 + 3 4 * +        #                        vlož - na zásobník
 
                            1 2 + 3 4 * +        # -           
 
  9                  5    1 2 + 3 4 * +        # -                      vypíš číslo 5 na výstup
 
                            1 2 + 3 4 * + 5      # -               
 
  10    [koniec vstupu]    1 2 + 3 4 * + 5      # -                      vyber a vypíš operátory na zásobníku
 
  
                            1 2 + 3 4 * + 5 -    #                        <-- VÝSLEDNÝ POSTFIXOVÝ VÝRAZ
+
=== Typ dát na zásobníku ===
                         
 
</pre>
 
  
Do tejto schémy je už jednoduché zapracovať aj ''zátvorky'':
+
V našom programe sme potrebovali zásobník čísel aj zásobník znakov.
* Zakaždým, keď vo vstupnom infixovom reťazci narazíme na ľavú zátvorku, vložíme ju do zásobníka. Podobne ako pri <tt>#</tt> ju budeme považovať za symbol s nižšou prioritou, než majú všetky binárne operátory (argument operátora spred tejto zátvorky totiž určite nemôže končiť v jej vnútri).  
+
* Vyriešili sme to tým, že na zásobník dávame položky typu token, ktoré obsahujú znak aj číslo.  
* Keď naopak narazíme na pravú zátvorku, potrebujeme vypísať všetky doposiaľ nevypísané operátory uzatvorené touto zátvorkou. Preto v takom prípade vyberieme a vypíšeme na výstup všetky operátory zo zásobníka až po prvú ľavú zátvorku (ktorú zo zásobníka taktiež vyberieme).
+
* Nie je to však príliš elegantné a plytve pamäťou na nepotrebné údaje.
 +
* Druhá možnosť je dvakrát implementovať celý zásobník pre rôzne typy dát, kopírovanie a menenie kódu je však tiež niečo, čomu sa chceme vyhnúť.
 +
* Najlepšie by bolo použiť techniku generického programovania, ktorá je k dispozícii vo veľa jazykoch, vrátane C++, ale nie C. Touto technikou vieme zásobník implementovať raz a použiť s rôznymi typmi. Ukážku tejto techniky uvidíme na poslednej prednáške a viac sa dozviete na Programovaní (2) v letnom semestri.
  
Funkcia <tt>infixToPostfix</tt>, realizujúca prevod z infixovej do postfixovej notácie, tak môže vyzerať napríklad nasledovne:
+
==Program na prácu s výrazmi==
  
 
<syntaxhighlight lang="C++">
 
<syntaxhighlight lang="C++">
 
#include <cstdio>
 
#include <cstdio>
 +
#include <cctype>
 
#include <cassert>
 
#include <cassert>
#include <cctype>
+
#include <cstring>
 
using namespace std;
 
using namespace std;
  
typedef char dataType;
+
/** Štruktúra pre jednu súčasť výrazu: znamienko alebo číslo */
 +
struct token {
 +
    char op;    // znamienko alebo medzera, ak ide o číslo
 +
    double val;  // číselná hodnota, ak je op medzera
 +
};
  
 +
/** Štruktúra pre postupnosť tokenov */
 +
struct tokenSequence {
 +
    token * items;  // pole tokenov
 +
    int size;      // veľkosť alokovaného poľa
 +
    int length;    // počet tokenov uložených v poli
 +
};
  
/* Sem pride definicia struktury pre zasobnik a funkcii poskytovanych zasobnikom. */
+
/** Funkcia inicializuje prázdnu postupnosť tokenov
 +
pričom alokuje pole požadovanej veľkosti. */
 +
void init(tokenSequence & tokens, int size) {
 +
    tokens.items = new token[size];
 +
    tokens.size = size;
 +
    tokens.length = 0;
 +
}
  
 +
/** Funkcia do postupnosti tokenov pridá nový token. */
 +
void addToken(tokenSequence & tokens, token & newToken) {
 +
    assert(tokens.length < tokens.size);
 +
    tokens.items[tokens.length] = newToken;
 +
    tokens.length++;
 +
}
  
int precedence(char op) {
+
/** Funkcia odalokuje pamäť alokovanú pre postupnosť tokenov */
     switch (op) {
+
void destroy(tokenSequence & tokens) {
         case '#':
+
    delete[] tokens.items;
         case '(':
+
}
             return 0;
+
 
             break;
+
/** Funkcia vypíše postupnosť tokenov, každý v hranatých zátvorkách. */
         case '+':
+
void printTokens(tokenSequence & tokens) {
        case '-':
+
     for (int i = 0; i < tokens.length; i++) {
            return 1;
+
         token curToken = tokens.items[i];
            break;
+
         if (curToken.op == ' ') {
         case '*':
+
             printf(" [val %g]", curToken.val);
         case '/':
+
        } else {
             return 2;
+
             printf(" [op %c]", curToken.op);
             break;
+
         }
 +
    }
 +
    printf("\n");
 +
}
 +
 
 +
/** Funkcia vypíše postupnosť tokenov ako aritmetický výraz. */
 +
void printTokenExpression(tokenSequence & tokens) {
 +
    for (int i = 0; i < tokens.length; i++) {
 +
         token curToken = tokens.items[i];
 +
         if (curToken.op == ' ') {
 +
             printf(" %g", curToken.val);
 +
        } else {
 +
             printf(" %c", curToken.op);
 +
        }
 
     }
 
     }
     return -1; // specialne pre biele znaky vrati -1
+
     printf("\n");
 
}
 
}
  
/* Skonvertuje infixovy vyraz infix to postfixovej formy a vysledok ulozi do retazca postfix */
+
/** Funkcia konvertuje výraz z reťazca na postupnosť tokenov. */
void infixToPostfix(const char *infix, char *postfix) {
+
void tokenize(char * str, tokenSequence & tokens) {
    stack s;
+
     init(tokens, strlen(str)); // inicializujeme prázdnu postupnosť
     init(s);
+
 
    push(s, '#');
+
     int strPos = 0; // pozícia v rámci reťazca
   
+
     while (str[strPos] != 0) { // kým nie sme na konci str
     int j = 0; // index do vystupneho retazca
+
         if (isspace(str[strPos])) { // preskakujeme biele znaky
     for (int i = 0; infix[i] != 0; i++) {
+
             strPos++;
         if (isdigit(infix[i]) || infix[i] == '.') {
+
         } else if (isdigit(str[strPos]) || str[strPos] == '.') {
             postfix[j++] = infix[i];
+
             // keď nájdeme cifru alebo bodku (začiatok čísla)
         }  
+
            double val;
        switch (infix[i]) {
+
            int skip;
             case '(':
+
             // načítame toto číslo pomocou sscanf, do skip uložíme počet znakov čísla
                push(s, '(');
+
            sscanf(&(str[strPos]), "%lf%n", &val, &skip);
                break;
+
            // vytvoríme a uložíme token
             case ')':
+
            token newToken = {' ', val};
                char c;
+
             addToken(tokens, newToken);
                while ((c = pop(s)) != '(') {
+
            // preskočíme všetky znaky čísla
                    postfix[j++] = ' ';
+
            strPos += skip;
                    postfix[j++] = c;
+
        } else {
                }
+
            // spracovanie zátvoriek alebo operátora
                break;
+
            assert(strchr("+-/*()~", str[strPos]) != NULL);
             default:
+
            // vytvoríme a uložíme token
                int p = precedence(infix[i]);
+
            token newToken = {str[strPos], 0};
                if (p == -1) {                      // predovsetkym biele znaky
+
            addToken(tokens, newToken);
                    break;
+
            strPos++;
                }
 
                postfix[j++] = ' ';
 
                while (p <= precedence(peek(s))) {
 
                    postfix[j++] = pop(s);
 
                    postfix[j++] = ' ';
 
                }
 
                push(s, infix[i]);
 
                break;
 
 
         }
 
         }
 
     }
 
     }
 +
}
 +
 +
// Nasleduje kód pre zásobník tokenov
 +
typedef token dataType;
 +
 +
/** Uzol spájaného zoznamu pre zásobník */
 +
struct node {
 +
    dataType data; // dáta uložené v uzle
 +
    node * next;  // smerník na ďalší uzol zoznamu
 +
};
 +
 +
/** Štruktúra pre zásobník implementovaný pomocou zoznamu*/
 +
struct stack {
 +
    node * top; // Smernik na vrch zasobníka  alebo NULL
 +
};
 +
 +
/** Funkcia inicializuje prázdny zásobník */
 +
void init(stack & s) {
 +
    s.top = NULL;
 +
}
 +
 +
/** Funkcia zistí, či je zásobník prázdny */
 +
bool isEmpty(stack & s) {
 +
    return s.top == NULL;
 +
}
 +
 +
/** Funkcia pridá prvok item na vrch zásobníka */
 +
void push(stack & s, dataType item) {
 +
    node * tmp = new node;
 +
    tmp->data = item;
 +
    tmp->next = s.top;
 +
    s.top = tmp;
 +
}
 +
 +
/** Funkcia odoberie prvok z vrchu zasobnika a vráti ho */
 +
dataType pop(stack & s) {
 +
    assert(!isEmpty(s));
 +
    dataType result = s.top->data;
 +
    node * tmp = s.top->next;
 +
    delete s.top;
 +
    s.top = tmp;
 +
    return result;
 +
}
 +
 +
/** Funkcia vráti prvok na vrchu zásobníka, ale nechá ho v zásobníku */
 +
dataType peek(stack & s) {
 +
    assert(!isEmpty(s));
 +
    return s.top->data;
 +
}
 +
 +
/** Funkcia uvoľní pamäť zásobníka */
 +
void destroy(stack & s) {
 
     while (!isEmpty(s)) {
 
     while (!isEmpty(s)) {
         char c = pop(s);
+
         pop(s);
        if (c != '#') {
+
    }
            postfix[j++] = ' ';
+
}
            postfix[j++] = c;
+
 
 +
/** Funkcia aplikuje operátor uložený v opToken na čísla
 +
* uložené v num1 a num2, výsledok uloží ako číslo do result.
 +
* Unárny operátor sa aplikuje iba na num1. */
 +
void applyOp(token opToken, token num1, token num2, token & result) {
 +
    result.op = ' ';
 +
    switch (opToken.op) {
 +
    case '~':
 +
        result.val = -num1.val;
 +
        break;
 +
    case '+':
 +
        result.val = num1.val + num2.val;
 +
        break;
 +
    case '-':
 +
        result.val = num1.val - num2.val;
 +
        break;
 +
    case '*':
 +
        result.val = num1.val * num2.val;
 +
        break;
 +
    case '/':
 +
        result.val = num1.val / num2.val;
 +
        break;
 +
    default: // iné operátory nepovoľujeme.
 +
        assert(false);
 +
    }
 +
}
 +
 
 +
/** Funkcia vyhodnotí a vráti hodnotou výrazu v postfixovom tvare. */
 +
double evaluatePostfix(tokenSequence & tokens) {
 +
    // zásobník, do ktorého ukladáme čísla
 +
    stack numberStack;
 +
    init(numberStack);
 +
 
 +
    for (int i = 0; i < tokens.length; i++) {
 +
        // aktuálny token zo vstupu
 +
        token curToken = tokens.items[i];
 +
        if (curToken.op == ' ') {
 +
            // čísla rovno ukladáme na zásobník
 +
            push(numberStack, curToken);
 +
        } else {
 +
            // spracovanie operátora
 +
            token num1, num2, result;
 +
            // najskôr vyberieme 1 alebo 2 čísla zo zásobníka
 +
            if (curToken.op == '~') {
 +
                num1 = pop(numberStack);
 +
            } else {
 +
                num2 = pop(numberStack);
 +
                num1 = pop(numberStack);
 +
            }
 +
            // na operandy aplikujeme operátor
 +
            applyOp(curToken, num1, num2, result);
 +
            // výsledné číslo uložíme na zásobník
 +
            push(numberStack, result);
 
         }
 
         }
 
     }
 
     }
     postfix[j] = 0;
+
     // zo zásobníka vyberieme výsledné číslo
      
+
    token result = pop(numberStack);
     destroy(s);
+
    // skontrolujeme, že zásobník je prázdny a výsledok je číslo
 +
    assert(isEmpty(numberStack) && result.op == ' ');
 +
     // uvoľníme pamäť zásobníka
 +
     destroy(numberStack);
 +
    return result.val;
 
}
 
}
  
const int maxN = 100;
+
/** Pomocná funkcia, ktorá vráti prioritu operátora.
 +
* Argument right určuje, či ide o pravý z dvoch porovnávaných operátorov */
 +
int priority(char op, bool right) {
 +
    if (op == '#' || op == '(') return 0;
 +
    if (op == '-' || op == '+') return 1;
 +
    if (op == '*' || op == '/') return 2;
 +
    if (op == '~' && !right) return 3;
 +
    if (op == '~' && right) return 4;
 +
    assert(false); // sem by sme sa nemali dostať
 +
}
  
int main(void) {
+
/** Skonvertuje infixový výraz infix do postfixovej formy */
     char infix[maxN];
+
void infixToPostfix(tokenSequence & infix, tokenSequence & postfix) {
     char postfix[2 * maxN];
+
     // inicializuje výslednú postupnosť tokenov
      
+
    init(postfix, infix.length);
     fgets(infix, maxN, stdin);
+
 
    infixToPostfix(infix, postfix);
+
    // vytvoríme zásobník operátorov a na spodok dáme #
      
+
    stack opStack;
     printf("Postfixovy tvar vyrazu: %s\n", postfix);
+
    init(opStack);
      
+
     token curToken = {'#', 0};
     return 0;
+
     push(opStack, curToken);
 +
 
 +
     for (int i = 0; i < infix.length; i++) {
 +
        // aktuálny token zo vstupu
 +
        curToken = infix.items[i];
 +
        if (curToken.op == ' ') {
 +
            // čísla rovno skopírujeme do výstupu
 +
            addToken(postfix, curToken);
 +
        } else if (curToken.op == '(') {
 +
            // začiatok zátvorky uložíme na zásobník
 +
            push(opStack, curToken);
 +
        } else if (curToken.op == ')') {
 +
            // koniec zátvorky:
 +
            // na výstup pridáme zo zásobníka operátory,
 +
            // ktoré boli v zátvorke
 +
            token popped = pop(opStack);
 +
            while (popped.op != '(') {
 +
                addToken(postfix, popped);
 +
                popped = pop(opStack);
 +
            }
 +
        } else {
 +
            // spracovanie operátora:
 +
            // na výstup pridáme zo zásobníka operátory s vyššou prioritou
 +
            int p = priority(curToken.op, true);
 +
            while (priority(peek(opStack).op, false) >= p) {
 +
                token popped = pop(opStack);
 +
                addToken(postfix, popped);
 +
            }
 +
            // aktuálny operátor dáme na zásobník
 +
            push(opStack, curToken);
 +
        }
 +
     }
 +
 
 +
     // zvyšné operátory presunieme zo zásobníka na výstup
 +
    while (peek(opStack).op != '#') {
 +
        token popped = pop(opStack);
 +
        addToken(postfix, popped);
 +
     }
 +
     destroy(opStack);
 
}
 
}
</syntaxhighlight>
 
  
''Cvičenie'': Rozšírte program vyššie o operáciu umocňovania <tt>^</tt> s prioritou vyššou ako <tt>*</tt> (dajte si pritom pozor na fakt, že umocňovanie je narozdiel od operácií <tt>+, -, *, /</tt> sprava asociatívne).
+
/** Pomocná funkcia, ktorá zo zásobníka vyberie
 +
* jedno alebo dve čísla, aplikuje na nich operátor a výsledok uloží na zásobník. */
 +
void processOp(stack & numberStack, token curToken) {
 +
    token num1, num2, result;
 +
    // najskôr vyberieme 1 alebo 2 čísla zo zásobníka
 +
    if (curToken.op == '~') {
 +
        num1 = pop(numberStack);
 +
    } else {
 +
        num2 = pop(numberStack);
 +
        num1 = pop(numberStack);
 +
    }
 +
    // na operandy aplikujeme operátor
 +
    applyOp(curToken, num1, num2, result);
 +
    // výsledné číslo uložíme na zásobník
 +
    push(numberStack, result);
 +
}
  
=== Rozšírenie na infixové výrazy obsahujúce unárne mínus ===
+
/** Spočíta hodnotu výrau v infixovej forme,
 +
* kombinuje infixToPostfix a evaluatePostfix */
 +
double evaluateInfix(tokenSequence & infix) {
 +
    // zásobník, do ktorého ukladáme čísla
 +
    stack numberStack;
 +
    init(numberStack);
  
Pri prevode infixového výrazu do postfixového tvaru sme predpokladali, že neobsahuje žiadny výskyt unárneho operátora <tt>-</tt>. V nasledujúcom tento nedostatok napravíme: napíšeme funkciu <tt>normalise</tt>, ktorá v danom infixovom výraze <tt>in</tt> nahradí všetky výskyty unárneho mínusu reťazcom <tt>(0-1)*</tt> a výsledok uloží do (opäť infixového) výrazu <tt>out</tt>. Mínus je pritom v korektnom infixovom výraze unárny práve vtedy, keď (modulo biele znaky, ktoré budeme ignorovať) nenasleduje za číslom, ani za pravou zátvorkou.
+
    // vytvoríme zásobník operátorov a na spodok dáme #
 +
    stack opStack;
 +
    init(opStack);
 +
    token curToken = {'#', 0};
 +
    push(opStack, curToken);
  
<syntaxhighlight lang="C++">
+
     for (int i = 0; i < infix.length; i++) {
void normalise(const char *in, char *out) {
+
         // aktuálny token zo vstupu
    int j = 0; // index do vystupneho retazca
+
        curToken = infix.items[i];
    bool hasOperand = false;
+
        if (curToken.op == ' ') {
     for (int i = 0; in[i] != 0; i++) {
+
             // čísla ukladáme na zásobník
         if (in[i] == '-' && !hasOperand) {
+
            push(numberStack, curToken);
             out[j++] = '(';
+
        } else if (curToken.op == '(') {
            out[j++] = '0';
+
             // začiatok zátvorky uložíme na zásobník
            out[j++] = '-';
+
             push(opStack, curToken);
             out[j++] = '1';
+
         } else if (curToken.op == ')') {
             out[j++] = ')';
+
             // koniec zátvorky:
            out[j++] = '*';
+
            // spracujeme zo zásobníka operátory,
         } else if (isdigit(in[i]) || in[i] == '.' || in[i] == ')') {
+
            // ktoré boli v zátvorke
             out[j++] = in[i];
+
             token popped = pop(opStack);
             hasOperand = true;
+
            while (popped.op != '(') {
        } else if (isspace(in[i])) {
+
                processOp(numberStack, popped);
            out[j++] = in[i];
+
                popped = pop(opStack);
 +
            }
 
         } else {
 
         } else {
             out[j++] = in[i];
+
             // spracovanie operátora:
             hasOperand = false;
+
            // spracujeme zo zásobníka operátory s vyššou prioritou
 +
            int p = priority(curToken.op, true);
 +
             while (priority(peek(opStack).op, false) >= p) {
 +
                token popped = pop(opStack);
 +
                processOp(numberStack, popped);
 +
            }
 +
            // aktuálny operátor dáme na zásobník
 +
            push(opStack, curToken);
 
         }
 
         }
 
     }
 
     }
     out[j] = 0;
+
 
 +
     // spracujeme zvyšné operátory zo zásobníka
 +
    while (peek(opStack).op != '#') {
 +
        token popped = pop(opStack);
 +
        processOp(numberStack, popped);
 +
    }
 +
    destroy(opStack);
 +
 
 +
    // zo zásobníka vyberieme výsledné číslo
 +
    token result = pop(numberStack);
 +
    // skontrolujeme, že ásobník je prázdny a výsledok je číslo
 +
    assert(isEmpty(numberStack) && result.op == ' ');
 +
    // uvoľníme pamäť zásobníka čísel
 +
    destroy(numberStack);
 +
    return result.val;
 +
}
 +
 
 +
/** Pomocná funkcia, ktorá vráti, či token je koncom operandu v infixovej notácii */
 +
bool isOperand(token curToken) {
 +
    return curToken.op == ' ' || curToken.op == ')';
 
}
 
}
</syntaxhighlight>
 
  
Prevod infixového výrazu s unárnymi mínusmi do postfixového tvaru teda môžeme realizovať tak, že najprv zavoláme funkciu <tt>normalise</tt> a následne funkciu <tt>infixToPostfix</tt>. (O niečo krajším riešením by bolo nahradenie unárnych mínusov nejakým novým špeciálnym znakom &ndash; v takom prípade by sa však musela zmeniť aj funkcia <tt>infixToPostfix</tt>.) Následne môžeme výsledný výraz aj vyhodnotiť pomocou funkcie <tt>evaluatePostfix</tt>.
+
/** Funkcia, ktorá v infixovom výraze zmení unárne mínus na ~ */
 +
void fixUnaryMinus(tokenSequence & infix) {
 +
    for (int i = 0; i < infix.length; i++) {
 +
        if (infix.items[i].op == '-'
 +
                && (i == 0 || !isOperand(infix.items[i - 1]))) {
 +
            infix.items[i].op = '~';
 +
        }
 +
    }
 +
}
  
<syntaxhighlight lang="C++">
+
/** Hlavný program načítava a vykonáva postupnosť príkazov tokenize, postfix, infix a end.
const int maxN = 100;
+
* Príkaz tokenize tokenizuje a vypíše vstupný výraz.
 +
* Príkaz postfix načíta výraz v postfixovom formáte a vypíše jeho hodnotu.
 +
* Príkaz infix načíta výraz v infixovom formáte, upraví v ňom unárne - na ~.
 +
* prevedie ho do postfixového formátu, vyhodnotí postfixový výraz a naopokon
 +
* vyhodnotí aj priamo infixovú formu výrazu.  */
 +
int main() {
 +
    const int maxLine = 100;
 +
    char command[maxLine];
 +
    char expression[maxLine];
  
int main(void) {
+
    while (true) {
    char infix1[maxN];
+
        int ret = scanf("%s", command);
    char infix2[6 * maxN];
+
        if (ret < 1 || strcmp(command, "end") == 0) {
    char postfix[12 * maxN];
+
            break;
   
+
        } else if (strcmp(command, "tokenize") == 0) {
    fgets(infix1, maxN, stdin);
+
            fgets(expression, maxLine, stdin);
 
+
            tokenSequence tokens;
    normalise(infix1, infix2);
+
            tokenize(expression, tokens);
    printf("Vyraz po odstraneni unarnych minusov: %s\n", infix2);
+
            printf(" tokens:");
   
+
            printTokens(tokens);
    infixToPostfix(infix2, postfix);
+
            destroy(tokens);
    printf("Vyraz v postfixovom tvare: %s\n", postfix);
+
        } else if (strcmp(command, "postfix") == 0) {
   
+
            fgets(expression, maxLine, stdin);
    printf("Hodnota vyrazu: %f\n", evaluatePostfix(postfix));
+
            tokenSequence postfix;
   
+
            tokenize(expression, postfix);
     return 0;
+
            double value = evaluatePostfix(postfix);
 +
            printf(" value: %g\n", value);
 +
            destroy(postfix);
 +
        }
 +
        if (strcmp(command, "infix") == 0) {
 +
            fgets(expression, maxLine, stdin);
 +
            tokenSequence infix;
 +
            tokenize(expression, infix);
 +
            fixUnaryMinus(infix);
 +
            printf(" infix:");
 +
            printTokenExpression(infix);
 +
            tokenSequence postfix;
 +
            infixToPostfix(infix, postfix);
 +
            printf(" postfix:");
 +
            printTokenExpression(postfix);
 +
            double value = evaluatePostfix(postfix);
 +
            printf(" value of postfix: %g\n", value);
 +
            double value2 = evaluateInfix(infix);
 +
            printf(" value of infix: %g\n", value2);
 +
            destroy(infix);
 +
            destroy(postfix);
 +
        }
 +
     }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Pri implementácii uvedeného programu sa však treba mať na pozore: kým totiž funkcia <tt>evaluatePostfix</tt> využíva zásobník prvkov typu <tt>double</tt>, funkcia <tt>infixToPostfix</tt> využíva zásobník prvkov typu <tt>char</tt>. Pri pokuse o skopírovanie kódu pre zásobník do zdrojového súboru tohto programu tak stojíme pred voľbou medzi
 
<syntaxhighlight lang="C++">
 
typedef char dataType;
 
</syntaxhighlight>
 
a
 
<syntaxhighlight lang="C++">
 
typedef double dataType;
 
</syntaxhighlight>
 
V tomto prípade ešte môžeme zvoliť druhú možnosť a za <tt>dataType</tt> považovať <tt>double</tt>; o zvyšok sa postará automatické pretypovanie znakov (čiže špeciálnych celých čísel) na reálne čísla a naopak. Toto riešenie má však ďaleko od ideálneho a pre iné dvojice typov nemusí riešenie tohto druhu existovať vôbec. (Ozaj elegantný prístup k tomuto problému vyžaduje pokročilejšie programátorské techniky z letného semestra.)
 

Aktuálna revízia z 13:50, 27. november 2023

Oznamy

Prednášky

  • Tento týždeň a budúci pondelok ešte prednášky v normálnom režime.
  • Budúcu stredu 6.12. v prvej polovici prednášky informácie k skúške a rady k skúškovému všeobecne, potom doberieme posledné povinné učivo.
  • Posledný týždeň semestra v pondelok 11.12. nepovinná prednáška o nepreberaných črtách jazykov C a C++, v stredu 13.12. prednáška pravdepodobne nebude.

Cvičenia a úlohy

  • Cvičenia bežia normálne každý utorok, piatkové cvičenia už iba 2x.
  • Zajtrajšia rozcvička bude o vyfarbovaní.
  • Odporúčame získať 5 bodov z cvičení, v opačnom prípade sú pre vás v piatok povinné cvičenia.
  • Budúci utorok bude teoretická rozcvička na papieri.
  • Tretiu domácu úlohu treba odovzdať do budúceho utorka 5.12. 22:00.

Semestrálny test

  • V stredu 13.12. o 18:10.
  • Treba získať aspoň 50% bodov.
  • Opravný termín v januári.
  • Môžete si priniesť ťahák v rozsahu 1 listu A4.
  • Na semestrálnom teste budú podobné typy príkladov, aké poznáte z teoretických cvičení, napríklad napíšte funkciu, ktorá robí zadanú činnosť, doplňte chýbajúce časti funkcie, zistite, čo funkcia robí.
  • Vyskytnú sa ale aj príklady, kde je úlohou napísať, ako bude na nejakom vstupe fungovať algoritmus alebo dátová štruktúra z prednášky. Ukážky takýchto príkladov nájdete na stránke Zimný semester, semestrálny test
  • Môžete si pozrieť aj ukážkový test pre pokročilých, ktorý má podobné typy príkladov ako semestrálny test.

Termíny skúšok

  • Piatok 15.12. 13:10
  • Piatok 12.1. 9:00
  • Piatok 19.1. 9:00
  • Piatok 26.1. 9:00, hlavne 1. opravný termín
  • Piatok 9.2. 9:00, hlavne 2. opravný termín

Na termíny sa bude dať prihlasovať od stredy 29.11. 20:00

  • Kapacita termínov bude obmedzená, prihláste sa teda radšej skôr, neskôr to môžete zmeniť.
  • Hláste sa iba na jeden termín!
  • Ak vidíte konflikt niektorého termínu s hromadnou skúškou alebo písomkou z iného predmetu, dajte mi prosím vedieť čím skôr, najneskôr v stredu.
  • Decembrový termín odporúčame hlavne študentom, ktorým programovanie nerobí problémy.
  • Viac informácií o skúške je na stránke Zimný semester, skúška, spolu cez to prejdeme budúcu stredu.
  • Na testovač tento týždeň pridáme zopár tréningových príkladov na skúšku.


Začneme s dokončením vyfarbovania z minulej prednášky

Aritmetické výrazy

Nejaký čas sa teraz budeme venovať spracovaniu aritmetických výrazov pozostávajúcich z reálnych čísel, operácií +,-,*,/ a zátvoriek (,). Hlavnou úlohou je vyhodnotenie daného výrazu; napríklad pre výraz

(65 - 3 * 5) / (2 + 3)

chceme vedieť povedať, že jeho hodnota je 10. Najprv ale zavedieme dva alternatívne spôsoby zápisu aritmetických výrazov, z ktorých jeden nám vyhodnocovanie výrazov značne uľahčí.

Infixová notácia

Bežný spôsob zápisu aritmetických výrazov v matematike sa zvykne nazývať aj infixovou notáciou. Toto pomenovanie odkazuje na skutočnosť, že binárne operátory (ako napríklad +,-,*,/) sa v tejto notácii píšu medzi svojimi dvoma operandmi. Poradie vykonávania jednotlivých operácií sa riadi zátvorkami a prioritou operácií.

Napríklad

(65 – 3 * 5) / (2 + 3)

je infixový výraz s hodnotou 10.

Prefixová notácia

Pri takzvanej prefixovej notácii (často podľa jej pôvodcu Jana Łukasiewicza nazývanej aj poľskou notáciou) sa každý operátor v aritmetickom výraze zapisuje pred svojimi dvoma operandmi.

Napríklad výraz (65 – 3 * 5) / (2 + 3) má prefixový zápis

/ - 65 * 3 5 + 2 3

Zaujímavosť: programovací jazyk Lisp a jeho varianty využívajú prefixovú notáciu na všetky výrazy, píšu však aj zátvorky, napríklad (+ 1 2).

Postfixová notácia

Pri postfixovej notácii (často nazývanej aj obrátenou poľskou notáciou) je situácia opačná: operátor sa zapisuje bezprostredne za svojimi dvoma operandmi.

Výraz (65 – 3 * 5) / (2 + 3) má teda postfixový zápis

65 3 5 * - 2 3 + /
  • Postfixová a prefixová notácia sú pre človeka o niečo ťažšie čitateľné, než bežná infixová notácia (čo môže byť aj otázkou zvyku).
  • Uvidíme však, že výrazy v postfixovej notácii sa dajú jednoducho vyhodnocovať.
  • Výhodou postfixovej a prefixovej notácie oproti infixovej je aj to, že nepotrebujú zátvorky.

Unárne mínus

  • Operátory +, * a / sú vždy binárne, čo znamená, že sa aplikujú sa na dva operandy.
  • Operátor - môže byť binárny aj unárny (unárny znamená, že sa aplikuje na jeden operand).
    • Príklad s binárnym mínus: 1 - 2.
    • Príklad s unárnym mínus: 2 * -(2 + 3).
  • Nevýhodou postfixovej a prefixovej notácie je, že bez zátvoriek v nich nie je možné rozpoznať binárne a unárne mínus.
    • Prefixový výraz - - 2 3 by napríklad mohol zodpovedať ako výrazu -2 - 3 aj výrazu -(2 - 3).
  • Na vyriešenie tohto problému budeme na unárne mínus používať znamienko ~ (iba interne v našom programe, nepoužíva sa tak všeobecne). V infixových výrazoch budú unárne mínus automaticky rozpoznané a nahradené ~.

Predspracovanie výrazu

Používateľ zadá výraz ako text v niektorej notácii. Aby sa nám s ním lepšie pracovalo, prevedieme ho najskôr na postupnosť symbolov (angl. token), pričom každý symbol bude buď číslo reprezentované v premennej typu double alebo znak reprezentujúci operátor alebo zátvorku.

Jednotlivé symboly budeme ukladať do záznamov typu token

struct token {
    char op;   
    double val;
};
  • Ak štruktúra obsahuje číslo, op bude medzera a samotné číslo bude v položke val.
  • Ak štruktúra reprezentuje iný symbol, tento symbol bude v položke op a položka val sa nebude používať.

Postupnosť symbolov uložíme do štruktúry tokenSequence, ktorá obsahuje pole položiek typu token a tiež koľko položiek bolo do poľa uložených.

struct tokenSequence {
    token * items;  // pole tokenov
    int size;       // veľkosť alokovaného poľa
    int length;     // počet tokenov uložených v poli
};

Preklad výrazu na postupnosť symbolov realizuje funkcia tokenize.

  • Na načítanie čísla používame funkciu sscanf, ktorá vyzerá podobne ako scanf, ale načítava zo zadaného reťazca
    • Ako reťazec zadáme &(str[strPos]), čo spôsobí, že sa začne načítavať od pozície strPos, mohli by sme písať aj str+strPos.
    • Formátovací znak %n nám do premennej skip uloží počet znakov, ktoré sa pri načítaní čísla použili, tento o tento počet potom posunieme premennú strPos.
  • Kód token newToken = {str[strPos], 0} inicializuje položku op na znak str[strPos] a položku val na nulu.
  • Pomocné funkcie init a addToken pracujú s postupnosťou symbolov, nájdete ich v programe na konci prednášky.
void tokenize(char * str, tokenSequence & tokens) {
    init(tokens, strlen(str));  // inicializujeme prázdnu postupnosť

    int strPos = 0;  // pozícia v rámci reťazca
    while (str[strPos] != 0) {  // kým nie sme na konci str
        if (isspace(str[strPos])) {  // preskakujeme biele znaky
            strPos++;
        } else if (isdigit(str[strPos]) || str[strPos] == '.') {
            // keď nájdeme cifru alebo bodku (začiatok čísla)
            double val;
            int skip;
            // načítame toto číslo pomocou sscanf, 
            // do skip uložíme počet znakov čísla
            sscanf(&(str[strPos]), "%lf%n", &val, &skip);
            // vytvoríme a uložíme token
            token newToken = {' ', val};
            addToken(tokens, newToken);
            // preskočíme všetky znaky čísla
            strPos += skip;
        } else {
            // spracovanie zátvoriek alebo operátora
            assert(strchr("+-/*()~", str[strPos]) != NULL);
            // vytvoríme a uložíme token
            token newToken = {str[strPos], 0};
            addToken(tokens, newToken);
            strPos++;
        }
    }
}

Vyhodnocovanie aritmetických výrazov v postfixovej notácii

  • Budeme používať zásobník, do ktorého budeme vkladať čísla čakajúce na spracovanie.
  • Operátor má v postfixovom zápise obidva operandy pred sebou. Keď narazíme na operátor, jeho operandy sme už prečítali a spracovali. Ide navyše o posledné dve prečítané alebo vypočítané hodnoty.

Výraz budeme postupne čítať zľava doprava:

  • Keď narazíme na číslo, vložíme ho na zásobník.
  • Keď narazíme na operátor:
    • Vyberieme zo zásobníka jeho dva operandy.
    • Prvé z týchto čísel je pritom druhým operandom a druhé z nich je prvým operandom.
    • Vykonáme s týmito operandmi danú operáciu a výsledok tejto operácie vložíme naspäť na zásobník.
  • Pri unárnom mínus vyberáme zo zásobníka iba jeden operand.
  • Tento postup opakujeme, až kým neprídeme na koniec výrazu. V tom okamihu by sme mali mať na zásobníku jediný prvok, výslednú hodnotu výrazu.

Technická poznámka:

  • Keďže vo výslednom programe budeme potrebovať zásobníky čísel aj znakov, používame v ňom zásobník položiek typu token. Ak by sme však robili čisto vyhodnocovanie postfixovej notácie, stačil by nám zásobník s prvkami typu double.


Cvičenie:

  • Odsimulujme činnosť tohto algoritmu na našom vstupe 65 3 5 * - 2 3 + /
  • Aké všelijaké prípady môžu nastať, keď na vstupe nemáme správny výraz v postfixovej notácii?


typedef token dataType;
// Nasleduje kód pre zásobník tokenov
// ...


/** Funkcia aplikuje operátor uložený v opToken na čísla
* uložené v num1 a num2, výsledok uloží ako číslo do result.
* Unárny operátor sa aplikuje iba na num1. */
void applyOp(token opToken, token num1, token num2, token & result) {
    result.op = ' ';
    switch (opToken.op) {
    case '~':
        result.val = -num1.val;
        break;
    case '+':
        result.val = num1.val + num2.val;
        break;
    case '-':
        result.val = num1.val - num2.val;
        break;
    case '*':
        result.val = num1.val * num2.val;
        break;
    case '/':
        result.val = num1.val / num2.val;
        break;
    default: // iné operátory nepovoľujeme.
        assert(false);
    }
}

/** Funkcia vyhodnotí a vráti hodnotou výrazu v postfixovom tvare. */
double evaluatePostfix(tokenSequence & tokens) {
    // zásobník, do ktorého ukladáme čísla
    stack numberStack;
    init(numberStack);

    for (int i = 0; i < tokens.length; i++) {
        // aktuálny token zo vstupu
        token curToken = tokens.items[i];
        if (curToken.op == ' ') {
            // čísla rovno ukladáme na zásobník
            push(numberStack, curToken);
        } else {
            // spracovanie operátora
            token num1, num2, result;
            // najskôr vyberieme 1 alebo 2 čísla zo zásobníka
            if (curToken.op == '~') {
                num1 = pop(numberStack);
            } else {
                num2 = pop(numberStack);
                num1 = pop(numberStack);
            }
            // na operandy aplikujeme operátor
            applyOp(curToken, num1, num2, result);
            // výsledné číslo uložíme na zásobník
            push(numberStack, result);
        }
    }
    // zo zásobníka vyberieme výsledné číslo
    token result = pop(numberStack);
    // skontrolujeme, že zásobník je prázdny a výsledok je číslo
    assert(isEmpty(numberStack) && result.op == ' ');
    // uvoľníme pamäť zásobníka
    destroy(numberStack);
    return result.val;
}

Prevod výrazu z infixovej do postfixovej notácie

  • Pre bežnú prax je dôležitejšie vyhodnocovanie výrazov v klasickej infixovej notácii.
  • Túto úlohu teraz vyriešime tak, že napíšeme funkciu, ktorá preloží aritmetický výraz z infixovej do postfixovej notácie.
  • Následne ho môžeme vyhodnotiť algoritmom popísaným vyššie.

Popis algoritmu

Uvažujme najprv výrazy bez zátvoriek a bez unárnych mínusov.

  • Pozostávajú teda z čísel a binárnych operátorov +, -, *, /, kde * a / majú vyššiu prioritu ako + a -.
  • Všetky tieto operátory sú navyše zľava asociatívne, t.j. napr. 30 / 6 / 5 je to isté ako (30 / 6) / 5.

Na prevod takéhoto výrazu do postfixového tvaru ním stačí prejsť zľava doprava a všimnúť si dve skutočnosti:

  • Poradie jednotlivých čísel v postfixovom výraze je rovnaké, ako v pôvodnom infixovom výraze.
  • Napríklad výraz 1 + 2 - 3 * 4 + 5 má postfixový tvar 1 2 + 3 4 * - 5 +.
  • Pri prechádzaní vstupným infixovým výrazom budeme teda čísla priamo pridávať do výstupného postfixového výrazu.
  • Každý operátor treba presunúť spomedzi jeho dvoch operandov za jeho druhý operand.
  • Ak teda vo vstupnom infixovom výraze narazíme na operátor, nepridáme ho hneď do výstupného výrazu, ale uložíme ho pre neskoršie pridanie na správnej pozícii.
  • Uložený operátor treba vypísať za jeho druhým operandom. Ak pritom aj samotný operand obsahuje nejaké ďalšie operátory, určite musia byť vypísané skôr. Operátory teda budeme ukladať na zásobník.
  • Keď vo vstupnom infixovom výraze narazíme na operátor, je možné, že tesne pred ním končí operand jedného alebo niekoľkých operátorov uložených na zásobníku. Vďaka ľavej asociatívnosti pritom ide o práve všetky operátory na vrchu zásobníka, ktoré majú vyššiu alebo rovnakú prioritu, ako nájdený operátor. Všetky tieto operátory teda postupne vyberieme zo zásobníka a vypíšeme ich do výstupného reťazca. Až následne na zásobník vložíme nájdený operátor.
  • Na konci vstupného reťazca vypíšeme všetky operátory, ktoré zostali na zásobníku.

Z technických dôvodov budeme na spodku zásobníka uchovávať „umelé dno” #, ktoré budeme chápať ako symbol s nižšou prioritou ako všetky operátory.

  • Situáciu, keď pri vyberaní operátorov zo zásobníka narazíme na jeho dno tak budeme môcť riešiť konzistentne so situáciou, keď narazíme na operátor s nižšou prioritou: v oboch prípadoch chceme s vyberaním prestať.

Na vstupnom výraze 1 + 2 - 3 * 4 + 5 bude práve popísaný algoritmus pracovať nasledovne:

Vstupný symbol  Výstupný výraz    Zásobník (dno vľavo) 
-------------------------------------------------------------------------------------------------------
           1                       #            pridaj 1 na výstup 
                1                  #

           +    1                  #            # má nižšiu prioritu ako +, nevyberaj ho
                1                  # +          vlož + na zásobník

           2    1                  # +          pridaj 2 na výstup
                1 2                # + 

           -    1 2                # +          + má rovnakú prioritu ako +, vyber + a pridaj na výstup
                1 2 +              #            # má nižšiu prioritu ako +, nevyberaj ho
                1 2 +              #            vlož - na zásobník
                1 2 +              # -   

           3    1 2 +              # -          pridaj 3 na výstup
                1 2 + 3            # - 

           *    1 2 + 3            # -          - má nižšiu prioritu ako *, nevyberaj ho
                1 2 + 3            # - *        vlož * na zásobník

           4    1 2 + 3            # - *        pridaj 4 na výstup
                1 2 + 3 4          # - *                     

           +    1 2 + 3 4          # - *        * má vyššiu prioritu ako +, vyber * a vypíš
                1 2 + 3 4 *        # -          - má rovnakú prioritu ako +, vyber - a vypíš
                1 2 + 3 4 * -      #            # má nižšiu prioritu ako +, nevyberaj ho
                1 2 + 3 4 * -      # +          vlož - na zásobník

           5    1 2 + 3 4 * -      # +          pridaj 5 na výstup
                1 2 + 3 4 * - 5    # +                 

    [koniec]    1 2 + 3 4 * - 5    # +          vyber operátory na zásobníku a pridaj na výstup
                1 2 + 3 4 * - 5 +  #            <-- VÝSLEDNÝ POSTFIXOVÝ VÝRAZ

Pridanie zátvoriek:

  • Keď vo vstupnom infixovom reťazci narazíme na ľavú zátvorku, vložíme ju do zásobníka. Podobne ako pri # ju budeme považovať za symbol s nižšou prioritou, než všetky binárne operátory (argument operátora spred tejto zátvorky totiž určite nemôže končiť v jej vnútri).
  • Keď narazíme na pravú zátvorku, potrebujeme vypísať všetky doposiaľ nevypísané operátory uzatvorené touto zátvorkou. Preto vyberieme a vypíšeme na výstup všetky operátory zo zásobníka až po prvú ľavú zátvorku (ktorú zo zásobníka taktiež vyberieme).

Pridanie unárneho mínus:

  • Unárne mínus má vyššiu prioritu ako binárne operátory. Predpokladáme, že ho máme zapísané ako ~.
  • Je však sprava asociatívne, t.j. --2 je to isté ako -(-2) a v postfixovom tvare to bude 2 ~ ~
  • Preto ak je aktuálny operátor unárne mínus (~), nevyhadzujeme iné unárne mínus zo zásobníka. Toto je v programe dosiahnuté tak, že nové unárne mínus ("pravé") má vyššiu prioritu ako to, čo už je na zásobníku ("ľavé").

Implementácia

/** Pomocná funkcia, ktorá vráti prioritu operátora.
 * Argument right určuje, či ide o pravý z dvoch porovnávaných operátorov */
int priority(char op, bool right) {
    if (op == '#' || op == '(') return 0;
    if (op == '-' || op == '+') return 1;
    if (op == '*' || op == '/') return 2;
    if (op == '~' && !right) return 3;
    if (op == '~' && right) return 4;
    assert(false); // sem by sme sa nemali dostať
}

/** Skonvertuje infixový výraz infix do postfixovej formy */
void infixToPostfix(tokenSequence & infix, tokenSequence & postfix) {
    // inicializuje výslednú postupnosť tokenov
    init(postfix, infix.length);

    // vytvoríme zásobník operátorov a na spodok dáme #
    stack opStack;
    init(opStack);
    token curToken = {'#', 0};
    push(opStack, curToken);

    for (int i = 0; i < infix.length; i++) {
        // aktuálny token zo vstupu
        curToken = infix.items[i];
        if (curToken.op == ' ') {
            // čísla rovno skopírujeme do výstupu
            addToken(postfix, curToken);
        } else if (curToken.op == '(') {
            // začiatok zátvorky uložíme na zásobník
            push(opStack, curToken);
        } else if (curToken.op == ')') {
            // koniec zátvorky:
            // na výstup pridáme zo zásobníka operátory,
            // ktoré boli v zátvorke
            token popped = pop(opStack);
            while (popped.op != '(') {
                addToken(postfix, popped);
                popped = pop(opStack);
            }
        } else {
            // spracovanie operátora:
            // na výstup pridáme zo zásobníka operátory s vyššou prioritou
            int p = priority(curToken.op, true);
            while (priority(peek(opStack).op, false) >= p) {
                token popped = pop(opStack);
                addToken(postfix, popped);
            }
            // aktuálny operátor dáme na zásobník
            push(opStack, curToken);
        }
    }

    // zvyšné operátory presunieme zo zásobníka na výstup
    while (peek(opStack).op != '#') {
        token popped = pop(opStack);
        addToken(postfix, popped);
    }
    destroy(opStack);
}

Cvičenie: Rozšírte program vyššie o operáciu umocňovania ^ s prioritou vyššou ako * (dajte si pritom pozor na fakt, že umocňovanie je na rozdiel od operácií +, -, *, / sprava asociatívne).


Predspracovanie unárneho mínus

  • Pre prevodom infixového výrazu do postfixového tvaru potrebujeme v infixovom výraze nahradiť unárne mínus znakom ~.
  • Robí to funkcia fixUnaryMinus.
  • Mínus je v korektnom infixovom výraze unárne práve vtedy, keď nenasleduje za číslom, ani za pravou zátvorkou, čo testuje pomocná funkcia isOperand.
/** Pomocná funkcia, ktorá vráti, či token je koncom operandu v infixovej notácii */
bool isOperand(token curToken) {
    return curToken.op == ' ' || curToken.op == ')';
}

/** Funkcia, ktorá v infixovom výraze zmení unárne mínus na ~ */
void fixUnaryMinus(tokenSequence & infix) {
    for (int i = 0; i < infix.length; i++) {
        if(infix.items[i].op == '-' 
           && (i == 0 || !isOperand(infix.items[i - 1]))) {
            infix.items[i].op = '~';
        }
    }
}

Záverečné poznámky

  • Infixové výrazy vieme vyhodnotiť tak, že najskôr ich prevedieme na postfixové a tie potom vyhodnotíme.
    • Pri vyhodnocovaní postfixovej formy používame zásobník čísel (operandov).
    • Pri prevode z infixovej formy na postfixovú používame zásobník operátorov.
  • Obidva procesy sa dajú vykonávať aj súčasne (počas prechodu výrazom používame jeden zásobník čísel a jeden zásobník operátorov).
  • Všeobecnejšie a elegantnejšie prístupy k analýze a vyhodnocovaniu výrazov ale aj celých programov uvidíte na predmete Kompilátory (Mgr. štúdium), ktorý využíva poznatky z predmetu Formálne jazyky a automaty (semester 2Z).

Typ dát na zásobníku

V našom programe sme potrebovali zásobník čísel aj zásobník znakov.

  • Vyriešili sme to tým, že na zásobník dávame položky typu token, ktoré obsahujú znak aj číslo.
  • Nie je to však príliš elegantné a plytve pamäťou na nepotrebné údaje.
  • Druhá možnosť je dvakrát implementovať celý zásobník pre rôzne typy dát, kopírovanie a menenie kódu je však tiež niečo, čomu sa chceme vyhnúť.
  • Najlepšie by bolo použiť techniku generického programovania, ktorá je k dispozícii vo veľa jazykoch, vrátane C++, ale nie C. Touto technikou vieme zásobník implementovať raz a použiť s rôznymi typmi. Ukážku tejto techniky uvidíme na poslednej prednáške a viac sa dozviete na Programovaní (2) v letnom semestri.

Program na prácu s výrazmi

#include <cstdio>
#include <cctype>
#include <cassert>
#include <cstring>
using namespace std;

/** Štruktúra pre jednu súčasť výrazu: znamienko alebo číslo */
struct token {
    char op;     // znamienko alebo medzera, ak ide o číslo
    double val;  // číselná hodnota, ak je op medzera
};

/** Štruktúra pre postupnosť tokenov */
struct tokenSequence {
    token * items;  // pole tokenov
    int size;       // veľkosť alokovaného poľa
    int length;     // počet tokenov uložených v poli
};

/** Funkcia inicializuje prázdnu postupnosť tokenov
pričom alokuje pole požadovanej veľkosti. */
void init(tokenSequence & tokens, int size) {
    tokens.items = new token[size];
    tokens.size = size;
    tokens.length = 0;
}

/** Funkcia do postupnosti tokenov pridá nový token. */
void addToken(tokenSequence & tokens, token & newToken) {
    assert(tokens.length < tokens.size);
    tokens.items[tokens.length] = newToken;
    tokens.length++;
}

/** Funkcia odalokuje pamäť alokovanú pre postupnosť tokenov */
void destroy(tokenSequence & tokens) {
    delete[] tokens.items;
}

/** Funkcia vypíše postupnosť tokenov, každý v hranatých zátvorkách. */
void printTokens(tokenSequence & tokens) {
    for (int i = 0; i < tokens.length; i++) {
        token curToken = tokens.items[i];
        if (curToken.op == ' ') {
            printf(" [val %g]", curToken.val);
        } else {
            printf(" [op %c]", curToken.op);
        }
    }
    printf("\n");
}

/** Funkcia vypíše postupnosť tokenov ako aritmetický výraz. */
void printTokenExpression(tokenSequence & tokens) {
    for (int i = 0; i < tokens.length; i++) {
        token curToken = tokens.items[i];
        if (curToken.op == ' ') {
            printf(" %g", curToken.val);
        } else {
            printf(" %c", curToken.op);
        }
    }
    printf("\n");
}

/** Funkcia konvertuje výraz z reťazca na postupnosť tokenov. */
void tokenize(char * str, tokenSequence & tokens) {
    init(tokens, strlen(str));  // inicializujeme prázdnu postupnosť

    int strPos = 0;  // pozícia v rámci reťazca
    while (str[strPos] != 0) {  // kým nie sme na konci str
        if (isspace(str[strPos])) {  // preskakujeme biele znaky
            strPos++;
        } else if (isdigit(str[strPos]) || str[strPos] == '.') {
            // keď nájdeme cifru alebo bodku (začiatok čísla)
            double val;
            int skip;
            // načítame toto číslo pomocou sscanf, do skip uložíme počet znakov čísla
            sscanf(&(str[strPos]), "%lf%n", &val, &skip);
            // vytvoríme a uložíme token
            token newToken = {' ', val};
            addToken(tokens, newToken);
            // preskočíme všetky znaky čísla
            strPos += skip;
        } else {
            // spracovanie zátvoriek alebo operátora
            assert(strchr("+-/*()~", str[strPos]) != NULL);
            // vytvoríme a uložíme token
            token newToken = {str[strPos], 0};
            addToken(tokens, newToken);
            strPos++;
        }
    }
}

// Nasleduje kód pre zásobník tokenov
typedef token dataType;

/** Uzol spájaného zoznamu pre zásobník */
struct node {
    dataType data; // dáta uložené v uzle
    node * next;   // smerník na ďalší uzol zoznamu
};

/** Štruktúra pre zásobník implementovaný pomocou zoznamu*/
struct stack {
    node * top; // Smernik na vrch zasobníka  alebo NULL
};

/** Funkcia inicializuje prázdny zásobník */
void init(stack & s) {
    s.top = NULL;
}

/** Funkcia zistí, či je zásobník prázdny */
bool isEmpty(stack & s) {
    return s.top == NULL;
}

/** Funkcia pridá prvok item na vrch zásobníka */
void push(stack & s, dataType item) {
    node * tmp = new node;
    tmp->data = item;
    tmp->next = s.top;
    s.top = tmp;
}

/** Funkcia odoberie prvok z vrchu zasobnika a vráti ho */
dataType pop(stack & s) {
    assert(!isEmpty(s));
    dataType result = s.top->data;
    node * tmp = s.top->next;
    delete s.top;
    s.top = tmp;
    return result;
}

/** Funkcia vráti prvok na vrchu zásobníka, ale nechá ho v zásobníku */
dataType peek(stack & s) {
    assert(!isEmpty(s));
    return s.top->data;
}

/** Funkcia uvoľní pamäť zásobníka */
void destroy(stack & s) {
    while (!isEmpty(s)) {
        pop(s);
    }
}

/** Funkcia aplikuje operátor uložený v opToken na čísla
* uložené v num1 a num2, výsledok uloží ako číslo do result.
* Unárny operátor sa aplikuje iba na num1. */
void applyOp(token opToken, token num1, token num2, token & result) {
    result.op = ' ';
    switch (opToken.op) {
    case '~':
        result.val = -num1.val;
        break;
    case '+':
        result.val = num1.val + num2.val;
        break;
    case '-':
        result.val = num1.val - num2.val;
        break;
    case '*':
        result.val = num1.val * num2.val;
        break;
    case '/':
        result.val = num1.val / num2.val;
        break;
    default: // iné operátory nepovoľujeme.
        assert(false);
    }
}

/** Funkcia vyhodnotí a vráti hodnotou výrazu v postfixovom tvare. */
double evaluatePostfix(tokenSequence & tokens) {
    // zásobník, do ktorého ukladáme čísla
    stack numberStack;
    init(numberStack);

    for (int i = 0; i < tokens.length; i++) {
        // aktuálny token zo vstupu
        token curToken = tokens.items[i];
        if (curToken.op == ' ') {
            // čísla rovno ukladáme na zásobník
            push(numberStack, curToken);
        } else {
            // spracovanie operátora
            token num1, num2, result;
            // najskôr vyberieme 1 alebo 2 čísla zo zásobníka
            if (curToken.op == '~') {
                num1 = pop(numberStack);
            } else {
                num2 = pop(numberStack);
                num1 = pop(numberStack);
            }
            // na operandy aplikujeme operátor
            applyOp(curToken, num1, num2, result);
            // výsledné číslo uložíme na zásobník
            push(numberStack, result);
        }
    }
    // zo zásobníka vyberieme výsledné číslo
    token result = pop(numberStack);
    // skontrolujeme, že zásobník je prázdny a výsledok je číslo
    assert(isEmpty(numberStack) && result.op == ' ');
    // uvoľníme pamäť zásobníka
    destroy(numberStack);
    return result.val;
}

/** Pomocná funkcia, ktorá vráti prioritu operátora.
* Argument right určuje, či ide o pravý z dvoch porovnávaných operátorov */
int priority(char op, bool right) {
    if (op == '#' || op == '(') return 0;
    if (op == '-' || op == '+') return 1;
    if (op == '*' || op == '/') return 2;
    if (op == '~' && !right) return 3;
    if (op == '~' && right) return 4;
    assert(false); // sem by sme sa nemali dostať
}

/** Skonvertuje infixový výraz infix do postfixovej formy */
void infixToPostfix(tokenSequence & infix, tokenSequence & postfix) {
    // inicializuje výslednú postupnosť tokenov
    init(postfix, infix.length);

    // vytvoríme zásobník operátorov a na spodok dáme #
    stack opStack;
    init(opStack);
    token curToken = {'#', 0};
    push(opStack, curToken);

    for (int i = 0; i < infix.length; i++) {
        // aktuálny token zo vstupu
        curToken = infix.items[i];
        if (curToken.op == ' ') {
            // čísla rovno skopírujeme do výstupu
            addToken(postfix, curToken);
        } else if (curToken.op == '(') {
            // začiatok zátvorky uložíme na zásobník
            push(opStack, curToken);
        } else if (curToken.op == ')') {
            // koniec zátvorky:
            // na výstup pridáme zo zásobníka operátory,
            // ktoré boli v zátvorke
            token popped = pop(opStack);
            while (popped.op != '(') {
                addToken(postfix, popped);
                popped = pop(opStack);
            }
        } else {
            // spracovanie operátora:
            // na výstup pridáme zo zásobníka operátory s vyššou prioritou
            int p = priority(curToken.op, true);
            while (priority(peek(opStack).op, false) >= p) {
                token popped = pop(opStack);
                addToken(postfix, popped);
            }
            // aktuálny operátor dáme na zásobník
            push(opStack, curToken);
        }
    }

    // zvyšné operátory presunieme zo zásobníka na výstup
    while (peek(opStack).op != '#') {
        token popped = pop(opStack);
        addToken(postfix, popped);
    }
    destroy(opStack);
}

/** Pomocná funkcia, ktorá zo zásobníka vyberie
* jedno alebo dve čísla, aplikuje na nich operátor a výsledok uloží na zásobník. */
void processOp(stack & numberStack, token curToken) {
    token num1, num2, result;
    // najskôr vyberieme 1 alebo 2 čísla zo zásobníka
    if (curToken.op == '~') {
        num1 = pop(numberStack);
    } else {
        num2 = pop(numberStack);
        num1 = pop(numberStack);
    }
    // na operandy aplikujeme operátor
    applyOp(curToken, num1, num2, result);
    // výsledné číslo uložíme na zásobník
    push(numberStack, result);
}

/** Spočíta hodnotu výrau v infixovej forme,
 * kombinuje infixToPostfix a evaluatePostfix */
double evaluateInfix(tokenSequence & infix) {
    // zásobník, do ktorého ukladáme čísla
    stack numberStack;
    init(numberStack);

    // vytvoríme zásobník operátorov a na spodok dáme #
    stack opStack;
    init(opStack);
    token curToken = {'#', 0};
    push(opStack, curToken);

    for (int i = 0; i < infix.length; i++) {
        // aktuálny token zo vstupu
        curToken = infix.items[i];
        if (curToken.op == ' ') {
            // čísla ukladáme na zásobník
            push(numberStack, curToken);
        } else if (curToken.op == '(') {
            // začiatok zátvorky uložíme na zásobník
            push(opStack, curToken);
        } else if (curToken.op == ')') {
            // koniec zátvorky:
            // spracujeme zo zásobníka operátory,
            // ktoré boli v zátvorke
            token popped = pop(opStack);
            while (popped.op != '(') {
                processOp(numberStack, popped);
                popped = pop(opStack);
            }
        } else {
            // spracovanie operátora:
            // spracujeme zo zásobníka operátory s vyššou prioritou
            int p = priority(curToken.op, true);
            while (priority(peek(opStack).op, false) >= p) {
                token popped = pop(opStack);
                processOp(numberStack, popped);
            }
            // aktuálny operátor dáme na zásobník
            push(opStack, curToken);
        }
    }

    // spracujeme zvyšné operátory zo zásobníka
    while (peek(opStack).op != '#') {
        token popped = pop(opStack);
        processOp(numberStack, popped);
    }
    destroy(opStack);

    // zo zásobníka vyberieme výsledné číslo
    token result = pop(numberStack);
    // skontrolujeme, že ásobník je prázdny a výsledok je číslo
    assert(isEmpty(numberStack) && result.op == ' ');
    // uvoľníme pamäť zásobníka čísel
    destroy(numberStack);
    return result.val;
}

/** Pomocná funkcia, ktorá vráti, či token je koncom operandu v infixovej notácii */
bool isOperand(token curToken) {
    return curToken.op == ' ' || curToken.op == ')';
}

/** Funkcia, ktorá v infixovom výraze zmení unárne mínus na ~ */
void fixUnaryMinus(tokenSequence & infix) {
    for (int i = 0; i < infix.length; i++) {
        if (infix.items[i].op == '-'
                && (i == 0 || !isOperand(infix.items[i - 1]))) {
            infix.items[i].op = '~';
        }
    }
}

/** Hlavný program načítava a vykonáva postupnosť príkazov tokenize, postfix, infix a end.
 * Príkaz tokenize tokenizuje a vypíše vstupný výraz.
 * Príkaz postfix načíta výraz v postfixovom formáte a vypíše jeho hodnotu.
 * Príkaz infix načíta výraz v infixovom formáte, upraví v ňom unárne - na ~.
 * prevedie ho do postfixového formátu, vyhodnotí postfixový výraz a naopokon
 * vyhodnotí aj priamo infixovú formu výrazu.  */
int main() {
    const int maxLine = 100;
    char command[maxLine];
    char expression[maxLine];

    while (true) {
        int ret = scanf("%s", command);
        if (ret < 1 || strcmp(command, "end") == 0) {
            break;
        } else if (strcmp(command, "tokenize") == 0) {
            fgets(expression, maxLine, stdin);
            tokenSequence tokens;
            tokenize(expression, tokens);
            printf(" tokens:");
            printTokens(tokens);
            destroy(tokens);
        } else if (strcmp(command, "postfix") == 0) {
            fgets(expression, maxLine, stdin);
            tokenSequence postfix;
            tokenize(expression, postfix);
            double value = evaluatePostfix(postfix);
            printf(" value: %g\n", value);
            destroy(postfix);
        }
        if (strcmp(command, "infix") == 0) {
            fgets(expression, maxLine, stdin);
            tokenSequence infix;
            tokenize(expression, infix);
            fixUnaryMinus(infix);
            printf(" infix:");
            printTokenExpression(infix);
            tokenSequence postfix;
            infixToPostfix(infix, postfix);
            printf(" postfix:");
            printTokenExpression(postfix);
            double value = evaluatePostfix(postfix);
            printf(" value of postfix: %g\n", value);
            double value2 = evaluateInfix(infix);
            printf(" value of infix: %g\n", value2);
            destroy(infix);
            destroy(postfix);
        }
    }
}