Programovanie (2) v Jave
1-INF-166, letný semester 2023/24

Prednášky · Pravidlá · Softvér · Testovač
· Vyučujúcich predmetu možno kontaktovať mailom na adresách uvedených na hlavnej stránke. Hromadná mailová adresa zo zimného semestra v letnom semestri nefunguje.
· JavaFX: cesta k adresáru lib je v počítačových učebniach /usr/share/openjfx/lib.


Prednáška 5: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
(Vytvorená stránka „== Štatistika z N čísel == Úlohou je zistiť o N prečítaných číslach nejaké štatistické údaje. === Maximum a minimum === Zrejme stačí čítať čísla pos...“)
 
Riadok 356: Riadok 356:
 
         window.wait(wait);
 
         window.wait(wait);
 
     }
 
     }
}
 
</pre>
 
 
== Využitie polí - príklady ==
 
 
=== Erastotenovo sito ===
 
 
Chceme vypísať všetky prvočísla po N. Budeme mať pole pravdivostných hodnôt, kde A[i] nám hovorí, či je i ešte potenciálne prvočíslo.
 
Postupovať budeme nasledovne:
 
 
* Začneme číslom 2 - toto je iste prvočíslo (tak ho vypíšeme). O jeho násobkoch však vieme, že iste nemôžu byť prvočísla - nastavíme preto pre každý násobok i=2*k pravidvostnú hodnotu A[i] na false.
 
* Následne sa vždy posunieme na prvé číslo, ktoré ešte môže byť prvočíslo (vypíšeme ho) a urobíme to isté s jeho násobkami.
 
 
<pre>
 
const int N=25;   
 
bool A[N];   
 
   
 
for (int i = 2; i <= N; i++){
 
  if (A[i]){
 
    cout << i << " "; 
 
    for (int j = 2*i; j <= N; j=j+i)
 
      A[j] = false;
 
  }
 
 
}
 
}
 
</pre>
 
</pre>

Verzia zo dňa a času 13:41, 4. október 2011

Štatistika z N čísel

Úlohou je zistiť o N prečítaných číslach nejaké štatistické údaje.

Maximum a minimum

Zrejme stačí čítať čísla postupne a pamätať si zatiaľ najväčšie a zatiaľ najmenšie číslo.

  • Ako ale začať? Ako nastaviť maximum a minumum na začiatok?
    • Jedna možnosť je nastaviť ich tak, aby to boli nezmyselné hodnoty a iste sa zmenili - napriklad maximum veľmi malé a minumum veľké. Kto nám ale zaručí, že používateľ nedá všetky čísla ešte menšie? Riešením je použiť najväčšie resp. najmenšie možné číslo - ale je to škaredé.
    • Druhá možnosť je si pamätať, že ešte nemáme správne nastavené minimum a maximum a pri prvej príležitosti ich nastaviť.
    • A prvá príležitosť je pri prvom čísle. Môžeme to teda urobiť priamo. Minimum iste nebude väčšie ako toto prvé číslo a maximum iste nebude menšie.
int main(void){
  int max, min, x, N;

  cout << "Zadaj pocet cisel: ";
  cin >> N;

  cout << "Zadavajte cisla: ";
  cin << x;

  min=x; max=x;
  for (int i=1; i<N; i++){
    cin << x;
    if (x<min) {min=x;}
    if (x>max) {max=x;}
  }

  cout << endl << "Maximum je " << max << " a minumum je " << min << endl;  
}

Výskyty čísel 0..9

Ak vieme, že na vstupe sú iba čísla od 0 do 9, tak by sme chceli vedieť, koľko jednotlivých čísel je. Mohli by sme to riesiť takto:

  • Pre každú možnú hodnotu si vytvoríme jednu premennú (dokopy ich bude teda 10 - napríklad p0, p1 .. p9) na začiatku nastavenú na 0.
  • V prípade, že prečítané číslo bolo 0 zväčšíme hodnotu p0, ak bolo 1 zväčšíme p1 ...

Je to však pomerne komplikovaný spôsob - a to máme iba 10 rôznych premenných. Problém je v tom, že štvrtá premenná je p4 vieme iba my ako programátori a počítač o tom nevie - nemá žiaden súvis medzi jednotlivými premennými.

