Funkcionalno programiranje u Javascriptu 2 – uvod u big data algoritme, pretraga velikih struktura podataka

Rastko Vukašinović, JS developer u Vastu, piše drugi deo teksta o funkcionalnom programiranju i Javascriptu

Rastko Vukašinović - 15. Avgust, 2016.

Korišćenje mep/ridjus algoritama i drugih funkcionalnih pristupa procesiranju velikih količina podataka obično se ne povezuje direktno sa Javaskriptom. Drugi programski jezici daleko su više korišćeni u ovu svrhu. Možda JS nije najoptimalniji jezik za ovakvu primenu (zbog asinhrone prirode jezika i lakoće razvoja, “možda” je naglašeno), ali postoji veliki benefit koji dolazi iz poznavanja ovakvog načina rada i sposobnosti primene, naročito sada kada FRP postaje sve popularniji pristup programiranju korisničkih interfejsa.

Dakle, evo brzog uvoda u način razmišljanja vođen strukturama podataka, kroz primere pretrage većih struktura podataka.

Filtriranje niza na osnovu sadržaja delova njegovih elemenata – prvi korak


/**
* Ovde imamo metod koji pretražuje kroz strukturu podataka koja pradstavlja setove DC Comocs grafičkih novela, vraćajući univerzume (string imena) setova koji sadrže traženi broj izdanja (znam da je ovo teško primer iz stvarnog života)
*/

var data = [
{ issues: [1, 2, 3], universe: ‘new52’ },
{ issues:[66, 77, 88], universe: ‘earth one’ },
{ issues:[2,11,22,33], universe: ‘earth two’ },
{ issues:[1,223,334], universe: ‘silver age’ }
];
var SearchByIssue = function(lookForNumber) {
return data.filter((set) => {
  return set.issues.some((number) => {
    return number === lookForNumber;
  });
}).map((set) => set.universe);
};

Primer iznad daje nam listu imena setova u kojima se nalazi izdanje sa brojem koji tražimo… Znam, ovo je suviše pojednostavljen primer :D

Ono što je vrlo optimalno kod ovog pristupa je to što .some() staje čim naiđe na prvi rezultat koji odgovara uslovu (vraća true), što je sjajno za ovakve vrste pretraga (kada some() vrati true, isti možemo koristiti kao triger procene u filter() kolbeku.

Filtriranje kolekcije (niza) na osnovu sadržaja edelova njegovih elemenata – drugi korak

Deo podataka na osnovu čijeg sadržaja tražimo je niz stringova unutar objekta (izdanja) unutar niza (set izdanja – edicija) koji je deo veće kolekcije u okviru seta kolekcija.

Drugim rečima, imamo mnogo kutija sa mnogo izdanja DC Comics stripova i izdanje u bazi nije predstavljeno samo brojem već strukturom koja nosi više informacija:


/** svaka kolekcija minimum sadrži niz izdanja i ime kolekcije, ali može sadržati i druge opisne podatke pored imena... */

[{ issues: [{
   id: 32,
   name:”Kingdom Come”,
   authors: [“Mark Waid”, “Alex Ross”],
   characters: [“Superman, Batman, Diana Prince, Captain Marvel, Spectre, Norman McCay”]
}...],
 universe: "alternatives"
},
...]

Konačan cilj je pronalaženje svih izdanja u kojima se pojavljuje određeni lik, tako da kao odgovor dobijemo lokaciju izdanja kao naziv kutije (univerzum) i id izdanja u toj kutiji.

Lako je pričati – pokaži mi kod


var SearchByCharacter = function(complexData, searchForName) {

/**
filter() pronalazi članove niza za koje kolbek vraća true, te mi prvo samo prođemo kroz sadržaj kutije koristeći some() da utvrdimo u kojima se nalazi ono što tražimo. some() vraća true čim se naiđe na član niza za koji kolbek vraća true što nam automatski izoluje samo kutije koje nam trebaju)
*/
return complexData.filter((item) => {
   return item.issues.some((issue) => {
       return (issue.characters.indexOf(searchForName) !== -1);
   });
})
/**
U sledećem koraku mapiramo podatke iz kutija za koje smo utvrdili da sadrže ono što tražimo (niz koji smo izfiltrirali)
**/
.map((box) => {
   /**
Unutar kutije mapiramo izdanja vraćajući niz adresa unutar velike kolekcije, zajedno sa bilo kojim drugim traženim podacima
   */
   return box.issues.map((issue) => {
                          return {
                             box: box.universe,
                             id: issue.id,
                             characters: issue.characters
                          };
                       })
/**
Dobijene podatke filtriramo po likovima koji se pojavljuju i to je ono što vraćamo za svaku kutiju (niz lokacija izdanja u kojima se lik pojavljuje u datoj kutiji)
*/
   .filter((issue) => {
       return (issue.characters.indexOf(searchForName) !== -1);
   });
})
/**
Rezultat je niz nizova izdanja sa likom koga tražimo. 

Dalje redukujemo sve ove nizove u jedan niz i tako dobijamo naše rezultate pretrage.
**/
.reduce((a, b) => a.concat(b));
};

Ako pogledamo primer iznad, vidimo da nema mnogo koda… Pristup je jednostavan i sastoji se uglavnom od funkcija višeg reda koje procenjuju podatke serijom kolbek funkcija za ispitivanje podataka.

Funkcije višeg reda su gradivne jedinice funkcionalnog programiranja, i pomoću njih dobijamo novu perspektivu rada sa velikim strukturama podataka. Korišćenjem istih smo u mogućnosti da posmatramo bilo koji proračun, pretragu ili bilo kakav drugi podacima vođen zadatak kao seriju proračuna i procena nad datim podacima. Svaka od operacija prosleđuje se funkciji višeg reda i njen rezultat po određenim pravilima prolazi dalje kroz naredne obrade kako bi na kraju dobili željeni konačni rezultat.

Ovakva obrada podataka, takođe, radi bez menjanja podataka nad kojima se proračuni i procene izvršavaju. Ovim dobijamo jednostavniju i čitljiviju strukturu koda i jedan od vrhunskih paterna za rad sa setovima podataka.

Paterni opisani ovde i u mom prethodnom članku na ovu temu, mogu se lako primeniti na rad sa “tokovima” podataka (“streams”, vremenski promenljive strukture podataka – tzv. obzervable), što ih može činiti i dobrim uvodom u reaktivno funkcionalno programiranje.

Tekst u izvornom obliku možete pročitati ovde, a prethodni Rastkov tekst ovde.