Uvod
Nizovi u Javascript-u deklarišu se i inicijalizuju na vrlo komotan način, ne podležu tipizaciji (niz može sadržati elemente različitog tipa), interna implementacija nizova je prilično kompleksna, a - ako bismo baš hteli - našli bismo bar još koju zamerku.
Međutim, mora se priznati da u radu sa nizovima u JS-u ima šarma i lepote (ima i svega navedenog iz prethodne rečenice; nismo zaboravili) i mora se priznati da u praksi - naravno, sve pod uslovom da "pazimo šta radimo" - stvari funkcionišu prilično glatko.
Pristup koji predlažemo čitaocima (i koji primenjujemo u praksi), podrazumeva da - kada već programski jezik dozvoljava "sve i svašta" - vođenje računa o bitnim stvarima preuzmemo na sebe, i sa nizovima postupamo onako kako bismo postupali da koristimo jezik sa strogom tipizacijom.
U takvim okolnostima, Javascript nam neće priređivati neprijatna iznenađenja.
Nizovi u Javascript-u uopšteno
Za početak, valja razmotriti šta se (uopšte) u Javascript-u podrazumeva pod pojmom "niz", jer krajnji rezultat zavisi od endžina koji se koristi, * pa ne možemo biti sigurni šta se dešava "ispod haube" (gde "niz" može biti zapisan: bilo kao statički niz, bilo kao hash-mapa, sve u zavisnosti od okolnosti).
Ovoga puta, bićemo praktični i nećemo ulaziti u detaljnu diskusiju o implementaciji nizova (ovom tematikom ćemo se svakako pozabaviti u doglednoj budućnosti), ali ćemo za sada definisati nizove u Javascript-u kao posebne promenljive koje omogućavaju smeštanje više podataka preko jednog identifikatora (naziva promenljive), pri čemu dati podaci ne moraju biti istog tipa, a moguće je i proizvoljno dodavanje i uklanjanje elemenata.
Deklaracija
Osnovni vid deklaracije niza u JS-u (praktično) podrazumeva i inicijalizaciju praznog niza u istoj liniji koda (jer, ukoliko izostavimo inicijalizaciju, promenljiva neće biti referenca na niz, već objekat sa sistemskom vrednošću null
):
let niz = [];
Niz je prazan, ali svakako mu se u daljem radu mogu dodavati elementi.
Inicijalizacija
Pored inicijalizacije praznog niza (kakvu smo već videli), često se javlja i inicijalizacija/deklaracija koja podrazumeva zadavanje (nekoliko) elemenata:
let niz = [ 1, 2, 2, 3, 4, 5, 5 ];
let imena = [ "Milan", "Petar", "Jovan" ];
Takođe, niz može biti inicijalizovan i preko funkcija koje vraćaju nizove.
let niz = funkcijaKojaVracaNiz(ulaz);
// Primer:
let s = "papir;kamen;makaze";
let lista = s.split(";"); // [ "papir" , "kamen" , "makaze" ]
Što se tiče inicijalizacija koje smo videli na slici #3, na scenu stupa "preuzimanje odgovornosti" sa naše strane.
Ako za inicijalizaciju koristimo funkciju koju smo sami definisali, postaraćemo se da funkcija: ili uredno inicijalizuje niz, ili da vrati izlaznu vrednost po kojoj ćemo prepoznati da je došlo do greške (ne moramo obavezno koristiti izuzetke i try-catch
blok, ali svakako nećemo prihvatati "zdravo za gotovo" da je niz uredno inicijalizovan).
Ukoliko koristimo gotova rešenja, konsultovaćemo dokumentaciju i ponovo biti pažljivi.
Pristup elementima
Pristup elementima niza obavlja se na uobičajen način, navođenjem indeksa u velikoj zagradi.
let imeOsobe = imena[2]; // "Jovan"
Kao i u C-u, indeksiranje počinje nulom (pa je u nizu od 5 elemenata, poslednji indeks koji možemo koristiti 4).
Očitavanje dužine niza - length
Dužinu niza (broj elemenata), možemo odrediti navođenjem sledećeg koda (preko svojstva length
):
let d = niz.length;
Sada možemo (recimo) napraviti i for
petlju koja će pristupiti svim elementima:
for (let i = 0; i < d; ++i) {
niz[i] = niz[i] + 1;
}
Dodavanje elementa na kraj niza
Za dodavanje elementa na kraj niza, koristi se komanda push
:
// [ 1, 2, 2, 3, 4, 5, 5 ]
niz.push(10); // [ 1, 2, 2, 3, 4, 5, 5, 10 ]
Za dodavanje elementa (ili elemenata) na proizvoljnu poziciju, koristi se komanda splice
(više o tome u nastavku).
Uklanjanje elemenata
Kada je u pitanju uklanjanje elemenata, razlikujemo tri slučaja:
- uklanjanje elementa sa kraja niza
- uklanjanje elementa sa početka niza
- uklanjanje elementa sa proizvoljne pozicije
Element sa kraja niza uklanja se preko komande pop
:
// niz: [ 1, 2, 2, 3, 4, 5, 5, 10 ]
let p = niz.pop();
// p: 10
// niz: [ 1, 2, 2, 3, 4, 5, 5 ]
Element sa početka niza uklanja se preko komande shift
:
// niz: [ 1, 2, 2, 3, 4, 5, 5 ]
let p = niz.shift();
// p: 1
// niz: [ 2, 2, 3, 4, 5, 5 ]
Kada je u pitanju uklanjanje elementa sa proizvoljne pozicije, koristi se (već pomenuta) funkcija splice
, s tim da je u pitanju univerzalna komanda preko koje je moguće obaviti: i dodavanje, i uklanjanje.
Funkcija splice - dodavanje i/ili uklanjanje elemenata na proizvoljnoj poziciji
Poslednje što smo naveli može delovati pomalo čudno, ali, komanda splice
je veoma univerzalna i može se koristiti za obe operacije:
niz.splice(indeks, n_brisanje, elementi_za_dodavanje ....);
// indeks - indeks na kome se obavlja dodavanje, ili
// od koga počinje uklanjanje
// n_brisanje - broj elemenata koje treba ukloniti
// elementi - lista elemenata koji će biti dodati
Kao što vidimo, zaista veoma univerzalno, ali - kako to (sve) funkcioniše u praksi?!
Dodavanje elemenata
Za dodavanje elemenata, postavićemo indeks na poziciju koja odgovara mestu na kome želimo da se pojavi prvi element iz liste novih elemenata i naglasiti da se nijedan element ne uklanja (drugi argument je 0). Od trećeg argumenta počinje lista elemenata koji se umeću.
Ako je i
indeks za umetanje, a n
broj elemenata za umetanje, cela operacija podrazumeva umetanje liste od n elemenata između pozicija i
i i + 1
.
let niz = [ 1 , 2 , 3 , 4 , 5 ]
niz.splice(2, 0, 1001, 1002)
// [ 1 , 2 , 1001 , 1002 , 3 , 4 , 5 ]
U konkretnom primeru, između druge i treće pozicije, umeću se dva nova elementa (1001, 1002).
Uklanjanje elemenata
Za uklanjanje elemenata, navodimo dva parametra: indeks prvog elementa koji je potrebno ukloniti i ukupan broj elemenata koje je potrebno ukloniti.
let niz = [ 1 , 2 , 3 , 4 , 5 ]
niz.splice(2, 1)
// [ 1 , 2 , 4 , 5 ]
U konkretnom primeru, na 3. poziciji (indeks 2) uklonili smo jedan element.
Kombinovani pristup
Po potrebi, prethodna dva pristupa je moguće kombinovati.
Prvo se uklanja određen broj elemenata (onoliko koliko je navedeno preko drugog argumenta) - počevši od indeksa koji je naveden kao prvi argument, pa se zatim umeću novi elementi, na mesto koje (praktično) odgovara poziciji na kojoj je došlo do uklanjanja.
let niz = [ 1 , 2 , 3 , 4 , 5 ]
niz.splice(2, 2, 2001, 2002)
// [ 1 , 2 , 2001, 2002, 5 ]
U konkretnom primeru, prvo se uklanjaju elementi sa indeksima 2 i 3 (dva elementa, počevši od indeksa 2), a zatim se između novih indeksa 1 i 2 (ili, uslovno rečeno, između prvobitnih indeksa 1 i 4) umeću novi elementi (2001 i 2002).
Spajanje nizova
U Javascript-u, dva niza možemo spojiti na sledeći način:
let a = [ 1, 2, 3 ];
let b = [ 4, 5, 6 ];
let c = a.concat(b); // [ 1, 2, 3, 4, 5, 6 ]
Sortiranje nizova
Možemo naravno implementirati i sopstvene funkcije za sortiranje nizova, ali, možemo umesto toga jednostavno pozvati sledeći kod.
let a = [ 2, 1, 3, 5, 4 ];
a.sort(); // [ 1, 2, 3, 4, 5 ]
U praksi (pogotovo za jednostavne primere), više nego prihvatljivo (složenost je O(nlogn)).
Spajanje niza u nisku znakova
Ukoliko želimo da niz elemenata spojimo u nisku, možemo koristiti komandu join
:
let a = [ "N", "i", "z", "o", "v", "i" ];
let s1 = a.join(); // "Nizovi"
let s2 = a.join("*"); // "N*i*z*o*v*i"
U obradi tekstualnih datoteka, komanda join
(baš kao i komanda split, koju ćemo prikazati u narednom odeljku), koristi se često.
Pretvaranje niske znakova u niz
Deljenje niske, obavlja se preko funkcije split
, a kao kriterijum za podelu može se koristiti određena niska ili regularni izraz:
let s = "Pariz-London-Lisabon";
let a = s.split("-"); // [ "Pariz", "London", "Lisabon" ]
Kao što je pomenuto, funkcije join
i split
jako dobro dođu u obradi teksta, pa tako, na primer, možemo pravilno formatirati listu imena u kojoj se separatori (znakovi za razdvajanje), ne koriste na dosledan način:
// Dobre informacije, ali, nedosledna
// upotreba znakova za razdvajanje:
let s = "Milan Ivan, Jovan # Dejan";
// Regex je praktično kolekcija svih "razdvajača":
let regex = /([ ,#])/
let lista = s.split(regex); // [ "Milan", " " , "Ivan", ",",
// " ", "Jovan", " ", "#",
// " " , "Dejan" ]
// Napomena: u praksi, lista bi sadržala i nekoliko
// praznih stringova ("")
let nova_lista = [];
for (let i = 0; i < lista.length; ++i) {
// Ako je element liste prazna niska ili neki
// od separatora, nećemo takav element ubacivati
// u novu listu:
if (lista[i] == "" || lista[i].match(regex)) {
continue;
}
// Sve ostale elemente (praktično - samo imena),
// ubacićemo u novu listu:
nova_lista.push(lista[i]);
}
// Na kraju, spojićemo listu u ("običnu") nisku,
// u kojoj će svako ime biti zapisano u novom redu
let s2 = nova_lista.join("\n");
Nisku smo rastavili preko funkcije split
, prošli smo kroz sve elemente liste (koja je nastala rastavljanjem niske), izdvojili smo imena i na kraju smo od liste formirali ("običnu") nisku, preko funkcije join
.
Funkcije višeg reda za rad sa nizovima
Funkcije koje smo nabrojali u ovom članku nisu naravno (ni iz daleka) sve funkcije koje se u JS-u koriste za rad sa nizovima, već samo one koje smatramo najkorisnijim za sam početak.
Iako smo članak namenili korisnicima sa ne-baš-mnogo iskustva, osvrnućemo se i na nekoliko (ponešto) naprednijih funkcija za rad sa nizovima koje se obično koriste u sprezi sa tzv. "lambda" notacijom.
Ne dajte da vas navedeni termini zaplaše, jer, u pitanju je prilično jednostavan način zapisa koji većina programera (čak i mlađih i/ili neiskusnijih) može lako prihvatiti intuitivno, a, kada bude došlo vreme, možete se detaljno upoznati sa sintaksom funkcija povratnih poziva i lambda izraza (ostavićemo pred kraj link prema članku koji smo posvetili navedenim temama).
every
Sledeći kod ....
a.every(x => x > 1);
.... vratiće true
pod uslovom da su svi elementi niza veći od 1 (u suprotnom, vratiće false).
some
Sledeći kod ....
a.some(x => x > 10);
.... vratiće true
pod uslovom da je bar jedan element niza veći od 10 (u suprotnom, vratiće false).
map
Ukoliko želimo da kopiramo niz i pri tom novim elementima dodelimo određene vrednosti preko funkcije, možemo koristiti sledeću sintaksu:
let a = [ 1, 2, 3, 4, 5 ];
let b = a.map(x => x * x);
Pokretanjem poslednje skripte, nastaće novi niz sa vrednostima 1, 4, 9, 16 i 25 (koje predstavljaju kvadrate vrednosti iz ulaznog niza).
filter
Ukoliko je potrebno da, pri kopiranju niza, uklonimo određene vrednosti, možemo koristiti komandu filter
.
Cela operacije podrazumeva kreiranje novog niza, u koji će biti kopirane samo vrednosti koje zadovoljavaju uslov:
let a = [ 1, 2, 3, 4, 5 ];
let b = a.filter(x => x > 3);
U primeru sa gornje slike, novi niz sadržaće samo elemente 4 i 5.
forEach
Za pojednostavljeni zapis petlje koja prolazi kroz ceo niz (kao što smo već nagovestili), možemo koristiti sledeću sintaksu:
let a = [ 1, 2, 3, 4, 5 ];
a.forEach(element => {
console.log(element);
});
Primećujemo da kod u slučaju funkcije forEach
elementima ne pristupamo preko indeksa, već preko reference element
.
Sintaksa deluje intuitivno, međutim, forEach
nije petlja, nego (lambda) funkcija, pa tako (na primer) za prekid ciklusa nećemo koristiti rezervisanu reč break
, već return
.
Kao što rekosmo, intuitivno jeste, ali, da biste za prave razumeli ono što smo naveli, biće neophodno da se (što pre, tim bolje) upoznate sa funkcijama povratnog poziva i lambda izrazima, za šta možete iskoristiti članak koji smo posvetili navedenim temama.
Za kraj ....
Kao što smo videli, rad sa nizovima u JS-u nije nikakav "bauk" (naravno, uz opasku da svakako moramo voditi više računa nego u strogo tipiziranim jezicima, gde ipak postoje određeni mehanizmi koji će nas sprečiti da u niz celobrojnih vrednosti dodajemo niske i slično).
Sledeći članak o JS-u posvetićemo implementaciji čvorova i struktura podataka u ovom programskom jeziku ....