Teraz ukážeme riešenie tohoto príkladu pomocou poľa.

#include <iostream.h>

int main(void) {
  int p[10];
  int c,N;
 
  for (int i=0; i<10; i++) p[i]=0;   // inicializácia pola p[0]=0; p[1]=0; ... p[9]=0;

  cout << "Zadajte pocet cisel: ";
  cin >> N;
  cout << "Zadavajte " << N << "cisel: ";
  for (int i=0; i<N; i++){
    cin << c;
    if (c>=0 && c<10) p[c]++;    // test, či je číslo z požadovaného rozsahu
  }

  cout << endl; 
  for (i=0; i<10; i++) cout << i << ": " << p [i] << endl; // výpis
}

Priemer

Podobne ako pri maxime a minime si aj priemer vieme počítať postupne.

  • Budeme si počítať súčet doterajších čísel a na záver ho vydelíme ich počtom.
  • Dá sa robiť aj postupne - aby sme nemali zapamätané príliš veľké číslo (súčet)?

Ak by sme chceli o každom čísle vedieť, či je nadpriemerné alebo podpriemerné zjavne by sme si museli čísla zapamätať.

  • Keby sme vedeli dopredu, koľko ich bude vedeli by sme to urobiť podobne ako v predchádzajúcom príklade.
#include <iostream.h>

int main(void) {
  int N=20;
  int p[20];
  int sucet=0;
  double priemer;
 
  cout << "Zadavajte " << N << "cisel: ";
  for (int i=0; i<N; i++){
    cin << p[i];
    sucet=sucet+p[i];
  }

  priemer=sucet/N;
  cout << "Priemer je " << priemer << "." << endl; 
  for (int i=0; i<N; i++) 
   if (p[i]>priemer) cout << p[i] << ": vacsie ako priemer." << endl;
   else if (p[i]<priemer) cout << p[i] << ": mensie ako priemer." << endl;
   else cout << p[i] << ": priemer." << endl;
}
  • Ak by sme nevedeli počet čísel, môžeme aspoň odhadnúť, že ich nebude viac ako NMax, ktoré definujeme ako konštantu v programe.
  • A prečo vlastne nemôžeme dať ako veľkosť poľa N, ktoré si prečítame od používateľa?

Polia

  • Rozsah poľa je konštantný výraz väčší ako 0. Prvky sa indexujú od 0 po počet - 1
  • Občas sa dá ako rozsah použiť aj dopredu zadefinovaná premenná, ale napr. nasledovný program v C++ skompilujete ale v C nie. Takže opatrne!
  int i=100;
  int p [i]; - i je premenná, ktorá vznikne až počas behu programu, a teda jej hodnota nie je počas kompilácie známa.
  • Veľkosť poľa môže byť ohraničená v závislosti od kompilátora.

Vytvorenie a inicializácia poľa

V C++ je niekoľko pravidiel, ktoré určujú kedy ich môže používať a čo sa stane, ak počet prvok neodpovedá počtu hodnôt v inicializácii. Pole je možné inicializovať iba v definícii.

int A[4]={3, 6, 8, 10}; //spravne
int B[4]; //spravne
B[4]={3, 6, 8, 10};  //nespravne
B[0]=3; B[1]=6; B[2]=8; B[3]=10;

Pri inicializácii sa dá dodať aj menej hodnôt ako má pole. Napr. inicializácia iba dvoch prvých hodnôt. Pri čiastočnej inicializácii nastaví prekladač ostatné prvky na nulu.

double C[5]={5.0, 13.9}; // inicializuje C[0]=5.0, C[1]=13.9 a C[2]..C[4]=0
double C[5]={0}; // jednoduchá inicializácia všetkých prvkov na 0

Ak pri inicializácii poľa necháme hranaté zátvorky prázdne prekladač si sám spočíta prvky poľa. Nie je to však odporúčaný postup.

int A[]={1, 5, 3, 8}; // zistí rozsah poľa 0..3

