Uvod
Nizovi u JavaScript-u, deklarišu se i inicijalizuju na vrlo komotan način, ne podležu strogoj 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 većini situacija u praksi - naravno, 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 (ostavićemo to za neku drugu priliku), ali ćemo za sada definisati nizove u JavaScript-u kao posebne promenljive koje omogućavaju smeštanje kolekcija podataka preko jednog identifikatora (naziva promenljive), pri čemu 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 (prazan) niz, već objekat sa sistemskom vrednošću null
):
let niz = [ ];
Niz je prazan, ali, svakako mu se u daljem toku izvršavanja programa, 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 blok try-catch
, 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 način koji je uobičajen u programskom jeziku C, * uz navođenje indeksa u velikoj zagradi.
let imenaNiz = [ "Dejan" , "Milan" , "Jovan" ];
let imeOsobe = imenaNiz[2]; // "Jovan"
Kao i u C-u, indeksiranje počinje od nule (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 očitati 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 (push)
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 funkcija splice
(više o funkciji splice
u nastavku).
Uklanjanje elemenata sa početka ili kraja niza (pop i shift)
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 u prethodnom odeljku, može delovati pomalo čudno, ali, komanda splice
je veoma univerzalna i može se koristiti za obe operacije:
niz.splice(indeks, n_uklanjanje, elementi_za_dodavanje ....);
// indeks - indeks na kome se obavlja dodavanje, ili
// od koga počinje uklanjanje
// n_uklanjanje - 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, potrebno je postaviti indeks na poziciju koja odgovara mestu na kome želimo da se pojavi prvi element iz liste novih elemenata i takođe, potrebno je 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, potrebno je navesti dva parametra: indeks prvog elementa koji se uklanja 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 trećoj poziciji (indeks 2), uklonili smo jedan element.
Kombinovani pristup
Po potrebi, prethodne dve operacije je moguće kombinovati (pri čemu se obavezno predaje tri ili više argumenata).
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 (concat)
U JavaScript-u, dva niza možemo spojiti preko funkcije concat
, 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 (sort)
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 ]
.... i niz će biti sortiran.
U praksi (pogotovo za jednostavne primere), više nego prihvatljivo.
Spajanje niza u nisku znakova (join)
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(); // "N,i,z,o,v,i"
let s2 = a.join("*"); // "N*i*z*o*v*i"
let s2 = a.join(""); // "Nizovi"
Ukoliko se funkciji join
ne preda argument, rezultujuća niska nastaje direktnim spajanjem elemenata niza, uz dodavanje zareza između svaka dva susedna elementa.
Ukoliko se, kao argument, preda niska (koja predstavlja crtu, prelazak u novi red i sl), predata niska se (u okviru rezultujuće niske), pojavljuje između svaka dva elementa prvobitnog niza (videti gornji primer).
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 (split)
Deljenje niske obavlja se preko funkcije split
, rezultat je lista elemenata, 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 č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.
every
Sledeći kod ....
a.every(x => x > 1)
.... vraća vrednost true
pod uslovom da su svi elementi niza veći od 1 (u suprotnom, povratna vrednost je false
).
some
Sledeći kod ....
a.some(x => x > 10)
.... vraća vrednost true
pod uslovom da je bar jedan element niza veći od 10 (u suprotnom, povratna vrednost je 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, 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, možemo (kao što smo već nagovestili), koristiti sledeću sintaksu:
let a = [ 1, 2, 3, 4, 5 ];
a.forEach(element => {
console.log(element);
});
Primećujemo da pri korišćenju funkcije forEach
, elementima ne pristupamo preko indeksa, već preko reference element
(s tim da je u pitanju proizvoljno izabran identifikator).
Sintaksa deluje intuitivno, međutim, forEach
nije petlja, nego (lambda) funkcija, pa se tako (na primer), za prekid ciklusa ne koristi rezervisana reč break
, već return
.
Kod koji smo prikazali jeste intuitivan, ali, da biste za prave razumeli sve što smo naveli o funkcijama kao što su map
, filter
i sl, biće neophodno da se (u dogledno vreme), detaljnije upoznate sa funkcijama povratnog poziva i lambda izrazima (za šta možete iskoristiti članak koji smo već ranije linkovali).
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 ....