Knjiga namenjena pocetnicima u C++ programiranju. Obradjeno u Dev-C++ okruzenju i na Windows OS platformi.Full description
Uvod u Programiranje
Full description
Arduino
Skripta iz kolegija Arhitetektura racunala na temu uvod u asemblersko programiranje u embestuFull description
Za sve pocetnike u programiranju. Zbirka zadataka iz programskog jezika C. Pruza sjajan uvid u programsku sintaksu i nacin rada programskog jezika C.Full description
robotiFull description
Uvod u java programiranjeFull description
Pokazivaci u jeziku CFull description
Skripta iz programiranja C#Full description
fpn, haris ceric,
Full description
UUE
UUE
Italija, Španjolska, Flandrija, Nizozemska. Francuska, Rokoko i Neoklasicizam
•
Metodi
Kako je programiranje modeliranje realnih procesa, pri rešavanju problema u programiranju često smo u prilici, kao i u realnim procesima, da izvršavamo istu akciju nad različitim podacima, u različitim trenucima i na različitim mestima. Analizirajmo proces nastave u školi. U okviru tog procesa postoji akcija ocenjivanje učenika od strane profesora, koja se može dogoditi gotovo u svakom trenutku nastavnog procesa na ma kom času. Ona se odvija po određenim pravilima i vezuje se za objekte različitih klasa koji učestvuju u toj akciji, kao što su profesor P, učenik U, predmet PR ... Rezultat akcije je ocena koju dobija učenik U od profesora P iz predmeta PR. Slično, možemo posmatrati obavljanje različitih računskih operacija sa razlomcima. Pri svakoj operaciji od pomoći može biti „skraćivanje“ razlomaka koje najlakše izvršavamo određivanjem najvećeg zajedničkog delioca (NZD) za imenilac i brojilac. Akcija određivanja NZD se izvršava nad dva prirodna broja i njen rezultat je najveći prirodan broj koji je sadržan u oba broja.
Takve akcije prirodno je izdvojiti u nezavisne (programske) celine kako bi ih mogli pozvati i izvršiti sa različitih mesta i u različito vreme. Pri pozivu moramo navesti podatke nad kojima se akcije izvršavaju, kao i obezbediti mogućnost korišćenja rezultata akcija. U objektno orijentisanom programiranju za svaku od takvih akcija kreiramo odgovarajući metod. Do sada smo isključivo koristili metode koji opisuju reakciju sistema na određene događaje u toku izvršavanja aplikacije. Metod je imenovani blok naredbi. Svaki metod sastoji se iz zaglavlja i tela metoda. U zaglavlju prvo navodimo kog je tipa rezultat metoda, zatim ime metoda za kojim sledi u malim zagradama spisak parametara. Posle zaglavlja navodimo telo metoda koje počinje otvorenom a završava se zatvorenom vitičastom zagradom. ()
predstavlja tip rezultata metoda i može biti bilo koji tip programskog jezika C#. Može se dogoditi da akcija koju opisujemo metodom ne proizvodi nikakav rezultat već samo izvršava određeni niz instrukcija. U tom slučaju za opis povratnog tipa koristimo rezervisanu reč void. je identifikator koji programer samostalno kreira i koje se koristi pri pozivu metoda. Pravila za formiranje imena (identifikatora) metoda su ista kao i za imena promenljivih. Pri kreiranju imena poželjno je da programer vodi računa o značenju i sadržaju metoda radi lakšeg korišćenja istog. se navodi između malih zagrada, i koristimo je za opis podataka koje metodu prenosimo, a na osnovu kojih će se vršiti obrada u telu metoda. Za svaki parametar navodi se tip a zatim ime parametra.
1
Može se navesti i jedna od službenih reči, ref ili out , ispred tipa parametra. Njima se određuje način prenosa parametra i to ćemo kasnije posebno razmatrati.
Ako metod ima više od jednog parametra međusobno ih razdvajamo zarezom. Metod može biti bez parametara, ali se zagrade moraju navesti. Kako pri kreiranju metoda nemamo konkretne podatke koje obrađujemo, parametri koje navodimo u listi parametara parametara su fiktivni, odnosno oni samo „čuvaju mesto“ za podatke koji će biti prosleđeni metodu pri njegovom pozivu. U okviru jedne klase može postojati više metoda metoda istog imena ali se u tom slučaju oni moraju razlikovati u listi parametara, ili po broju ili po tipu parametara. Metode istog imena sa različitim listama parametara zovemo preopterećenim metodima. Zaglavlje metoda maxDvaBroja koji vraća rezultat tipa int a ima dva parametra, a i b tipa int izgleda izgleda ovako int maxDvaBroja(int a, int b)
Ovakav metod možemo preopteretiti metodom koji na osnovu dva realna broja vraća realan broj double maxDvaBroja(double a, double b)
Najveći zajednički delilac dva prirodna broja možemo odrediti upotrebom metoda NZD čije zaglavlje izgleda ovako unsigned NZD(unsigned a, unsigned b)
Slično, zaglavlje metoda pisi koji koji ne vraća vrednost, a ima jedan parametar s tipa string izgleda ovako void pisi(string s)
Ako metod pisi nema nema parametara njegovo zaglavlje bi izgledalo ovako void pisi()
sastoji se od odgovarajućih iskaza programskog jezika C# koji su navedeni između vitičastih zagrada. Upravo ti iskazi se izvršavaju kada se metod pozove. Ukoliko je povratni tip metoda različit od void, neophodno je da bar jedan od iskaza u telu metoda bude oblika return izraz;
Ovaj iskaz omogućava izlazak iz metoda pri čemu je vrednost izraza rezultat metoda. Pri tome povratni tip metoda i tip izraza, koji se navodi iza rezervisane reči return , moraju biti isti. Iskaz return možemo koristiti i u metodima tipa void za napuštanje napuštanje metoda. Kako takvi metodi nemaju rezultat u iskazu ne navodimo izraz.
Vrlo često je pri obradi podataka osim podataka koje obrađujemo neophodno koristiti i pomoćne promenljive za registrovanje međurezultata nastalih pri obradi. U metodima, podaci koje obrađujemo su predstavljeni fiktivnim parametrima, a pomoćne promenljive navodimo u telu metoda i njima se ne može pristupiti van metoda u kojem su definisane. Vrlo često pomoćne promenljive definisane u metodu nazivamo lokalnim promenljivima. Navedimo primere nekih jednostavnih metoda. 2
o
metod za određivanje većeg od dva cela broja
int maxDvaBroja(int a, int b) { int max; if (a > b) max = a; else max = b; return max; }
U ovom metodu je definisana lokalna promenljiva max koju koristimo za određivanje rezultata obrade i čiju vrednost vraćamo kao rezultat metoda. Iskazom return obezbeđuje se vraćanje rezultata iz metoda, kao i prekidanje daljeg izvršavanja metoda. Uobičajeno je da se ovaj iskaz nalazi na kraju tela metoda. Često smo u situaciji da pod određenim uslovima treba da prekinemo izvršavanje metoda pre kraja. U tom slučaju iskaz return navodimo u naredbi grananja kojom proveravamo ispunjenost određenog uslova. Tako, metod za određivanje maksimuma dva broja možemo napisati i na sledeći način. int maxDvaBroja(int a, int b) { if (a > b) return a; return b; } o
metod za sabiranje dva cela broja
int saberi(int a, int b) { return a + b; } o
metod za prikazivanje teksta na formi
void pisi(string s) { label1.Text = s; return ; // ovaj iskaz i skaz mozemo izostaviti }
Iskaz return bez navođenja izraza koristimo kod metoda koje ne vraćaju vrednost čiji je povratni tip void . U takvim metodama ako je iskaz return poslednji, možemo ga izostaviti, jer se metod automatski prekida kada izvršavanje dođe do kraja. void pisi(string s) { ... if(...) { ... return ; // ovaj iskaz ne mozemo izostaviti }
3
... return ; // ovaj iskaz i skaz mozemo izostaviti }
Kada se u toku izvršavanja aplikacije ukaže potreba za izvršavanje akcije opisane metodom, neophodno je obezbediti dvosmernu komunikaciju metoda iz koga pokrećemo akciju (izvorni metod) i metoda kojim opisujemo akciju (pozvani metod). To postižemo pozivom metoda, tako što fiktivnim parametrima pozvanog metoda prosleđujemo vrednosti neophodne za izvođenje akcije a u izvorni metod vraćamo rezultat izvedene akcije. Metod pozivamo na sledeći način ()
izraze čijim vrednostima inicijalizujemo fiktivne parametre. Navedeni izrazi moraju se po broju i tipu slagati sa opisom fiktivnih parametara u definiciji metoda. Parametre međusobno odvajamo zarezom. Ako metod nema parametara pri njegovom pozivu moramo navesti zagrade.
stvarnih
parametara>sadrži
Metod maxDvaBroja ima dva parametra tipa int pa njegov poziv, ako su promenljive x i i y tipa tipa int , može da izgleda ovako: ovako: maxDvaBroja(17,89); maxDvaBroja(Convert.ToInt32(tB1.Text),Convert.ToInt32(tB2.Text)); maxDvaBroja(x,y); maxDvaBroja(x+34,300-2*y);
Kada metod vraća neku vrednost, ta vrednost može da se upotrebi svuda gde se može upotrebiti vrednost povratnog tipa. Na primer, metod maxDvaBroja vraća int , pa tu vrednost možemo koristiti svuda gde možemo koristiti cele brojeve. int a,b,x,y,z = maxDvaBroja2(12,89); label1.Text = z.ToString(); label1.Text = maxDvaBroja2(12,89).ToString(); z= maxDvaBroja(maxDvaBroja(x, maxDvaBroja(maxDvaBroja(x,y), y), maxDvaBroja(a,b)); if (maxDvaBroja(x, y) > 100) { ... } else { ... }
4
Metod možemo pozivati iz svih metoda klase u kojoj je definisan, a metodi čiji je nivo pristupa public mogu se pozivati i van klase klase u kojoj su definisani. Prilikom poziva metoda tok aplikacije se prenosi u pozvani metod dok se on ne završi a zatim se nastavlja u izvornom metodu. Da bi po završetku pozvane metode osigurali povratak i uspešan nastavak izvorne metode neophodno je u memoriji zapamtiti adresu povratka u izvorni metod i vrednosti lokalnih promenljivih izvornog metoda. Te informacije se pamte u stek memoriji, i služe da se po završetku pozvanog metoda tok aplikacije vrati u izvorni metod na mesto na koje nam ukazuje adresa povrataka kao i da se obnove vrednosti lokalnih promenljivih. Posle povratka te informacije se iz steka uklanjaju. o Primeri
aplikacija sa metodama 1. Kreirati aplikaciju koja omogućava crtanje automobila po formi. Korisnik crta automobil tako što pritisne taster miša gde želi da se iscrta automobil. Svaki iscrtani automobil je drugačije veličine i boje. Napisati metodu koja crta jedan automobil sa zadatim parametima, pozicijom, veličinom i bojom.
U listi argumenata matode se nalaze promenljive koje opisuju jedan automobil. Celi brojevi x i y su koordinate tačke na kojoj se nalazi centar automobila. Celi brojevi a i b su dimenzije. Na slici je prikazan koordinatni sistem i potrebne koordinate za iscrtavanje automobila.
5
public void Auto( public void Auto( int int x, x, int y, y, int a, a, int b, b, Color boja, boja, Graphics g) g) { Pen olovka olovka = new new Pen Pen ( (Color C olor .Black, .Black, 2); SolidBrush cetka cetka = new new SolidBrush SolidBrush (boja); (boja); g.FillRectangle(cetka, x - a / 4, y - b / 2, a / 2, b / 2); g.FillRectangle(cetka, x - a / 2, y, a, b / 2); g.DrawLine(olovka, x - a / 4, y - b / 2, x + a / 4, y - b / 2); g.DrawLine(olovka, x - a / 2, y + b / 2, x + a / 2, y + b / 2); g.DrawLine(olovka, x - a / 2, y, x - a / 4, y); g.DrawLine(olovka, x + a / 2, y, x + a / 4, y); g.DrawLine(olovka, x - a / 2, y, x - a / 2, y + b / 2); g.DrawLine(olovka, x + a / 2, y, x + a / 2, y + b / 2); g.DrawLine(olovka, x - a / 4, y, x - a / 4, y - b / 2); g.DrawLine(olovka, x + a / 4, y, x + a / 4, y - b / 2); cetka.Color = Color .Black; .Black; g.FillEllipse(cetka, x - a / 4 - a / 6, y + b / 2 - a / 6, a / 3, a / 3); g.FillEllipse(cetka, x + a / 4 - a / 6, y + b / 2 - a / 6, a / 3, a / 3); } Random r r = new new Random Random (); (); private void private void Form1_MouseClick( Form1_MouseClick( object object sender, sender, MouseEventArgs e) e) { int a a = r.Next(40, 200); int b b = a / 2; Graphics g g = CreateGraphics(); Cr eateGraphics(); Color boja boja = Color .FromArgb(r.Next(256), .FromArgb(r.Next(256), r.Next(256), r.Next(256)); Auto(e.X, e.Y, a, b, boja, g); }
2. Kreirati aplikaciju u kojoj se automobil kreće po formi sa leva na desno. Kada stigne do kraja forme, krene iz početka.
6
Ukoliko su u novu aplikaciju iskopira metod iz prethodnog zadatka koji sadrži najteži deo, sama aplikacija postaje jednostavna. int x, y; private void Form1_Load(object sender, EventArgs e) { x = ClientRectangle.Width / 2; y = ClientRectangle.Height / 2; timer1.Start(); //isto sto i timer1.Enabled=true } private void Form1_Paint(object sender, PaintEventArgs e) { Graphics g = CreateGraphics(); Auto(x, y, 150, 75, Color.YellowGreen, g); } private void timer1_Tick(object sender, EventArgs e) { x += 5; x %= ClientRectangle.Width+75; Refresh(); }
3. Napisati metode koje na osnovu dužine stranice jednokostraničnog trougla računaju njegovu: površinu, visinu i poluprečnik opisane kružnice, kao i metodu koja crta trougao i njegove upisanu i opisanu kružnicu. Kreirati aplikaciju koja omogućava da korisnik dobije podatke o krugu na osnovu unete dužine stranice.
7
double Povrsina( Povrsina( double double a) a) { return (a (a * a * Math .Sqrt(3)) .Sqrt(3)) / 4; } double Visina( Visina( double double a) a) { return (a (a * Math .Sqrt(3)) .Sqrt(3)) / 2; } double PoluprecnikOpisane( PoluprecnikOpisane( double double a) a) { return 2.0 2.0 / 3 * Visina(a); } double PoluprecnikUpisane( PoluprecnikUpisane( double double a) a) { return 1.0 1.0 / 3 * Visina(a); } void CrtajTrougao( CrtajTrougao( double double a) a) { Graphics g g = pictureBox1.CreateGraphics(); pi ctureBox1.CreateGraphics(); g.Clear(pictureBox1.BackColor); Pen olovka olovka = new new Pen Pen ( (Color C olor .Black, .Black, 2); int x0 x0 = pictureBox1.Width / 2; int y0 y0 = pictureBox1.Height / 2; Point A A = new new Point Point (x0, (x0, ( int int )(y0 )(y0 - PoluprecnikOpisane(a))); Point B = new Point (( (( int int )(x0 )(x0 a / 2), ( int int )(y0 )(y0 + PoluprecnikUpisane(a))); Point C = new Point (( (( int int )(x0 )(x0 + a / 2), ( int int )(y0 )(y0 + PoluprecnikUpisane(a))); g.DrawLine(olovka, A, B); g.DrawLine(olovka, B, C); g.DrawLine(olovka, C, A); double r r = PoluprecnikUpisane(a); double R R = PoluprecnikOpisane(a); g.DrawEllipse(olovka, ( int int )(x0 )(x0 - r), ( int int )(y0 )(y0 - r), ( int int )(2 )(2 * r), ( iint nt )(2 )(2 * r));
8
g.DrawEllipse(olovka, ( int int )(x0 )(x0 - R), ( int int )(y0 )(y0 - R), ( int int )(2 )(2 * R), ( int int )(2 )(2 * R)); } private void private void button1_Click( button1_Click( object object sender, sender, EventArgs e) e) { try { double a a = Convert .ToDouble(tbA.Text); .ToDouble(tbA.Text); lbA.Items.Add(a.ToString( "0.000" "0.000" )); )); lbP.Items.Add(Povrsina(a).ToString( "0.000" "0.000" )); )); lbH.Items.Add(Visina(a).ToString( "0.000" "0.000" )); )); lbR.Items.Add(PoluprecnikOpisane(a).ToString( "0.000" "0.000" )); )); CrtajTrougao(a); tbA.Clear(); } catch { MessageBox .Show( .Show( "Greska" "Greska" ); ); } }
4. Kreirati aplikaciju koja omogućava simulaciju rada semafora za vozila (tri kruga, crveni, žuti i zeleni se naizmenično uključuju po poznatim pravilima). Omogućiti prelazak iz uobičajenog režima rada semafora u režim žutog trepćućeg svetla.
Svetla na semaforu smenjuju se u unapred određenim vremenskim intervalima, pa je neophodno korišćenje objekta klase Timer . Postoji 4 različita stanja na semaforu koja različito traju i njihov redosled je tačno određen: • iz stanja 1 (uključeno crveno) prelazimo u stanje 2 (crveno i žuto); • iz stanja 2 prelazimo u stanje 3 (zeleno); • •
iz stanja 3 prelazimo u stanje 4 (žuto); iz stanja 4 prelazimo u stanje 1;
9
Za različita stanja ćemo koristiti enumeraciju StanjeSemofora koja može da ima 4 različite vrednosti. Uvođenjem enumeracije, smo dodelili d odelili odgovarajuća StanjeSemafora.Crveno, i čitka imena stanjima: StanjeSemafora.CrvenoZuto, StanjeSemafora.Zuto.
StanjeSemafora.Zeleno
i
Semafor ćemo kreirati kao strukturu koju definišu stanje u kojem se nalazi, koordinate položaja i dimenzije. Kreiramo metod CrtajSvetlo koji na osnovu rednog broja (1 – crveno svetlo, 2 - žuto svetlo i 3 - zeleno), zeleno), boje i podatka o tome da li je svetlo upanjeno ili ugašeno odgovarajući crta krug. Metod CrtajSemafor iscrtava semafor pozivom metoda CrtajSvetlo u trenutnom stanju. Iscrtavanje aktivnog stanja obavljamo u okviru događaja Tick objekta klase Timer pozivom metoda CrtajSemafor i i uz proveru koliko koje stanje traje. enum StanjeSemafora enum StanjeSemafora { Crveno, CrvenoZuto, Zeleno, Zuto } struct Semafor { public StanjeSemafora stanje; stanje; public int x, x, y, a; } void CrtajSvetlo( CrtajSvetlo( Graphics Graphics g, g, Semafor s, s, int i, i, Color boja, boja, bool ispunjen) ispunjen) { SolidBrush cetka cetka = new new SolidBrush SolidBrush ( (Color C olor .White); .White); Pen olovka olovka = new new Pen Pen (boja, (boja, 3); if (ispunjen) (ispunjen) cetka.Color = boja; g.FillEllipse(cetka, s.x, s.y + (i - 1) * s.a, s.a, s.a); g.DrawEllipse(olovka, s.x, s.y + (i - 1) * s.a, s.a, s.a); } void CrtajSemafor( CrtajSemafor( Graphics Graphics g, g, Semafor s) s) { if (s.stanje (s.stanje == StanjeSemafora .Crveno) .Crveno) { CrtajSvetlo(g, s, 1, Color .Red, .Red, true ); ); CrtajSvetlo(g, s, 2, Color .Yellow, .Yellow, false ); ); CrtajSvetlo(g, s, 3, Color .Green, .Green, false ); ); } else if else if (s.stanje (s.stanje == StanjeSemafora .CrvenoZuto) .CrvenoZuto) { CrtajSvetlo(g, s, 1, Color .Red, .Red, true ); ); CrtajSvetlo(g, s, 2, Color .Yellow, .Yellow, true ); ); CrtajSvetlo(g, s, 3, Color .Green, .Green, false ); ); } else if else if (s.stanje (s.stanje == StanjeSemafora .Zeleno) .Zeleno) { CrtajSvetlo(g, s, 1, Color .Red, .Red, false ); ); CrtajSvetlo(g, s, 2, Color .Yellow, .Yellow, false ); ); CrtajSvetlo(g, s, 3, Color .Green, .Green, true ); ); } else if else if (s.stanje (s.stanje == StanjeSemafora .Zuto) .Zuto)
5. Kreirati aplikaciju koja omogućava da se prikaže sledeći datum za uneti datum. Napisati metodu koja na osnovu jednog datuma datu ma vraća sledeći.
Kreiramo strukturu Datum koja sadrži tri polja: d za za dan, m za mesec i g za godinu. Metod SledeciDan na na osnovu datuma vraća sledeći datum. Ukoliko je datum 31. decembar, metod treba da vrati 1. januar sledeće godine. U ostalim slučajevima je potrebno da uvećamo broj dana za 1 i da proverimo da time nismo dobili veći broj dana nego što tekući mesec ima. Ukoliko se to dogodilo, treba da vratimo prvi datum u sledećem mesecu. struct Datum struct Datum
11
{ public int public int d, d, m, g; } Datum SledeciDan( SledeciDan( Datum Datum dan) dan) { Datum sledeci; sledeci; if (dan.d (dan.d == 31 && dan.m == 12) { sledeci.d = 1; sledeci.m = 1; sledeci.g = dan.g + 1; return sledeci; sledeci; } sledeci.d = dan.d + 1; sledeci.m = dan.m; sledeci.g = dan.g; if (sledeci.d (sledeci.d > BrDanaUMesecu(dan.m, dan.g)) { sledeci.d = 1; sledeci.m++; } return sledeci; sledeci; } private bool private bool Prestupna( Prestupna( int int g) g) { return (g (g % 4 == 0 && g % 100 != 0) || g % 400 == 0; } private int private int BrDanaUMesecu( BrDanaUMesecu( int int m, m, int g) g) { switch (m) (m) { case 1: 1: case 3: 3: case 5: 5: case 7: 7: case 8: 8: case 10: 10: case 12: 12: return 31; 31; case 4: 4: case 6: 6: case case 9: 9: case 11: 11: return 30; 30; case 2: 2: if (Prestupna(g)) (Prestupna(g)) return 29; 29; return 28; 28; default : return 0; 0; } } private void void btnSledeci _Click( object object sender, sender, EventArgs e) e) { Datum dan; dan; dan.d = Convert .ToInt32(tbDan.Text); .ToInt32(tbDan.Text); dan.m = Convert .ToInt32(tbMesec.Text); .ToInt32(tbMesec.Text); dan.g = Convert .ToInt32(tbGodina.Text); .ToInt32(tbGodina.Text); Datum sledeci sledeci = SledeciDan(dan); tbSledeci.Text = sledeci.d + "." + + sledeci.m + "." + + sledeci.g + "." ; }
12
•
Nešto više o metodima
o Prenos
parametara metodima
U svim primerima koje smo do sada realizovali pri pozivu metoda kreirana je kopija vrednosti parametra i prosleđivana metodu. Ukoliko se pri pozivu na mestu stvarnog parametra nalazi promenljiva metodu se prosleđuje kopija njene vrednosti, tako da svaka dalja komunikacija promenljive i metoda tu prestaje. Samim tim vrednost promenljive ostaje ista i posle izvršavanja metoda, bez obzira na promene prosleđene kopije u toku izvršavanja metoda. Ukoliko želimo da menjamo vrednost promenljive metodom, moramo mu proslediti adresu lokacije na kojoj se promenljiva nalazi da bi metod mogao da joj pristupi i po potrebi menja. U programskom jeziku C# ovakav prenos parametra parametra realizujemo upotrebom službenih službenih reči ref ili ili out u zaglavlju i u pozivu metoda. U zaglavlju službenu reč ref ili out navodimo ispred tipa parametra, a u pozivu ispred stvarnog parametra koji u ovom slučaju mora biti promenljiva. Službenu reč ref možemo koristiti isključivo ukoliko je promenljiva koju koristimo koristimo kao stvarni parametar inicijalizovana pre poziva metoda. Službenu reč out , koristimo ukoliko pri određivanju nove vrednosti promenljive koju koristimo kao stvarni parametar ne koristimo njenu prethodnu vrednost, ukoliko ukoliko je ima, a u metodu joj moramo dodeliti dodeliti vrednost. Prema tome, ref koristimo ako menjamo postojeću vrednost promenljive a out ako u metodi izračunavamo potpuno novu vrednost, nezavisnu od prethodne. Na mestu stvarnih parametara mogu se nalaziti promenljive i vrednosnog i referentnog tipa. Ukoliko pri prenosu ne koristimo službene reči ref i out , kao što smo prethodno naglasili, metodu se prenosi kopija vrednosti te promenljive. Kako je kod referentnog tipa vrednost promenljive zapravo adresa objekta metod raspolaže tom adresom i može pristupiti objektu pa samim tim i promeniti njegovo stanje. Sve promene izvršene nad tim objektom u toku izvršavanja metoda ostaju i po završetku metoda, jer se ne pravi kopija objekta već kopija njegove adrese. Ukoliko samu adresu menjamo u toku metoda to nema uticaja na vrednost promenljive referentnog tipa koja je prosleđena kao stvarni parametar. Pri korišćenju službene reči ref promenljiva koja se prenosi kao stvarni parametar mora biti inicijalizovana. Ukoliko je u pitanju promenljiva referentnog tipa možemo promeniti ne samo stanje nego i lokaciju objekta, odnosno napraviti potpuno novi objekat na koji ukazuje referentna promenljiva koja je prosleđena metodu kao stvarni parametar.
Primer: U Primer: U ovom primeru ćemo razmatrati promene vrednosti vrednosne (int) i referenetne (Pen) promenljive, koje prosleđujemo kao stvarne parametre određenih određenih metoda, u zavisnosti od upotrebe službene reči ref u tim metodima.
Posmatrajmo metod f1 u kome se za oba parametra prenose kopije vrednosti promenljivih ( x ,o) navedenih u pozivu. •
void f1( f1( int int broj, broj, Pen olovka) olovka) { broj = 10; olovka.Color = Color .Blue; .Blue; }
Bez obzira što se u metodu f1 menja vrednost parametra broj , kojem u pozivu odgovara promenljiva x , vrednost promenljive x se se neće promeniti jer je metodu f1 prosleđena kopija njene vrednosti odnosno broj 5. Međutim, vrednost promenljivih referentnog tipa je adresa lokacije na kojoj je promenljiva kreirana, pa kako je parametar olovka referentnog tipa (Pen) a njemu pri pozivu odgovara objekat klase Pen, o, metodu f1 se prenosi adresa memorijske lokacije na kojoj je kreirana o. Zato pri promeni atributa Color u metodu f1, zaista menjamo boju objekta kreiranog u metodu btPromene_Click čija je adresa prosleđena kao stvarni parametar.
14
U metodu f2 uz prvi parametar navodimo službenu reč ref (i pri definiciji i pri pozivu metoda) čime smo obezbedili da se metodu prosledi adresa promenljive x koja se u pozivu pojavljuje kao stvarni parametar. •
void f2( f2( ref ref int broj, broj, Pen olovka) olovka) { broj = 10; olovka.Color = Color .Green; .Green; }
U ovom slučaju promena vrednosti parametra broj u u metodu f2 dovodi do promene vrednosti promenljive x jer je metodu f2 prosleđena adresa promenljive x pa pa se sve promene parametra broj izvršavaju izvršavaju na lokaciji na kojoj je registrovana promenljiva x . Za drugi parametar situacija je ista kao i u metodu f1.
U metodu f3 menjamo vrednost parametra olovka tako što kreiramo potpuno novi objekat klase Pen i adresu lokacije na kojoj je kreiran dodeljujemo parametru olovka. •
void f3( f3( Pen olovka) olovka) { olovka = new Pen ( ( Color Color .Indigo); .Indigo); }
Metodu f3 je pri pozivu prosleđena adresa lokacije na kojoj je kreirana promenljiva o. Kreiranjem novog objekta iste klase parametru metode dodeljuje se adresa lokacije na kojoj je objekat formiran ali to nema uticaja na promenljivu o jer se njena vrednost (adresa memorijske lokacije) određena u metodu btPromene_Click ne ne menja.
Ako želimo da u metodu „premestimo“ postojeći objekat klase Pen moramo, kao u metodu f4, koristiti službenu reč ref i tako metodu proslediti adresu na kojoj je zapisana adresa memorijske lokacije gde je kreiran stvarni parametar o. •
void f4( f4( ref ref Pen olovka) olovka) {
15
olovka = new Pen ( ( Color Color .Indigo); .Indigo); }
U metodu f4, kao i u metodu f3, kreiramo novi objekat klase Pen ali adresu lokacije na kojoj je kreiran upisujemo na mesto gde je upisana adresa stvarnog parametra (promenljive o) čime stvarni parametar menjamo.
Promenljive vrednosnog tipa prenosimo metodu bez korišćenja službene reči ref ako ako ne želimo da taj metod menja njihovu vrednost. U suprotnom, ako ako želimo da metod menja vrednost promenljive vrednosnog tipa, koristimo ref . Promenljive referentnog tipa prenosimo metodu bez korišćenja službene reči ref ako ako ne želimo da menjamo memorijsku lokaciju promenljive, pri čemu sadržaj lokacije možemo menjati. U suprotnom, ako želimo da u metodu menjamo lokaciju promenljive koristimo ref .
Primena metoda sa ref i out parametrima
1. U magacinu su prazne kartonske kutije nabacane na gomilu pa zauzimaju puno prostora. Magacioner slaže kutije na palete tako što uzima jednu po jednu kutiju sa gomile i stavlja svaku u prethodno složenu kutiju ako je to moguće, ili na novu paletu ako nije. Kreirati aplikaciju kojim se određuje koliko paleta je upotrebljeno i koliko kutija ima na svakoj. Dimenzije svake od kutija (tri cela broja odvojena sa po jednom prazninom) redom kojim ih magacioner slaže na palete navedene su u po jednom redu tekstualne datoteke kutije.txt. Kutija se stavlja u drugu kutiju tako da odgovarajuće ivice budu međusobno paralelne. Kutija dimenzija a, b, c ( (a ≤ b ≤ c ) može se staviti u kutiju dimenzija a1, b1, c1 (a1 ≤ b1 ≤ c1), akko odgovarajuće dimenzije zadovoljavaju sledeći poredak a
Prilikom prenosa parametara a, b i c koristimo koristimo službenu reč out jer ih ovom metodom inicijalizujemo. Ovaj metod vraća true ako možemo iz datoteke pročitati dimenzije kutije inače vraća false. bool uzmiSledecuKutiju( uzmiSledecuKutiju( StreamReader StreamReader sr, sr, out int a, out int b, out int c) { a = b = c = 0; if (sr.EndOfStream) (sr.EndOfStream) return return false false ; string s s = sr.ReadLine(); int p1, p1, p2; p1 = s.IndexOf( ' ' ); );
Dovođenje dimenzija a, b, c u u poredak a<=b<=c realizujemo realizujemo metodom void uredi( uredi( ref ref int a, a, ref int b, b, ref int c) c)
Ova metoda može promeniti vrednosti parametara a, b, c pa pa zato koristimo službenu reč ref . void uredi(ref uredi(ref int a, ref int b, ref int c) { if (a (a > b) razmeni( ref ref a, a, ref b); b); if (a (a > c) razmeni( ref ref a, a, ref c); c); if (b (b > c) razmeni( ref ref b, b, ref c); c); } void razmeni(ref razmeni(ref int a, ref int b) { int p; p; p = a; a = b; b = p; } private void private void btOdredi_Click( btOdredi_Click( object object sender, sender, EventArgs e) e) { StreamReader sr sr = new StreamReader( "kutije.txt" "kutije.txt" ); ); int a, a, b, c, a1, b1, c1; int brojKutijaNaPaleti brojKutijaNaPaleti = 0, brojPaleta = 0; a1 = b1 = c1 = 0; while (uzmiSledecuKutiju(sr, (uzmiSledecuKutiju(sr, out a, out b, out c)) { uredi( ref ref a, a, ref b, b, ref c); c); if (a (a < a1 && b < b1 && c < c1) brojKutijaNaPaleti++; else { if (brojKutijaNaPaleti (brojKutijaNaPaleti > 0) { listBox1.Items.Add(brojKutijaNaPaleti); brojPaleta++; } brojKutijaNaPaleti = 1; } a1 = a; b1 = b; c1 = c; } if (brojKutijaNaPaleti (brojKutijaNaPaleti > 0) { listBox1.Items.Add(brojKutijaNaPaleti); brojPaleta++; } listBox1.Items.Add( "-------" "-------" ); ); listBox1.Items.Add( "Ukupno "Ukupno paleta " + + brojPaleta); sr.Close(); }
17
2. Napisati metod kojim se za dati datum (dan, mesec, godina) određuje datum sutrašnjeg dana. Ukoliko posle poziva metoda nećemo koristiti polazni datum možemo promenljive koje ga definišu iskoristiti za prihvatanje rezultata. U tom slučaju pri prenosu parametara prirodno je koristiti službenu reč ref, jer stvarni paramatri imaju vrednost pre poziva metoda koja se menja u metodu. void Sutra( Sutra( ref int ref int d, d, ref ref int int m, m, ref ref int int g) g) { d++; if (d (d > BrDanaUMesecu(m, g)) { d = 1; m++; if (m (m == 13) { m = 1; g++; } } } int BrDanaUMesecu( BrDanaUMesecu( int int m, m, int g) g) { switch (m) (m) { case 1: 1: case 3: 3: case 5: 5: case 7: 7: case 8: 8: case 10: 10: case 12: 12: return 31; 31; case 4: 4: case 6: 6: case 9: 9: case 11: 11: return 30; 30; case 2: 2: if (Prestupna(g)) (Prestupna(g)) return 29; 29; return 28; 28; default : return 0; 0; } } bool Prestupna( Prestupna( int int g) g) { return (g (g % 4 == 0 && g % 100 != 0) || g % 400 == 0; }
Ukoliko želimo da posle poziva metoda koristimo i polazni datum neophodno je uvesti nove parametre metoda za prihvatanje rezultata. U tom slučaju pri prenosu tih parametara prirodno je koristiti službenu reč out , jer stvarni paramatri dobijaju vrednost u metodu. void Sutra( Sutra( int int d, d, int m, m, int g, g, out int d1, d1, out int m1, m1, out int g1) g1) { d1 = d + 1; m1 = m; g1 = g; if (d1 (d1 > BrDanaUMesecu(m1, g1)) { d1 = 1; m1++; if (m1 (m1 == 13)
18
{ m1 = 1; g1++; } } }
3. U ulaznom fajlu u okviru teksta se nalaze i prirodni brojevi (niz uzastopnih dekadnih cifara). Kreirati aplikaciju kojom se određuje koliko brojeva ima i koji je najveći broj u fajlu kao i redni broj linije koja sadrži najviše brojeva. Čitamo sadržaj fajla znak po znak. Kad naiđemo na cifru pozovemo metod citajBroj kojim se formira broj od cifara koje slede. Ovaj metod ima parametar fajl iz kojeg se čita (sr ) i adresu parametra ch tipa char , jer je posle poziva metoda potrebno znati koji znak sledi iza pročitanog broja. Metod citajBroj vraća vraća vrednost pročitanog broja. Prilikom formiranja broja, iz fajla čitamo znak po znak dok je pročitani znak cifra ili dok ne dostignemo kraj fajla. Broj formiramo, polazeći od 0, tako što cifre redom dopisujemo na kraj trenutne vrednosti broja (trenutnu vrednost broja množimo sa 10 i dodajemo novu cifru). U ovom primeru proveru da li je znak dekadna cifra vršimo korišćenjem statičke metode IsDigit strukture char . Metod IsDigit ima parametar tipa char , a vraća true ako ako je znak prenet kao parametar dekadna cifra, cifra, u suprotnom vraća false . int citajBroj( citajBroj( StreamReader StreamReader sr, sr, ref int ch) ch) { int a a = 0; while (ch (ch != -1 && char char .IsDigit(( .IsDigit(( char char )ch)) )ch)) { a = a * 10 + ch - '0' ; ch = sr.Read(); } return a; } private void cbIzborFajla_SelectedIndexChanged( cbIzborFajla_SelectedIndexChanged( object object sender, sender, EventArgs e) e) { StreamReader sr sr = new new StreamReader StreamReader (cbIzborFajla.Text (cbIzborFajla.Text + ".txt" ); ); int x, x, maxBroj, brBrojeva, brULiniji, tekLin, maxLin, maxBrULin; maxBroj = -1; // -1; // maksimalan broj u fajlu fajlu brULiniji = 0; // broj brojeva u tekucoj linija brBrojeva = 0; // 0; // ukupan broj brojeva tekLin = 1; // redni broj tekuce linije maxLin = 0; // 0; // redni broj linije koja sadrzi najvise brojeva maxBrULin = 0; // 0; // broj brojeva u liniji koja sadrzi sadrzi najvise brojeva int ch ch = sr.Read(); while (ch (ch != -1) { //provera da li pocinje broj if ( ( char char .IsDigit(( .IsDigit(( char char )ch)) )ch)) { // citanje broja x = citajBroj(sr, ref ch); ch); // uvecanje ukupnog broja brojeva brBrojeva++; // uvecanje broja brojeva u liniji brULiniji++;
19
// provera da li je procitani broj za sada najveci broj if (x (x > maxBroj) maxBroj = x; } //provera da li je dostignut kraj linije if (ch (ch == '\n' ) { //provera da li tekuca linija ima za sada najvise brojeva if (brULiniji (brULiniji > maxBrULin) { maxBrULin = brULiniji; maxLin = tekLin; } //povecanje rednog broja tekuce linije tekLin++; //broj brojeva u novoj liniji je 0 brULiniji = 0; } //provera da li je dostignut kraj fajla if (ch (ch != -1) ch = sr.Read(); } // obrada poslednje linije if (brULiniji (brULiniji > maxBrULin) { maxBrULin = brULiniji; maxLin = tekLin; } sr.Close(); lRezultat.Text = "Brojeva ima " + + brBrojeva + "\n" ; if (maxBroj (maxBroj >= 0) lRezultat.Text += "Najveci broj u fajlu je " + + maxBroj + "\n" ; if (maxLin (maxLin > 0) lRezultat.Text += "Najvise brojeva sadrzi " + + maxLin + ". linija" ; }
4. Kreirati aplikaciju kojom se određuje zbir razlomaka zapisanih u tekstualnoj datoteci razlomci.txt. Svaki razlomak je zapisan u jednoj liniji i to tako što je prvo naveden označen ili neoznačen brojilac, zatim / ukoliko je imenilac različit od 1, pa neoznačen imenilac (primeri ispravno zapisanih razlomaka: -23/56, +567/34, 10/4, 34, -234). U objektu klase Label prikazati izraz čiju vrednost izračunavamo i izračunatu vrednost. Prilikom učitavanja podataka iz fajla, brojilac i imenilac dobijaju vrednost pa uz parametre preko kojih im pristupamo pišemo službenu reč out . Posebnom metodom citajBroj realizujemo generisanje celog broja iz tekstualne datoteke, datoteke, počev od pozicije na kojoj se nalazimo u datoteci. Kako je posle poziva metoda potrebno znati koji znak sledi iza pročitanog broja (da li je znak '/' '/'), ), parametar kojim prenosimo taj znak mora biti out . . void ProcitajRazlomak( ProcitajRazlomak( StreamReader StreamReader sr, sr, out int brojilac, brojilac, out int imenilac) imenilac) { char ch; ch; brojilac=citajBroj(sr, out ch); if (ch (ch == '/' ) imenilac = citajBroj(sr, out ch); else imenilac = 1;
20
SkratiRazlomak( ref ref brojilac, brojilac, ref imenilac); imenilac); sr.ReadLine(); // citanje do kraja linije } int citajBroj( citajBroj( StreamReader StreamReader sr, sr, out char ch) ch) { int znak = 1, broj=0; ch = ( char char )sr.Read(); )sr.Read(); if (ch (ch == '+' || || ch == '-' ) { if (ch (ch == '-' ) znak = -1; ch = ( char char )sr.Read(); )sr.Read(); } while ( ( Char Char .IsDigit(ch)) .IsDigit(ch)) { broj = 10 * broj + ch - '0' ; ch = ( char char )sr.Read(); )sr.Read(); } return znak * broj; }
U metodu SkratiRazlomak parametri brojilac i imenilac menjaju postojeću vrednost pa prilikom njihovog navođenja koristimo ref . void SkratiRazlomak( SkratiRazlomak( ref ref int brojilac, brojilac, ref int imenilac) imenilac) { int x x = NZD( Math Math .Abs(brojilac), .Abs(brojilac), imenilac); if (x (x != 0) { brojilac /= x; imenilac /= x; } } int NZD( NZD( int int a, a, int b) b) { int c; c; while (b (b != 0) { c = a % b; a = b; b = c; } return a; }
U metodu ZbirRazlomaka parametri koji predstavljaju razlomke koje xBr , xIm, yBr , yIm) su vrednosni, a uz parametre rezultata ( zBr , sabiramo ( xBr zIm) navodimo službenu reč out jer njih određujemo u metodu. metodu. void ZbirRazlomaka( ZbirRazlomaka( int int xBr, xBr, int xIm, xIm, int yBr, yBr, int yIm, yIm, out int zBr, zBr, out int zIm) zIm) { zIm = xIm * yIm; zBr = xBr * yIm + xIm * yBr; SkratiRazlomak( ref ref zBr, zBr, ref zIm); zIm); } private void btRazlomci_Click( btRazlomci_Click( object object sender, sender, EventArgs e) e) { StreamReader sr sr = new StreamReader ( ("razlomci.txt" " razlomci.txt" ); ); int zB, zB, zI, b, i;
21
ProcitajRazlomak(sr, out zB, out zI); if (zI (zI != 1) lbRez.Text = zB + "/" + + zI; else lbRez.Text = zB.ToString(); while (!sr.EndOfStream) (!sr.EndOfStream) { ProcitajRazlomak(sr, out b, out i); if (b (b < 0) lbRez.Text += b.ToString(); else lbRez.Text += "+" + + b; if (i (i != 1) lbRez.Text += "/" + + i; ZbirRazlomaka(zB, zI, b, i, out zB, out zI); } lbRez.Text += "=" + + zB; if (zI (zI != 1) lbRez.Text += "/" + + zI; sr.Close(); }
o Rekurzivni
metodi
Rekurzija Rekurzija (lat. recursio, recursion od recurrere: vraćanje) predstavlja postupak koji pri rešavanju nekog problema za zadate parametre koristi rešenje istog problema za neke druge parametre. Posmatrajmo poznati problem Hanojskih kula (Hanoi of Towers): Na stub A je nanizano N diskova diskova različitog prečnika, tako da je prvi odozdo najveći, a svaki sledeći je manji od onog ispod. Na raspolaganju su jos 2 stuba, B i C. Potrebno je premestiti sve diskove sa stuba A na stub B, pri čemu se može korisiti stub C. Dozvoljeno je u jednom koraku premestiti jedan dostupni (gornji) disk sa jednog stuba na neki drugi. Pri tome, ni u jednom trenutku, ni na jednom stubu ne sme da se nadje disk većeg prečnika iznad manjeg.
Za malo N problem problem se može, uz dosta truda i razmišljanja, rešiti iterativno. Povećanjem broja diskova postupak se komplikuje pa nam rekurzija ostaje kao jedina mogućnost za rešavanje ovog problema. Rekurzivno rešenje osnovnog problema (prebacivanje N diskova diskova sa stuba A na stub B, koristeći stub C, P(N , A, B, C), možemo opisati na sledeći način: • Ako je broj diskova N =1 =1 (osnovni slučaj) o
prebacimo disk sa stuba A na stub B čime je problem rešen
22
o
inače (rekurzivno pravilo) prebacimo N -1 -1 disk sa stuba A na stub C, koristeći stub B (rešenje problema P(N -1, -1, A, C, B))
prebacimo preostali disk sa stuba A na stub B
prebacimo N -1 -1 disk sa stuba C na stub B, koristeći stub A (rešenje problema P(N -1, -1, C, B, A))
Veliki rekurzivno. • •
broj pojmova iz svakodnevnog života možemo opisati
Na primer, pretka date osobe možemo definisati na sledeći način: Roditelji osobe A su preci osobe A (osnovni slučaj) Roditelji predaka osobe A su takođe preci osobe A (rekurzivno pravilo). o o o o
Veliki broj matematičkih pojmova definišemo rekurzivno: Faktorijel prirodnog broja N, u oznaci N! 0!=1 (osnovni slučaj) N!=N*(N-1)! (rekurzivno pravilo) 23
Nti (N je prirodan broj uključujući i 0) stepen realnog broja X, u oznaci X N 0 o X =1 (osnovni slučaj) N (N-1) o X =X*X (rekurzivno pravilo) o Aritmetičke izraze gradimo rekurzivnim postupkom o Brojna konstanta je aritmetički izraz (osnovni slučaj) o Ako su A i B aritmetički izrazi onda su i (A+B), (A-B), (A*B) i (A/B) (rekurzivno pravilo) U programskom jeziku C# rekurziju realizujemo kreiranjem rekurzivnih metoda. o
U svakom metodu M neke klase možemo pozvati svaki metod metod te klase, bez obzira na nivo pristupa (public ili private), pa i sam metod M. Metod koji u nekoj od svojih instrukcija sadrži poziv sebe samog zovemo rekurzivni metod.
U tim metodima definiše se nekoliko (jedan ili više) osnovnih slučajeva i rekurzivno pravilo kojim se rešavanje složenijih slučajeva svodi na rešavanje jednostavnijih, pozivom istog metoda za druge parametre. Pri rekurzivnom pozivu nekog metoda, kao i pri pozivu bilo kog metoda, tok aplikacije se prenosi u pozvani metod dok se on ne završi a zatim se nastavlja u izvornom metodu. Informacije o povratku u izvorni metod se pamte u stek memoriji organizovanoj po LIFO (Last in First out ) principu. To omogućava uspešnu realizaciju rekurzije jer metod koji je poslednji pozvan mora se prvi završiti da bi bio omogućen završetak prethodno pozvanih metoda, čiji je on sastavni deo. Primer 1: 1: Rešavanje prethodno razmatranog problema Hanojskih kula realizujemo sledećim metodom: void Hanoj( Hanoj( int int brojDiskova, char izvor, char cilj, char pom, Li stBox l) { if (brojDiskova>1) (brojDiskova>1) { Hanoj(brojDiskova-1, izvor, pom, cilj, l); l.Items.Add(izvor + " -> " +cilj); +cilj); Hanoj(brojDiskova-1, pom, cilj, izvor, l); } else l.Items.Add(izvor l.Items.Add(izvor + " -> " +cilj); +cilj); }
Primer 2: 2: Posmatrajmo metod kojim u objekat klase ListBox upisujemo sve prirodne brojeve do zadatog broja N , uključujući i broj N . Ukoliko želimo da redosled upisivanja brojeva u ListBox bude od 1 do N ovaj metod možemo realizovati korišćenjem sledećeg rekurzivnog razmišljanja: veći od 1 upisati u ListBox sve prirodne brojeve • Ako je broj N veći do broja N -1 -1 (uključujući i broj N -1) -1) u ListBox • Upisati broj N u 24
void pisi(int N, ListBox l) { if (N > 1) pisi(N – 1, l); l.Items.Add(N); }
Analizirajmo izvršavanje ovog metoda kada ga pozovemo za N =3, =3, pisi(3,l). Prvo proveravamo uslov, 3>1. Kako je uslov ispunjen, realizuje se poziv metoda pisi(2,l). To prouzrokuje kreiranje drugog primerka metoda sa parametrom N čija je vrednost 2, a informacije o mestu povratka u pisi sa metod pisi(3,l) (vraćanje na naredbu l.Items.Add(N);) i o trenutnoj vrednosti parametra (N=3) čuvamo na steku. U drugom primerku ponovo se izvrši prva naredba, provera uslova 2>1, zatim se poziva metod pisi , sada sa parametrom 1. To prouzrokuje kreiranje trećeg primerka metoda u kojem parametar N ima ima vrednost 1, a informacije o mestu povratka u metod pisi(2,l) i o trenutnoj vrednosti parametra čuvamo na steku. U trećem primerku prvo se proverava uslov 1>1. Kako uslov nije ispunjen ne vrši se kreiranje novog primerka metoda već se izvršava sledeća naredba trećeg primerka, upisuje se 1 u ListBox l. Na taj način je završen treći primerak pa nastavljamo izvršavanje drugog primerka na osnovu informacija uzetih sa steka. Drugi primerak završavamo izvršavanjem naredbe upisa 2 u ListBox. Po završetku drugog primerka nastavljamo izvršavanje prvog primerka na osnovu informacija uzetih sa steka. Izvršavanjem upisa 3 u ListBox l izvršena je i poslednja naredba polaznog primerka metoda pa se aplikacija nastavlja od naredbe koja sledi za pozivom metoda pisi(3,l). Ukoliko želimo da brojeve upišemo u redosledu od broja N do do broja 1 dovoljno je da pri realizaciji promenimo redosled re dosled koraka: u ListBox • Upisati broj N u veći od 1 upisati u ListBox sve prirodne brojeve • Ako je broj N veći do broja N -1 -1 (uključujući i broj N -1) -1) U C# ovakav rezon zapisujemo na sledeći način: void pisi(int N, ListBox l) { l.Items.Add(N); if (N > 1) pisi(N – 1, l); }
Primer 3: 3: Kreirajmo rekurzivni metod za izračunavanje N ! void int faktorijel(int N) { if(N==0) return 1;//osnovni slucaj return N*faktorijel(N-1);//rekurzivno pravilo }
Pri pozivu faktorijel(3), pošto je 3 različito od 0, pozivamo faktorijel(2) i na steku pamtimo da po povratku rezultat tog metoda 25
moramo pomnožiti sa 3 i proizvod vratiti kao rezultat polaznog metoda. Slično se izvršavaju i metodi faktorijel(2) i faktorijel(1). Metod faktorijel(0) vraća 1, jer je parametar jednak 0, čime je omogućen povratak u metod faktorijel(1) i njegov završetak sa povratnom vrednošću 1(1*1). Slično, završetak metoda faktorijel(1) omogućava povratak u metod faktorijel(2) i njegov završetak sa povratnom vrednošću 2(2*1). Na kraju, po završetku metoda faktorijel(2) vraćamo se u metod faktorijel(3) koji se završava sa povratnom vrednošću 6(3*2).
Iako je rekurzivni način razmišljanja dosta blizak čovekovom načinu razmišljanja, zbog načina realizacije rekurzivna rešenja su često neefikasna, spora i memorijski zahtevna. Zato moramo biti obazrivi pri izboru načina rešavanja problema. U primerima koji slede slede često smo smo koristili rekurziju, i pored efikasnijih iterativnih rešenja, samo da bi ilustrovali rekurzivan način rešavanja.
Primeri rekurzivnih metoda
1. Napisati rekurzivan metod kojim se određuje a) suma prvih n prirodnih brojeva b) n -ti -ti stepen broja a •
Određivanje sume prvih n prirodnih brojeva možemo realizovati korišćenjem sledećeg rekurzivnog razmišljanja: o o
Ako je broj n jednak 0 suma je 0 Inače, rekurzivno određujemo sumu prvih brojeva i na taj rezultat dodajemo broj n.
n-1 prirodnih
int suma( suma( int int n) n) { if (n (n == 0) return 0; 0; return n n + suma(n - 1); }
26
Slično, pri određivanju n-tog (n>0) stepena broja a, možemo rekurzivno odrediti (n-1)–vi stepen broja a i dobijeni rezultat pomnožiti brojem a. Za n=0 vrednost stepena bilo kog broja je 1. float stepen( stepen( float float a, a, int n) n) { if (n (n == 0) return 1; 1; return a a * stepen(a, n - 1); }
2. Napisati rekurzivni metod koji određuje a) sumu cifara prirodnog broja n b) k -tu -tu cifru gledano s desna u levo u prirodnom broju n c) broj dobijen izbacivanjem iz prirodnog broja n svakog pojavljivanja cifre c d) broj dobijen tako što se u prirodnom broju n posle svake neparne cifre (desno od nje) doda 0 e) zbir parnih i zbir neparnih cifara u prirodnom broju n Uočimo da su problemi opisanu u ovom zadatku problemi koji zahtevaju analizu prirodnog broja cifru po cifru. Svaki prirodan broj n možemo zapisati u obliku (n/10)*10+n%10. U skladu sa tim, rešavanju problema koji analiziraju cifre prirodnog broja možemo pristupiti rekurzivno koristići sledeći rezon: •
Ako je broj jednocifren (ili 0) rešavamo problem koji je najčešće trivijalan.
•
Inače, rekurzivno pozivamo rešavanje problema za n/10 i koristeći to rešenje i poslednju cifru (n%10) dolazimo do rešenja našeg problema.
int sumaCifara( sumaCifara( int int n) n) { if (n (n < 10) return n; n; return n n % 10 + sumaCifara(n / 10); } int KCifra( KCifra( int int n, n, int k) k) { if (n (n == 10) return 0; 0; if (k (k == 1) return n n % 10; return KCifra(n KCifra(n / 10, k - 1); } nt izbaciC( izbaciC( int int n, n, int c) c) { if (n (n == 0) return 0; 0; if (n (n % 10 == c) return izbaciC(n izbaciC(n / 10, c); return izbaciC(n izbaciC(n / 10, c) * 10 + n % 10; } int posleNeparne0( posleNeparne0( int int n) n) { if (n (n == 0) return 0; 0; if (n (n % 2 == 1) return posleNeparne0(n posleNeparne0(n / 10) * 100 + (n % 10) * 10; return posleNeparne0(n posleNeparne0(n / 10) * 10 + (n % 10); } void zbirParnihNeparnih( zbirParnihNeparnih( int int n, n, out out int int sp, sp, out out int int sn) sn)
27
{ if (n (n == 0) sn = sp = 0; else { zbirParnihNeparnih(n / 10, out sp, out sn); if (n (n % 2 == 0) sp += n % 10; else sn sn += n % 10; } }
3. Kreirati aplikaciju kojom se od tekstualne datoteke čije ime unosimo korišćenjem objekta klase TextBox formira nova datoteka u kojoj su sve linije polazne datoteke ispisane u inverznom poretku. Novu datoteka kreiramo tako što za svaku liniju polazne pozivamo metod okreniLiniju koji u rezultujuću datoteku upisuje sadržaj linije u inverznom poretku. private void btOkreniLinije_Click( btOkreniLinije_Click( object object sender, sender, EventArgs e) e) { StreamReader sr sr = new StreamReader (textBox1.Text (textBox1.Text + ".txt" ); ); StreamWriter sw sw = new StreamWriter ( ( "nova" "nova" + + textBox1.Text + ".txt" ); ); while (!sr.EndOfStream) (!sr.EndOfStream) { okreniLiniju(sr, sw); sw.WriteLine(); sr.ReadLine(); } sr.Close(); sw.Close(); }
Da bi liniju ispisali u inverznom poretku možemo koristiti sledeće rekurzivno razmišljanje: • Ako je kraj datoteke prekidamo izvršavanje metoda •
Inače, čitamo jedan znak iz polazne datoteke o o
Ako je kraj linije prekidamo izvršavanje metoda Inače, pozivamo okretanje ostalog dela linije, a zatim pročitani znak upisujemo u rezultujuću datoteku.
4. Napisati rekurzivni metod kojim se određuje zapis prirodnog broja n u u a) binarnom b) heksadekadnom pozicionom brojevnom sistemu. Pri prevođenju prirodnog broja u pozicioni brojevni sistem osnove K , broj delimo osnovom dok ne dobijemo 0, pri čemu od ostataka deljenja 28
formiramo zapis broja u traženom sistemu tako što ih navodimo u obrnutom redosledu od redosleda određivanja. Na primer, broj 37 prevodimo u sistem sa osnovom 4 na sledeći način: 37:4=9 uz ostatak 1 9:4=2 uz ostatak 1 2:4=0 uz ostatak 2 Ostaci 1, 1 i 2 čine zapis broja 37 u sistemu sa osnovom 4, 211. a)
string binarni( int int n) n) { if (n (n > 0) return binarni(n / 2) + (n % 2); return �� ��� � } b)
string heksa( int int n) n) { if (n (n > 0) return heksa(n / 16) + cifra(n % 16); ��� � return �� }
Cifre sistema osnove osnove veće od 10 su i slovne (A, B, C, D, E, F, F, ...), pa svakom od mogućih ostataka moramo dodeliti odgovarajući znak kako bismo kreirali traženi zapis prirodnog broja. Za određivanje znaka koji odgovara cifri koristimo metod char cifra(int c). Znaci u kodnim rasporedima su u određenom redosledu. Znaci koji odgovaraju dekadnim ciframa ciframa slede jedni za drugim pa ih određujemo tako tako što brojnu vrednost cifre dodajemo na znak '0' (implicitno se konvertuje u ceo broj, kod znaka '0') pa dobijenu vrednost (kod tražene cifre) konvertujemo u znakovni tip: ( char char )( )( '0' '0' + + 0)= '0' '0' ( char char )( )( '0' '0' + + 1)= '1' '1' ... ( char char )( )( '0' '0' + + 9)= '9' '9' .
Slično, velika slova abecede u kodnom rasporedu slede jedna za drugim. U heksadekadnom brojevnom sistemu cifre 'A', 'B', 'C', 'D', 'E' i 'F' imaju redom vrednosti 10, 11, 12, 13, 14 i 15. Da bi od vrednosti cifre (c , c >9) >9) dobili odgovarajući znak, potrebno je da odredimo za koliko se ta cifra razlikuje od 10 (c -10), -10), pa da dobijenu vrednost dodamo na znak 'A' ('A' 'A'+c-10 +c-10) ) čime dobijamo kod tražene cifre: ( char char )( )( 'A' 'A' + + 10-10)= 'A' 'A' ( char char )( )( 'A' 'A' + + 11-10)= 'B' 'B' ... ( char char )( )( 'A' 'A' + + 15-10)= 'F' 'F' . char cifra( cifra( int int c) c) { if (c (c < 10) return ( char char )( )( '0' '0' + + c); return ( char char )( )( 'A' 'A' + + c-10);
29
}
5. Neka su a 1, a2, ..., a p redom sleva nadesno cifre prirodnog broja n (n=a 1a2 ... ap-1ap). Napisati rekurzivni metod kojim se određuje a) suma p·a1+(p-1)·a2+...+1·ap (8167 4·8+3·1+2·6+1·7) b) suma 1·a1+2·a2+3·a3+...+p·ap (8167 1·8+2·1+3·6+4·7) U prvom slučaju potrebno je cifru jedinicu pomnožiti sa 1, cifru desetica sa 2 i dalje redom. Kako cifre izdvajamo upravo tim redosledom dovoljno je uvesti vrednosni parametar k sa kojim množimo odgovarajuću cifru. Na početku k ima ima vrednost 1 jer ga množimo sa cifrom jedinica a pri svakom rekurzivnom pozivu k uvećavamo uvećavamo za 1. int sumaA( sumaA( int int n, n, int k) k) { if (n (n > 0) return sumaA(n / 10, k + 1) + k * (n % 10); return 0; }
U drugom slučaju, cifru najveće težine broja n treba pomnožiti sa 1 a ostale cifre redom sa 2, 3, ... tako da cifru jedinica množimo sa brojem cifara broja n. Kako cifre izdvajamo obrnutim redosledom potrebno je odrediti vrednost sa kojom množimo odgovarajuću cifru. Zato uvodimo out parametar k koji koji predstavlja broj cifara parametra n. Po izvršenju rekurzivnog poziva sumaB(n / 10, out k) parametar k predstavlja broj cifara u broju n / 10 pa dodavanjem jedinice na k dobijamo vrednost sa kojom treba da pomnožimo cifru jedinica broja n. int sumaB( sumaB( int int n, n, out int k) k) { if (n (n > 0) { int x1 x1 = sumaB(n / 10, out k); k++; return x1 x1 + k * (n % 10); } k = 0; return 0; 0; } private void tbN_TextChanged(object sender, EventArgs e) { rbSumaA.Checked = rbSumaB.Checked = false; lbRezultat.Text = ""; } private void rbSumaA_CheckedChanged(object sender, EventArgs e) { int n = Convert.ToInt32(tbN.Text); if (rbSumaA.Checked) lbRezultat.Text = sumaA(n, 1).ToString(); else if(rbSumaB.Checked) { int k; lbRezultat.Text = sumaB(n, sumaB(n, out k).ToString(); } }
30
6. Napisati rekurzivni metod za određivanje najvećeg zajedničkog delioca za dva data prirodna broja, a zatim koristeći taj metod kreirati aplikaciju kojom se određuje nzd brojeva iz fajla brojevi.txt
NZD se korišćenjem Euklidovog algoritma određuje rekurzivno na sledeći način:
int nzd( nzd( int int a, a, int b) b) { if (b (b == 0) return a; return nzd(b, a % b); } private void btOdrediNzd_Click( btOdrediNzd_Click( object object sender, sender, EventArgs e) e) { StreamReader sr sr = new StreamReader ( ("brojevi.txt" " brojevi.txt" ); ); int x, x, n; x = 0; while (!sr.EndOfStream) (!sr.EndOfStream) { n = Convert .ToInt32(sr.ReadLine()); .ToInt32(sr.ReadLine()); x = nzd(n, x); } lbRezultat.Text = x.ToString(); sr.Close(); }
7. Napisati rekurzivni metod za određivanje n -tog -tog člana Fibonačijevog niza. Fibonačijev niz je definisan definisan na sledeći način:
long f (int n) { if (n < 3) return 1; return f(n - 1) + f(n - 2); }
31
Iako je definicija Fibonačijevog niza rekurzivna, rešavanje ovog problema rekurzijom je posebno neefikasno zbog ponavljanja rešavanja istog problema (preklapanja podproblema). Primetimo da pri određivanju vrednosti 5-tog člana Fibonačijevog niza metod f(4) pozivamo jednom, metod f(3) pozivamo dva puta, a metod f(2) čak 3 puta. Efikasnije, iterativno rešenje istog problema navedeno je u primerima aplikacija ciklične strukture. 8. Generisati sve bezbedne lance sastavljene od N molekula molekula plutonijuma(Pu) i olova(Pb). Lanac je bezbedan kada se u njemu ne pojavljuju dva molekula plutonijuma jedan do drugog.
Prilikom generisanja bezbednog lanca dužine N koristimo koristimo sledeći rekurzivni rezon: Bezbedni lanac dužine N ( (N >0) >0) •
počinje molekulom plutonijuma iza koga sledi bilo koji bezbedni lanac dužine N -1 -1 koji počinje molekulom olova
•
počinje molekulom olova iza koga sledi bilo koji bezbedni lanac dužine N -1 -1 32
U rekurzivnom metodu Lanci(string molekul, int duzina, string lanac, ListBox l)
bezbedni lanac pamtimo u parametru lanac koji koji je na početku prazan string i u koji dodajemo jedan po jedan molekul dok ne dodamo svih N molekula. molekula. U parametru duzina pamtimo koliko još molekula treba da dodamo u lanac. Vrednost molekula koji možemo dodati u lanac zavisi od poslednjeg molekula koji je u lancu, čiju vredenost pamtimo u parametru molekul . U lanac možemo uvek dodati molekul olova (Pb), a molekul plutonijuma (Pu) možemo dodati samo ako je prethodni molekul bio olovo (molekul == "Pb" ). ). void Lanci( Lanci( string string molekul, molekul, int duzina, duzina, string lanac, lanac, ListBox l) l) { if (duzina (duzina == 0) l.Items.Add(lanac); else { Lanci( "Pb" "Pb" , duzina - 1, lanac+ "Pb" "Pb" , l); if (molekul (molekul == "Pb" ) { Lanci( "Pu" "Pu" , duzina - 1, lanac + "Pu" , l); } } }
Metod Lanci prvi prvi put pozivamo sa vrednošću parametra molekul "Pb" "Pb" jer na taj način obuhvatamo lance dužine N koji koji počinju i sa "Pb" i sa "Pu". private void nudDuzina_ValueChanged( nudDuzina_ValueChanged( object object sender, sender, EventArgs e) e) { int n n = ( int int )nudDuzina.Value; )nudDuzina.Value; lbLanci.Items.Clear(); Lanci( "Pb" "Pb" , n, "" , lbLanci); }
9. Generisati sve prirodne brojeve sastavljene od neparnih cifara čija je suma cifara jednaka zadatom broju.
private void nudSumaCifara_ValueChanged( nudSumaCifara_ValueChanged( object object sender, sender, EventArgs e) e) { int n n = ( int int )nudSumaCifara.Value; )nudSumaCifara.Value; listBox1.Items.Clear(); GenerisiBroj(n, "" , listBox1); }
33
private void GenerisiBroj( GenerisiBroj( int int suma, suma, string broj, broj, ListBox l) l) { if (suma (suma >= 0) { if (suma (suma == 0) l.Items.Add(broj); else { for ( ( int int cifra cifra = 1; cifra < 10; cifra += 2) GenerisiBroj(suma - cifra, broj + cifra, l); } } }
10. Kreirati aplikaciju koja crta crtež prikazan prik azan na slici.
Primetimo da su delovi ovog crteža konstruisani na isti način kao i osnovni crtež. Takve crteže koje možemo razložiti na delove koji su njegove umanjene kopije nazivamo fraktalima. Fraktal prikazan na slici je poznati fraktal, trougao Sjerpinskog. Uočimo središta svake od stranica polaznog trougla. Spajanjem tih središta trougao je podeljen na četiri podudarna trougla. Svi ti trouglovi, osim centralnog, su umanjene kopije celog crteza, tako da svaki od njih crtamo na isti način kao polazni crtež. Dok je stranica trougla veća od neke unapred određene veličine ( u rešenju koje sledi ta veličina je 5 piksela) crtež razbijamo na manje crteže. Tek kada je stranica trougla manja od zadate veličine taj trougao iscrtavamo. Iscrtavanjem malih trouglova gradimo veće trouglove sve do polaznog trougla. private void Form1_Load( Form1_Load( object object sender, sender, EventArgs e) e) { Width = 400; Height = ( int int )(400 )(400 * Math .Sqrt(3) .Sqrt(3) / 2); } private void Form1_Paint( Form1_Paint( object object sender, sender, PaintEventArgs e) e) { Point A A = new Point (ClientRectangle.Width (ClientRectangle.Width / 2, 0); Point B B = new Point (0, (0, ClientRectangle.Height); Point C C = new Point (ClientRectangle.Width, (ClientRectangle.Width, ClientRectangle.Height); CrtajTrouglove(e.Graphics, A, B, C); } private void CrtajTrouglove( CrtajTrouglove( Graphics Graphics g, g, Point A, A, Point B, B, Point C) C) {
34
if ((B.X ((B.X - C.X) * (B.X - C.X) + (B.Y - C.Y) * (B.Y - C.Y) >25) { Point A1 A1 = new Point ((A.X ((A.X + B.X) / 2, (A.Y + B.Y) / 2); Point B1 B1 = new Point ((B.X ((B.X + C.X) / 2, (B.Y + C.Y) C .Y) / 2); Point C1 C1 = new Point ((C.X ((C.X + A.X) / 2, (C.Y + A.Y) / 2); CrtajTrouglove(g, A, A1, C1); CrtajTrouglove(g, A1, B, B1); CrtajTrouglove(g, C1, B1, C); } else { trougao(g, A, B, C); } } void trougao( trougao( Graphics Graphics g, g, Point A, A, Point B, B, Point C) C) { g.DrawLine( Pens Pens .Black, .Black, A, B); g.DrawLine( Pens Pens .Black, .Black, B, C); g.DrawLine( Pens Pens .Black, .Black, C, A); }
11. Kreirati aplikaciju koja crta crtež prikazan prik azan na slici.
Random r = new Random(); void CrtajPravougaonike(Graphics g, Point A, Point B) { int w=B.X - A.X; int h=B.Y - A.Y; if (( w> 5) && (h > 5)) { Point C = new Point(A.X + w / 2, A.Y + h / 2); Color boja= Color.FromArgb(r.Next(255),r.Next(255),r.Next(255)); SolidBrush cetka = new SolidBrush(boja); Sol idBrush(boja); g.FillRectangle(cetka, A.X, A.Y, w / 2, h/2); cetka.Color = Color.FromArgb (r.Next(255), r.Next(255), r.Next(255)); g.FillRectangle(cetka, C.X, C.Y, w / 2, h/2); CrtajPravougaonike(g, A, C); CrtajPravougaonike(g, C, B); }
35
} private void Form1_Paint(object sender, PaintEventArgs e) { Point A = new Point(0, 0); Point B = new Point(ClientRectangle.Width, ClientRectangle.Height); Graphics g=CreateGraphics(); CrtajPravougaonike(g, A, B); }
12. Dati prirodan broj n ispiši u faktorijelnom sistemu n=(AmAm-1 ... A2A1)f = Am∙m!+Am-1∙(m-1)!+...A 2∙2!+A1∙1! pri čemu su Ai cifre od 0 do 9. 59=(2121)f =2∙4!+1∙3!+2∙2!+1∙1! =2∙4!+1∙3!+2∙2!+1∙1! private void button1_Click( button1_Click( object object sender, sender, EventArgs e) e) { int n n = Convert .ToInt32(textBox1.Text); .ToInt32(textBox1.Text); lRezultat.Text = "" ; faktorijelniZapis(n,2); } private void textBox1_TextChanged( textBox1_TextChanged( object object sender, sender, EventArgs e) e) { lRezultat.Text = "" ; } void faktorijelniZapis( faktorijelniZapis( int int n, n, int k) k) { if (n (n > 0) { faktorijelniZapis(n / k, k + 1); lRezultat.Text += (n % k).ToString(); } }
13. U svakoj liniji tekstualne datoteke formule.txt nalaze se ispravno zapisane formule oblika koji je opisan Bekusovom notacijom 1: ::=|() ::=0|1|...|9 ::=+|-|* Kreirati aplikaciju kojom se za svaku formulu u objektu klase ListBox prikaže njena vrednost. Na primer, vrednost formule 3 je 3, a vrednost formule ((2+1)*(4-2)) je 6. Prilikom određivanja vrednosti formule, formulu čitamo znak po znak iz datoteke. Definicija formule je rekurzivna što možemo iskoristiti prilikom pisanja rekurzivne metode: • Ako je pročitani znak cifra vrednost formule je vrednost pročitane cifre. •
Inače, formula je složena (upravo smo pročitali otvorenu zagradu) i sastoji se se od dve formule između kojih je navedena navedena operacija. U skladu sa tim pozovemo rekurzivno izračunavanje vrednosti prve formule (a), pročitamo operaciju ( op), pozovemo rekurzivno izračunavanje vrednosti druge formule (b), pročitamo zatvorenu zagradu i vvratimo ratimo rezultat (a op b).
1
Pogledajte Dodatak 36
int vrednost( vrednost( StreamReader StreamReader sr) sr) { char ch ch = ( char char )sr.Read(); )sr.Read(); if (ch (ch >= '0' && && ch <= '9' ) return ( int int )ch )ch - ( int int ) )'0' ' 0' ; int a,b; a,b; char op; op; a= vrednost(sr); // odredjivanje vrednosti prve formule op = ( char char )sr.Read(); )sr.Read(); // čitanje operacije b = vrednost(sr); // odredjivanje vrednosti druge formule ch = ( char char )sr.Read(); // )sr.Read(); // čitanje zatvorene zagrade if (op (op == '+' ) return a + b; if (op (op == '-' ) return a - b; return a * b; } private void btFormule_Click( btFormule_Click( object object sender, sender, EventArgs e) e) { StreamReader sr sr = new StreamReader (tb.Text (tb.Text + ".txt" ); ); lb.Items.Clear(); int x; x; while (!sr.EndOfStream) (!sr.EndOfStream) { x = vrednost(sr); lb.Items.Add(x); sr.ReadLine(); } sr.Close(); }
14. U svakoj liniji tekstualne datoteke formule.txt nalaze se ispravno zapisane formule oblika koji je opisan Bekusovom notacijom: ::=|M(,)| m(,) ::={} ::=0|1|...|9 Pri tome, M predstavlja maksimum a m minimum dve vrednosti. Svaka formula je u novom redu. Kreirati aplikaciju kojom se određuju njihove vrednosti. Na primer: Ulaz 3 Izlaz 3 Ulaz M(4, m(9,7)) Izlaz 7 Ulaz m(m(6,8), M(2,m(5,7))) Izlaz 5 int max(int a, int b) { if (a > b) return a; else return b; } int min(int a, int b) {
37
if (a < b) return a; else return b; } bool cifra(char c) { return c >= '0' && c <= '9'; } int vrednost(StreamReader sr) { char ch = (char)sr.Read(); if (ch >= '0' && ch <= '9') { int a=(int)ch-(int)'0'; while(cifra((char)sr.Peek())) { ch=(char)sr.Read(); a=a*10+(int)ch-(int)'0'; } return a; } char op = ch; ch = (char)sr.Read();// citanje otvorene zagrade int a, b; a = vrednost(sr); ch = (char)sr.Read();// citanje zareza b = vrednost(sr); ch = (char)sr.Read();// citanje zatvorene zagrade if (op == 'M') return max(a, b); else return min(a, b); } private void btFormule(object sender, EventArgs { StreamReader sr = new StreamReader("formule.txt"); int x; string s; while (!sr.EndOfStream) { x = vrednost(sr); lb.Items.Add(x); s = sr.ReadLine(); } sr.Close(); }
38
•
Nizovi
Često smo u prilici da pri programskoj implementaciji određenih realnih procesa radimo sa većom količinom podataka istog tipa, kojima opisujemo stanje objekata koji učestvuju u procesu. U procesu ocenjivanja učenika svaki učenik ima ocenu iz svakog predmeta. Ako bismo uvodili posebnu promenljivu za svaku ocenu program bi bio glomazan a i nedovoljno prilagodljiv različitim situacijama ( broj predmeta zavisi od razreda pa ne znamo unapred broj ocena).
Kako se sa svim tim podacima izvode iste ili slične akcije i kako vrlo često unapred ne znamo njihov broj, za realizaciju je neophodno uvođenje složenijih tipova koji objedinjuju sve te podatke. U nekim procesima redosled pojavljivanja ili redosled obrade tih podataka je bitan pa je potrebno da ovakav složeni tip sadrži u sebi i informaciju o poziciji svakog pojedinačnog podatka u njemu. U zavisnosti od procesa koji opisujemo, te podatke možemo rasporediti jedan za drugim u istoj vrsti, u tabeli (u više vrsta), u više tabela ... Ocene jednog učenika predstavljaju podatke raspoređene u jednoj vrsti, ocene svih učenika jednog odeljenja mogu se rasporediti u tabeli, a ocene svih učenika jedne škole u više tabela.
U gotovo svim višim programskim jezicima potreba za ovakvim tipom podataka je rešena uvođenjem nizova, odnosno promenljivih nizovnog tipa podataka. Nizovi po rasporedu podataka mogu biti jednodimenzioni (raspoređeni u jednoj vrsti), dvodimenzioni (raspoređeni u tabeli) itd.. Niz sadrži informacije istog tipa, raspoređene raspor eđene u poznatom redosledu. Pojedinačnim elementima niza se može pristupiti proizvoljno, navođenjem imena niza i pozicije elementa u svakoj od dimenzija niza. Poziciju elementa u svakoj od dimenzija niza zovemo indeksom elementa. Ako posmatramo niz ocena nekog učenika ocena iz trećeg predmeta nalazi se na trećoj poziciji u nizu. Kada posmatramo tabelu ocena jednog odeljanja, ocena petog učenika iz trećeg predmeta nalazi se u petoj vrsti tabele, na trećoj poziciji.
U programskom jeziku C# možemo kreirati nizove čiji su elementi proizvoljnog tipa. Niz deklarišemo navođenjem tipa elemenata, za kojim sledi par srednjih zagrada ([]) a zatim, posle razmaka, ime niza. Ukoliko je niz višedimenzioni između zagrada se navode zarezi čiji je broj za jedan manji od dimenzije niza. int[] oceneUcenika1, oceneUcenika2; // oceneUcenika1, oceneUcenika2 oceneUcenika2 su promenljive deklarisane kao // jednodimenzioni nizovi celih brojeva int[,] oceneOdeljenja1, oceneOdeljenja2; // oceneOdeljenja1, oceneOdeljenja2 su promenljive deklarisane deklarisane kao // dvodimenzioni nizovi celih brojeva int[,,] oceneSkole;
39
// oceneSkole je promenljiva deklarisana kao kao trodimenzioni niz celih // brojeva string[] imenaProfesora; Pen[] olovke; Button[,] tabla;
Bez obzira da li je tip elemenata niza vrednosni ili referentni, niz je referentni tip podataka pa se pri deklaraciji ne rezerviše prostor za elemente niza. Prostor za registrovanje elemenata niza kreira se u toku izvršavanja aplikacije u trenutku definisanja niza. Pri definisanju niza, kao i pri definisanju ostalih promenljivih referentnog tipa, koristi se operator new . Jednodimenzione nizove definišemo na sledeći način: imeNiza=new tipElemenata[kapacitetNiza]
U prethodnoj definiciji o imeNiza je identifikator programskog jezika C#, o tipElemenata je proizvoljan tip podataka, sistemski ili prethodno definisan, kojim opisujemo elemente niza, o kapacitetNiza je celobrojni izraz koji u trenutku definisanja niza mora imati vrednost veću od 0, kojim opisujemo maksimalan broj elemenata niza. oceneUcenika1=new int[10]; oceneUcenika2=new int[n];
Kao i kod ostalih tipova podataka u C# deklaracija i definicija niza često su objedinjene. int[] oceneUcenika1=new int[10]; Pen[] olovke=new Pen[10];
Kako je int vrednosni tip podataka pri definiciji niza oceneUcenika1 odvaja se prostor za registrovanje 10 celih brojeva. Pri definiciji niza olovke odvaja se prostor za registrovanje 10 referenci (adresa) na objekte klase Pen ali ti objekti nisu kreirani, već svi elementi niza imaju vrednost null .
Prilikom definisanja niza, elemente niza možemo inicijalizovati navođenjem vrednosti razdvojenih zarezom unutar vitičastih zagrada posle iskaza new kojim je niz definisan. Broj vrednosti navedenih u vitičastim zagradama mora se poklapati sa kapacitetom niza. Na primer niz x celih brojeva inicijalizuje se na sledeći način: x = new int[5] { 12, -3, 46, 634, 7 };
Možemo izostaviti izostaviti navođenje kapaciteta niza ako inicijalizujemo inicijalizujemo elemente elemente niza (u tom slučaju prevodilac veličinu niza određuje na osnovu broja navedenih vrednosti). Prethodnu definiciju niza i inicijalaizaciju elemenata niza x možemo možemo zapisati i ovako: x = new int[] { 12, -3, 46, 634, 7 };
Elemente niza možemo inicijalizovati i prilikom deklaracije niza navođenjem vrednosti odvojenih zarezom unutar vitičastih vi tičastih zagrada. int[] y = { 12, -3, 46, 634, 7 };
40
Svakom pojedinačnom elementu niza imeNiza se može pristupiti direktno na sledeći način: imeNiza[indeks]
gde je indeks celobrojni izraz kojim je određena pozicija elementa u nizu. Najmanja dozvoljena vrednost izraza indeks je 0, a najveća kapacitet niza umanjen umanjen za 1. U nekim situacijama situacijama nisu nisu iskorišćeni svi elementi niza pa je neophodno koristiti promenljivu koja ukazuje na broj efektivno popunjenih elemenata niza čija vrednost mora biti imeđu 0 i kapaciteta niza. Taj broj efektivno popunjenih elemenata niza kratko zovemo broj elemenata niza ili dužina niza. U radu sa nizovima možemo uočiti tri različita zadatka: e lementima o formiranje niza, dodeljivanje vrednosti elementima Nakon definisanja niza, niz treba popuniti odgovarajućim elementima: elementima: Tip []niz=new Tip[n]; for(int i=0;i
gde je DodeliVrednost(int i) metoda koja vraća vrednost tipa Tip u zavisnosti od indeksa i . Možemo formirati niz x od n slučajnih celih brojeva iz intervala [a,b]na sledeći način: Random R R = new Random (); (); x = int [n]; [n]; for ( ( int int i i = 0; i < n; i++) x[i] = R.Next(a, b+1);
Slično, niz x od od n olovaka slučajne boje i slučajne debljine možemo formirati na sledeći način: x = new Pen [n]; [n]; Random R R = new Random (); (); for ( ( int int i i = 0; i < n; i++) { Color boja=C boja=C olor olor .FromArgb .FromArgb (R.Next(256),R.Next(256),R.Next(256)); x[i] = new Pen (boja,R.Next(1,11)); (boja,R.Next(1,11)); }
Primetimo da kada su elementi niza referentnog tipa, svaki od elementa moramo kreirati upotrebom operatora new ili mu moramo dodeliti već postojeći objekat. • analiza
elemenata niza i određivanje karakteristika niza
Pri analizi sadržaja niza ili pri obradi podataka sačuvanih u nizu pristupamo pojedinačnim elementima niza, promenom indeksa, najčešće sekvencijalno od elementa sa indeksom 0 do elementa sa indeksom za 1 manjim od dužine niza. for(int i=0;i
Sledeći segment koda određuje sumu elemenata niza x od n celih brojeva:
41
suma = 0; for ( ( int int i i = 0; i < n; i++) { suma += x[i]; }
• transformacija
niza, uklanjanje ili umetanje elementa, promena redosleda ili vrednosti elementima.
Napomenimo da prilikom umetanja i uklanjanja elementa iz niza ne možemo direktno, bez alociranja novog prostora za niz, menjati kapacitet niza već samo menjamo broj efektivno popunjenih popunjeni h elemenata niza. Pri rešavanju problema u kojima koristimo nizove javlja se potreba za promenom sadržaja niza, bilo da je u pitanju uklanjanje elemenata ili njihova promena. Recimo da poslednji element niza x od od n elemenata treba k prebaciti na poziciju u nizu ali tako da se redosled ostalih elemenata sačuva. pom=x[n-1]; // cuvanje vrednosti poslednjeg elementa for ( ( int int i i = n-1; i>k ; i--) // pomeranje elemenata od //pretposlednjeg do k-tog za jedno mesto udesno { x[i]= x[i-1]; } x[k] = pom; //upis sačuvanog elementa na poziciju k
Vrlo često navedeni zadaci nisu jasno razgraničeni već se u realizaciji određenih problema prepliću. Prilikom formiranja niza, pri dodavanju novog elementa, možemo analizirati već postojeće elemente i na osnovu rezultata analize transformisati postojeći sadržaj niza radi umetanja novog člana. Primere koji slede ćemo ipak razvrstati po navedenim zadacima radi sistematičnog izlaganja. o Primeri
formiranja nizova 1. Napisati metode kojima se a) formira niz celih brojeva i) čitanjem iz tekstualnog fajla datog imena u kome se u prvom redu nalazi broj elementa niza a zatim u svakom sledećem po jedan element niza ii) generisanjem slučajnih brojeva iz datog intervala [a,b]. Dužinu niza takođe generisati na slučajan način način iz intervala [10,100]. b) prikazuje niz celih brojeva u objektu klase ListBox. a)
i) Pre čitanja pojedinačnih elemenata niza potrebno je rezervisati prostor za njihovo registrovanje, pa ćemo prvo iz fajla pročitati broj elemenata niza ( n) i rezervisati potreban prostor za elemente niza. Zatim, redom čitamo element po element niza. void citaj( citaj( string string s, s, out int [] [] a, out int n) n) { StreamReader sr sr = new StreamReader (s); (s);
42
n = Convert .ToInt32(sr.ReadLine()); .ToInt32(sr.ReadLine()); a = new int [n]; [n]; for ( ( int int i i = 0; i < n; i++) a[i] = Convert .ToInt32(sr.ReadLine()); .ToInt32(sr.ReadLine()); sr.Close(); } ii)
Kao i u prethodnom primeru prvo ćemo generisati broj elemenata niza ( n), zatim rezervisati prostor prostor za elemente niza i redom generisati elemente niza. void generisi( generisi( int int a, a, int b, b, Random R, R, out int n, n, out int [] [] x) { n = R.Next(10, 101); x = new int [n]; [n]; for ( ( int int i i = 0; i < n; i++) x[i] = R.Next(a, b+1); }
b) void prikaz( prikaz( int int [] [] a, int n, n, ListBox lb) lb) { for ( ( int int i i = 0; i < n; i++) lb.Items.Add(a[i].ToString()); }
2. Napisati metod kojim se a) formira niz krugova čitanjem iz tekstualnog fajla; u prvom redu fajla nalazi se broj elemenata niza a zatim za svaki krug sledeći podaci u tri linije: u prvoj, centar kruga (u formatu (x,y)), u drugoj, poluprečnik a u trećoj boja (tri cela broja odvojena blanko simbolom). b) formira niz krugova generisanjem na slučajan način, ako su date donje i gornje granice za svaku od koordinata kao i za poluprečnik; broj elemenata niza generisati iz intervala [3,100]. c) prikazuje niz x od n krugova krugova u objektu klase Graphics. Kako je krug složeni podatak koji opisujemo sa više informacija (koordinate centra, poluprečnik, boja), definisaćemo strukturu Krug kojom ćemo objediniti sve te informacije. Bez strukture, niz krugova bi morali registrovati kao više različitih nizova (niz X koordinata, niz Y koordinata, niz poluprečnika, niz boja). Struktura Krug ima sledeće atribute: koordinate centra, poluprečnik, boju i atribut kojim definišemo da li krug prikazujemo kružnom linijom ili popunjen. Poslednji atribut nije potreban za rešenje ovog zadatka ali će biti koristan u nekim sledećim zadacima. struct Krug { public int x,y, x,y, r; public Color boja; boja; public bool popunjen; popunjen; }
a)
Krug citajKrug( citajKrug( StreamReader StreamReader sr) sr) { string s1; s1; int p,r,g,b; p,r,g,b; Krug k; k; //(x,y)
43
s1=sr.ReadLine(); p=s1.IndexOf( ',' ',' ); ); k.x= Convert Convert .ToInt32(s1.Substring(1,p-1)); .ToInt32(s1.Substring(1,p-1)); k.y = Convert .ToInt32(s1.Substring(p .ToInt32(s1.Substring(p + 1, s1.Length - p - 2)); //r k.r = Convert .ToInt32(sr.ReadLine()); .ToInt32(sr.ReadLine()); // red green blue s1 = sr.ReadLine(); p = s1.IndexOf( ' ' ); ); r = Convert .ToInt32(s1.Substring(0, .ToInt32(s1.Substring(0, p)); s1 = s1.Remove(0, p + 1); p = s1.IndexOf( ' ' ); ); g= Convert .ToInt32(s1.Substring(0, .ToInt32(s1.Substring(0, p)); b = Convert .ToInt32(s1.Substring(p+1)); .ToInt32(s1.Substring(p+1)); k.boja = Color .FromArgb(r, .FromArgb(r, g, b); k.popunjen = false ; return k; } void citaj( citaj( string string s, s, out Krug [] [] k, out int n) n) { StreamReader sr sr = new StreamReader (s); (s); n = Convert .ToInt32(sr.ReadLine()); .ToInt32(sr.ReadLine()); k = new Krug [n]; [n]; for ( ( int int i i = 0; i < n; i++) { k[i] = citajKrug(sr); } sr.Close(); } b)
Prilikom generisanja kruga prvo na slučajan način generišemo poluprečnik kruga r iz iz intervala [dR, gR], a zatim centar kruga. Da bi ceo krug pripadao datim granicama po osama, x koordinatu koordinatu biramo iz intervala [ r, gX – r] a y koordinatu iz intervala [ r, gY – r]. Boju kruga generišemo gene rišemo na slučajan način. Krug generisiKrug( generisiKrug( Random Random R, R, int gX, gX, int gY, gY, int dR, dR, int gR) gR) { Krug k; k; k.r = R.Next(dR, gR+1); k.x = R.Next(k.r, gX - k.r+1); k.y = R.Next(k.r, gY - k.r+1); k.boja = Color .FromArgb(R.Next(256), .FromArgb(R.Next(256), R.Next(256), R.Next(256)); k.popunjen = false ; return k; } void generisi( generisi( Random Random R, R, int gX, gX, int gY, gY, int dR, dR, int gR,out gR,out Krug [] [] k, out int n) n) { n = R.Next(3, 101); k = new Krug [n]; [n]; for ( ( int int i i = 0; i < n; i++) { k[i]=generisiKrug(R, gX, gY, dR, gR); } } c) void CrtajKrug( CrtajKrug( Graphics Graphics g, g, Krug k) k) {
44
if (k.popunjen) (k.popunjen) { SolidBrush cetka = new SolidBrush(k.boja); g.FillEllipse(cetka, k.x - k.r, k.y - k.r, 2 * k.r, 2 * k.r); } else { Pen olovka olovka = new Pen (k.boja); (k.boja); g.DrawEllipse(olovka, k.x - k.r, k.y - k.r, 2 * k.r, 2 * k.r); } } void crtaj( crtaj( Graphics Graphics g, g, int n, n, Krug [] [] k) { for ( ( int int i i = 0; i < n; i++) { CrtajKrug(g, k[i]); } }
3. Napisati metod kojim se generiše niz krugova koji se dodiruju spolja a čiji centri imaju fiksnu y koordinatu (datu vrednost h ). ). Poluprečnike krugova birati na slučajan način od 10 do 50. Generisati krugove dok njihova ukupna širina ne premaši datu vrednost ( a ). ). U ovom rešenju koristimo strukturu Krug definisanu u prethodnom zadatku. Poluprečnik generisanih krugova se bira između 10 i 50 pa je ukupan broj krugova na širini a najviše a/20. U skladu sa tim rezevišemo potreban prostor za najviše a/20 krugova. krugova. Širinu koju zauzimaju generisani krugovi registrujemo promenljivom s koja je na početku inicijalizovana na 0 ( s=0). Prvo generišemo poluprečnik kruga (r ). ). Ako generisanim poluprečnikom ukupna širina krugova premašuje zadatu vrednost a prekidamo formiranje niza. Inače, dodajemo novi krug čije koordinate centra su određene uslovima da svi krugovi imaju fiksiranu y koordinatu (k[n].y = h ) i da se dodiruju spolja (nadovezuju se jedan na drugi, k[n].x = s+r ). ). Boju kruga određujemo na slučajan način. Širinu koju zauzimaju generisani krugovi uvećamo za prečnik poslednjeg generisanog kruga (s += 2*r ) i nastavljamo generisanje sledećeg poluprečnika.
void generisi2( int int a, a, int h, h, Random R, R, out Krug [] [] k, out int n) n) { k = new Krug [a/20]; [a/20]; n = 0; int s s = 0; int r= r= R.Next(10, 50); while (s+2*r<=a) (s+2*r<=a) { k[n].r = r; k[n].x = s+r; k[n].y = h; k[n].boja = Color .FromArgb(R.Next(256), .FromArgb(R.Next(256), R.Next(256), R.Next(256)); n++; s += 2*r;
45
r = R.Next(10, 50); } }
4. Napisati metod kojim se od elemenata niza celih brojeva x dužine dužine n formiraju formiraju dva niza, i neparni , tako da niz parni sadrži sadrži parne a niz neparni neparne neparne elemente polaznog parni i niza x . Svakom od nizova parni i i neparni odrediti odrediti dužinu (bp i i bn ). ). Kako unapred ne znamo broj parnih i broj neparnih elemenata nizove parni i neparni kreiramo tako da mogu da prime sve elemente polaznog niza. Prolaskom kroz postojeći niz njegove elemente dodajemo na kraj jednog ili drugog novoformiranog niza u zavisnosti od njihove parnosti. Pri dodavanju elementa na kraj niza njegov indeks je jednak dosadašnjem broju elemenata niza koji posle dodavanja uvećavamo za 1 ( parni[bp] = x[i]; bp++;). void podeli( podeli( int int []x,int []x,int n,out n,out int []parni,out []parni,out int bp,out bp,out int []neparni,out []neparni,out int bn) bn) { bp = bn = 0; parni = new int [n]; [n]; neparni = new int [n]; [n]; for ( ( int int i=0;i
5. U školi koja ima najviše 100 učenika vodi se svakodnevno evidencija o vremenu koje učenici provode na Internetu. Informacije su date u fajlu internet.txt tako da se u svakoj liniji fajla nalaze, odvojeni blanko simbolom, korisničko ime učenika i vreme izraženo u minutama koje je proveo na Internetu pri jednom konektovanju na mrežu. Kreirati aplikaciju kojom se prikazuju u objektu klase ListBox korisničko ime svakog od učenika i ukupno vreme koje je proveo na Internetu. Informacije o učeniku, korisničko ime i vreme provedeno na Internetu, objedinimo u strukturu Ucenik. Na početku rezervišemo prostor za 100 učenika (niz x ), ), a broj učenika postavimo na 0 ( n=0). Iz fajla čitamo informacije o učeniku (s ime, t vreme), vreme), a zatim proverimo da li je taj učenik već registrovan u nizu. Ako jeste, odgovarajućem elementu niza uvećamo atribut vreme, inače, dodamo još jedan član nizu. Metod nadji vraća vraća poziciju u nizu gde se nalaze informacije o učeniku čije je korisničko ime s, odnosno -1 ako taj učenik nije registrovan u nizu. struct Ucenik { public string ime; public int vreme; } int nadji( Ucenik Ucenik [] [] x, int n, string s) { for (int i = 0; i < n; i++) {
46
if (x[i].ime == s) return i; } return -1; } void citaj(out Ucenik [] [] x, out int n) { x = new Ucenik [100]; [100]; n = 0; StreamReader sr sr = new StreamReader ( ("internet.txt" " internet.txt" ); ); string s, red; int p, t; while (!sr.EndOfStream) { red = sr.ReadLine(); p = red.IndexOf( " " ); ); s = red.Substring(0, p); // korisnicko ime t = Convert .ToInt32(red.Substring(p .ToInt32(red.Substring(p + 1)); // vreme p = nadji(x, n, s); if (p == -1) { x[n].ime = s; x[n].vreme = t; n++; } else x[p].vreme += t; } sr.Close(); } void prikazNiza( Ucenik Ucenik [] [] x, int n, ListBox lb) lb) { for (int i = 0; i < n; i++) { lb.Items.Add(x[i].ime + " " + + x[i].vreme); } } private void Form1_Load(object sender, EventArgs e) e) { Ucenik [] [] x; int n; citaj(out x, out n); prikazNiza(x, n,listBox1); }
6. Kreirati aplikaciju koja određuje broj pojavljivanja slova abecede u tekstualnoj datoteci čije ime je zadato u objektu klase TextBox. Pri brojanju slova ne treba razlikovati mala i velika slova. U svakom redu objekta o bjekta klase ListBox List Box prikazati odgovarajuće slovo abecede i broj njegovog pojavljivanja u datoteci. Zadatak možemo rešiti slično kao prethodni, uvođenjem strukture kojom registrujemo slovo i broj pojavljivanja tog tog slova u datoteci. Redom čitamo znak po znak iz datoteke i ako je učitano slovo već registrovano u nizu uvećamo broj njegovog pojavljivanja inače dodajemo novi element nizu. Kako unapred znamo sva slova koja se mogu pojaviti možemo za registovanje broja pojavljivanja slova da koristimo niz celih brojeva br , tako da elementi br[0], a[1], …, br[25] sadrže broj pojavljivanja redom slova ’a’,
47
’b’, ..., ’z’. Na početku sve elemente niza inicijalizujemo nulom. Redom čitamo znak po znak iz fajla dok ne dostignemo kraj. Svaki pročitani znak (ch), po potrebi konvertujemo u malo slovo ( ch - 'A' + 'a' ), ), a zatim uvećavamo odgovarajući član niza br (br[ch- 'a']++). Posle analize svih znakova iz fajla u objektu klase ListBox prikažemo tražene podatke. private void btBrojSlova_Click( btBrojSlova_Click( object object sender, sender, EventArgs e) e) { StreamReader sr sr = new StreamReader (textBox1.Text (textBox1.Text + ".txt" ); ); int [] [] br = new int [26]; [26]; for ( ( int int i i = 0; i < 26; i++) br[i] = 0; int ch; ch; while (!sr.EndOfStream) (!sr.EndOfStream) { ch=sr.Read(); if (ch (ch >= 'A' && && ch <= 'Z' ) ch = ch - 'A' + + 'a' ; if (ch (ch >= 'a' && && ch <= 'z' ) br[ch - 'a' ]++; ]++; } sr.Close(); listBox1.Items.Clear(); for ( ( int int i i = 'a' ; i < 'z' 'z' ; i++) if (br[i (br[i - 'a' ] > 0) listBox1.Items.Add(( char char )(i) )(i) + " " + + br[i - 'a' ]); ]); }
7. Napisati metod kojim se na osnovu niza a celih brojeva dužine n i prirodnog broja k formira niz b i i određuje njegova dužina d , tako što se niz a podeli podeli redom u grupe po k članova (poslednja grupa može da ima manje od k članova), sume elemenata grupa predstavljaju elemente niza b . b[0]=a[0]+a[1]+...+a[k-1] b[1]=a[k]+a[k+1]+....+a[2*k-1] ... b[i]=a[i*k]+a[i*k+1]+....+a[i*k+k-1] ... Prvo odredimo koliko u nizu a od n elemenata ima grupa po k članova (d=n/k ). ). Ako broj elemenata n nije deljiv sa k onda poslednja grupa, pri deljenju niza a na grupe po k članova, ima manje od k članova i broj elemenata niza b treba uvećati za 1 ( d++). Posle određivanja dužine niza b rezervišemo prostor za elemente niza b, i svaki element niza b postavimo na 0. Redom analiziramo elemente niza a, i -ti -ti element niza a pripada grupi i/k , pa ga dodajemo odgovarajućem elementu niza b (b[i/k] += a[i] ). void grupeK( grupeK( int int [] [] a, int n, n, int k, k, out int [] [] b, out int d) d) { d = n / k; if (n (n % k != 0) d++; b = new int [d]; [d]; for ( ( int int i i = 0; i < d; i++) b[i] = 0; for ( ( int int i i = 0; i < n; i++) b[i / k] += a[i]; }
Prikazujemo još jedno rešenje zadatka. Kao u prethodnom rešenju prvo rezervišemo prostor za elemente niza b zatim redom određujemo njegove 48
elemente. Element b[i] predstavlja sumu k elemenata a[i*k], a[i*k+1], a[i*k+2], ...,a[i*k+k-1] , izuzetak je poslednja grupa koja može da ima manje od k elemenata. elemenata. Sumu elemenata grupe registrujemo u promenljivoj s koju na početku postavimo na 0. Promenljivoj s redom dodajemo k uzastopnih uzastopnih elemenata niza a počev od elementa sa indeksom i*k (l = i*k ), ), pri tome vodimo računa da li je u pitanju poslednja grupa (l< n). Dobijenu sumu s dodelimo elementu b[i]. void grupeK2 grupeK2 ( int int [] [] a, int n, n, int k, k, out int [] [] b, out int d) d) { d = n / k; if (n (n % k != 0) d++; b = new int [d]; [d]; for ( (int i nt i=0;i
8. Dati niz krugova koji se dodiruju spolja a čiji centri se nalaze na pravoj p paralelnoj x osi, podelimo u grupe po k krugova (za dato k , možda poslednja grupa ima manje od k elemenata) i za svaku grupu odredi se krug najveće površine. Tako dobijeni krugovi su elementi niza b . Napisati metod kojim se formira niz krugova b i određuje njegova dužina d . Na slici su elementi niza b za za k =3 =3 prikazani kao popunjeni krugovi.