Indexovanie hodnotou mimo intervalu

  int a [10], b [10];
  int i;
  for (i=0; i<10; i++) a [i]=random (100); // náhodné hodnoty

Pozor, kompilátor nekontroluje indexy prvkov

 a [11]=1234;.
  • Skompilujete, ale hodnota 12345 sa zapíše do pamäte na zlé miesto,
  • Môže to mať nepredvídateľné následky: prepísanie obsahu iných premenných (chybný výpočet alebo „nevysvetliteľné“ správanie sa programu) alebo prepísane časti kódu vášho programu (čo vo väčšine prípadov spôsobí „zamrznutie“ alebo reset počítača),


Kopírovanie a testovanie rovnosti

V prípade, že chceme vytvoriť pole, ktoré je kópiou už existujúceho poľa, ponúka sa možnosť príkazu priradenia b=a;. Takýto príkaz však neskompilujete – nedá sa takto priraďovať, treba kopírovať prvok po prvku.

  for (i=0; i<10; i++) b[i]=a[i];

Podobne sa nedá porovnávať polia pomocou podmienky if (a==b) cout << "Ok";. Takúto podmienku síce skompilujete, ale nikdy to nebude pravda – neporovná sa obsah poľa, ale niečo úplne iné (adresy polí v pamäti). Treba to riešiť opäť prvok po prvku.

  bool r=true;
  for (i=0; i<10; i++) { r=r && (a [i]==b [i]); }
  if (r) cout << "Ok\n"; 

Príklady na prácu s poľom

  • Načítajte pole čísel a vypíšte ho v opačnom poradí.
  • Skúste poradie povymienať priamo v poli a nie iba pri výpise.
  • Načítajte pole čísel a vypíšte ho v náhodnom poradí.
  • Ako by ste pole náhodne povymieňali priamo v pamäti?

Vector

Aby sme predišli problémom s určením rozsahu poľa môžeme použiť typ vector z knižnice STL. Ide o pole s dynamickou alokáciou pamäte, čo znamená, že ak by priestor, ktorý si vyhradil nestačil vyrieši si to sám.

int main(void){

  vector<int> A;
  int c,N;

  cout << "Zadaj pocet cisel: ";
  cin >> N;
  cout << "Zadavaj cisla: ";

  for (int i=0; i<N; i++){
    cin >> c;
    A.push_back(c);
  }
 
  cout << endl << "Teraz ich vypisem:";
  for (int i=0; i<N; i++){
    cout << A[i];
  }

}
  • Deklarovať vector môžeme jedným z nasledujúcich spôsobov
vector<int> A;       //vytvorí pole celých čísel
vector<int> A(10);   //vytvorí pole 10 celých čísel, ktoré všetky nastaví na default hodnotu
vector<int> A(5,1);  //vytvorí pole 5 celých čísel, ktoré nastaví na 1
  • Prístup k prvkom vectora je možný dvoma spôsobmi
    • klasicky pomocou A[index] - má podobné problémy ako polia
    • A.at(index) je bezpečnejší spôsob, kedy v prípade indexu mimo rozsahu nebude robiť neplechu
  • Vkladanie do poľa je možné tiež dvomi spôsobmi - ale pozor na miešanie
    • do už vytvoreného miesta (ak sme tak deklarovali vector) pomocou priradenia (ďalší priestor získam pomocou A.resize(nova hodnota))
    • pomocou A.push_back(x), kedy ako ďalší prvok nastaví x s tým, že o pamäť sa stará sám (veľkosť si potom môžeme zistiť pomocou A.size())

Kreslíme padajúce kruhy

  • Vytvoríme si polia pre x-ovú a y-ovú súradnicu kruhu.
  • Potrebujeme aj pole, do ktorého si budeme dávať celočíselné identifikátory nakreslených kruhov, aby sme ich neskôr mohli zmazať.
#include "../SimpleDraw.h"
#include <cstdlib>
#include <ctime>

int main(void) {
    const count = 30; /* počet kruhov */
    int size = 300; /* veľkosť obrázku */
    int diameter = 15; /* polomer kruhu */
    int step = 4; /* o kolko padne dolu v jednom kroku */
    int repeat = 50; /* pocet iteracii */
    double wait = 0.1; /* cakaj po kazdej iteracii */

    /* inicializácia generátora pseudonáhodných čísel */
    srand(time(NULL));

    SimpleDraw window(size, size);
    window.setBrushColor("lightblue");

    int x[count]; /* x-ova poloha kruzku */
    int y[count]; /* y-ove poloha kruzku */
    int id[count]; /* id objektu na obrazovke */

    /* kazdemu kruzku vygeneruj nahodnu polohu */
    for (int i = 0; i < count; i++) {
        x[i] = rand() % (size - diameter);
        y[i] = rand() % (size - diameter);
    }

    /* opakuj repeat iteracii */
    for (int r = 0; r < repeat; r++) {
        /* chod cez vsetky kruhy */
        for (int i = 0; i < count; i++) {
            /* ak nie sme v prvej iteracii, treba zmazat kruh */
            if (r > 0) {
                window.removeItem(id[i]);
            }
            /* zvys y-ovu suradnicu o step */
            y[i] += step;
            /* ak sme prilis nizko, zacni na vrchu na nahodnom x */
            if (y[i] >= size - diameter) {
                y[i] = 0;
                x[i] = rand() % (size - diameter);
            }
            /* vykresli kruzok na novom mieste */
            id[i] = window.drawEllipse(x[i], y[i], diameter, diameter);
        }
        /* na konci iteracie chvilu pockaj */
        window.wait(wait);
    }
}
  • Takéto polia nie sú ideálne, lebo údaje o jednom kruhu sú v troch rôznych poliach a bolo by logickejšie ich mať pokope.
  • Situácia by bola ešte horšia, ak by každý kruh mal napr. aj náhodnú farbu s troma zložkami R,G,B. To by sme potrebovali päť polí.
  • Na spojenie údajov k jednému kruhu použijeme dátovú štruktúru struct

Padajúce kruhy so struct

#include "../SimpleDraw.h"
#include <cstdlib>
#include <ctime>

struct kruh {
    int x, y; /* coordinates */
    int id;   /* id for deleting */
    int r, g, b; /* red, green, blue */
};

void generujKruh(kruh &k, int max) {
    k.x = rand() % max;
    k.y = rand() % max;
    k.r = rand() % 256;
    k.g = rand() % 256;
    k.b = rand() % 256;
    k.id = -1;
}

int main(void) {
    const count = 30; /* počet kruhov */
    int size = 300; /* veľkosť obrázku */
    int diameter = 15; /* polomer kruhu */
    int step = 4; /* o kolko padne dolu v jednom kroku */
    int repeat = 50; /* pocet iteracii */
    double wait = 0.1; /* cakaj po kazdej iteracii */

    int max = size - diameter; /* maximum possible coordinate */

    /* inicializácia generátora pseudonáhodných čísel */
    srand(time(NULL));

    SimpleDraw window(size, size);
    window.setBrushColor("lightblue");

    /* pole kruhov */
    kruh kruhy[count];

    /* kazdemu kruzku vygeneruj nahodnu polohu */
    for (int i = 0; i < count; i++) {
        generujKruh(kruhy[i], max);
    }

    /* opakuj repeat iteracii */
    for (int r = 0; r < repeat; r++) {
        /* chod cez vsetky kruhy */
        for (int i = 0; i < count; i++) {
            /* ak nie sme v prvej iteracii, treba zmazat kruh */
            if (r > 0) {
                window.removeItem(kruhy[i].id);
            }
            /* zvys y-ovu suradnicu o step */
            kruhy[i].y += step;
            /* ak sme prilis nizko, zacni na vrchu na nahodnom x */
            if (kruhy[i].y >= max) {
                generujKruh(kruhy[i], max);
                kruhy[i].y = 0;
            }
            /* vykresli kruzok na novom mieste */
            window.setBrushColor(kruhy[i].r, kruhy[i].g, kruhy[i].b);
            kruhy[i].id = window.drawEllipse(kruhy[i].x, kruhy[i].y,
                    diameter, diameter);
        }
        /* na konci iteracie chvilu pockaj */
        window.wait(wait);
    }
}