2014-10-27

Malo JavaScript-a

Ima jedna čudna priča o meni i znanju nekih tehnologija. Šta god uradio u istoj, ja kažem da je ne znam :). Recimo da sam shvatam da ne znam dovoljno. Jedna od tih tehnologija koju "ne znam" je JavaScript.

Tokom mog razvitka igrao sam se svačim u JavaScript-u, ali na kraju sam rešio da koristim JavaScript klase koje kontrolišu neke HTML elemente (ah, koja mudrost, sigurno postoji gomila framewokr-ova koji to isto omogućavaju, ali ja volim da se ipak sam igram). Ono što želim da uradim je sledeće: u HTML-u imam nekoliko DIV-ova, svaki od njih treba da sadrži i kontroliše nezavisni brojac. To jest treba da ispiše u DIV-u trenutnu vrednost brojača, da ponudi mogućnost reseta i mogućnost inkremenisanja tog brojača. Naravno brojači treba da budu nezavisni.

Nikakva mudrost, napravimo DIV, smestimo BUTTON koji okida neku JavaScript funkciju i eto. Mogu reći da je to dovoljno dobro, sve dok ne poželite da dodate novi brojač (mozda dinamički ...). Od jednom imamo malo kopiranja koda i tu može doći do problema. Zato ja idem drugim putem. Kada se učita HTML, ja ću da povežem DIV i neki objekat odgovarajuće klase. Ta klasa ce da ispiše sve što treba i da održava svoju instancu brojača. Problem nastaje kod samog ispisa onclick="this.increment()". To neće raditi jer se this odnosi na konkretan HTML element. Moramo nekako da prosledimo globalno ime kojom pristupamo odredjenoj instanci:

function Counter(name, draw_id)
{
  this.name = name;
  this.draw_id = draw_id;

  this.draw = function()
  {
    var html =
      'Name: '+this.name+'<br/>'+
      'Count: '+this.count+'<br/>'+
      '<button onclick="'+this.name+'.reset();">Reset</button><br/>'+
      '<button onclick="'+this.name+'.increment();">Increment</button>';
    document.getElementById(this.draw_id).innerHTML = html;
  };

  this.reset = function()
  {
    this.count = 0;
    this.draw();
  };

  this.increment = function()
  {
    this.count++;
    this.draw();
  };

  this.reset();
}

function init()
{
  c1 = new Counter('c1', 'd1');
  c1.increment();

  c2 = new Counter('c2', 'd2');
  c2.increment();
  c2.increment();

  c3 = new Counter('c3', 'd3');
  c3.increment();
  c3.increment();
  c3.increment();
}
Ovaj primer možete ovde videti: http://www.chupcko.org/blog/counter_1.html.

Šta mi se ovde ne svidja? Dva puta moramo da napišemo ime, jednom kao promenljivu, a jednom kao string. Kada bi mogli nekako ... nešto ... I tu se setim jednog malog trika, dodajem na kraju klase sledeće:

  window[this.name] = this;
I sada možemo da inicijalizujemo samo sa:
function init()
{
  new Counter('c1', 'd1');
  c1.increment();

  new Counter('c2', 'd2');
  c2.increment();
  c2.increment();

  new Counter('c3', 'd3');
  c3.increment();
  c3.increment();
  c3.increment();
}
Ovaj primer možete ovde videti: http://www.chupcko.org/blog/counter_2.html.

Kada bi mogli nekako da dobijemo za neku instancu neke klase u JavaScript-u njegovo ime ... Pa ček, JavaScript ima nešto mnogo lepo, možemo uz pomoć Object.prototype da dodajemo nove funkcije koje važe za sve objekte. Ideja je da dodamo dve funkcije, neka se jedna $id i neka vraća neki id instance objekta. A druga $name koja vraća ime (u kojem naravno dominira spomenuti id). Sve to zatvoreno u closure (zbog globalne promenljive koja se koristi za id), i uz malo keširanja:

(
  function()
  {
    var _id = 0;
    Object.prototype.$id = function()
    {
      if(typeof this.$_id === 'undefined')
      {
        this.$_id = _id;
        _id++;
      }
      return this.$_id;
    };
    Object.prototype.$name = function()
    {
      if(typeof this.$_name === 'undefined')
      {
        this.$_name = '$$_'+this.$id();
        window[this.$_name] = this;
      }
      return this.$_name;
    };
  }
)();

function Counter(draw_id)
{
  this.draw_id = draw_id;

  this.count = undefined;

  this.draw = function()
  {
    var html =
      'Name: '+this.$name()+'<br/>'+
      'Count: '+this.count+'<br/>'+
      '<button onclick="'+this.$name()+'.reset();">Reset</button><br/>'+
      '<button onclick="'+this.$name()+'.increment();">Increment</button>';
    document.getElementById(this.draw_id).innerHTML = html;
  };

  this.reset = function()
  {
    this.count = 0;
    this.draw();
  };

  this.increment = function()
  {
    this.count++;
    this.draw();
  };

  this.reset();
}

function init()
{
  var c1 = new Counter('d1');
  c1.increment();

  var c2 = new Counter('d2');
  c2.increment();
  c2.increment();

  var c3 = new Counter('d3');
  c3.increment();
  c3.increment();
  c3.increment();
}
Ovaj primer možete ovde videti: http://www.chupcko.org/blog/counter_3.html.

I na kraju malo ulepšano: http://www.chupcko.org/blog/counter_4.html.

Naravno sve ovo može i na drugi način, ali ostaviću vama da ga sami pronadjete (igranje DOM-om :) ).

2014-08-22

Sortiranje ...

Naravno većina je pomislila da ću pričati nesto o algoritmima sortiranja. Neću, pokušaću da napišem program koji sortira nekoliko promenljivih. Konkretno 3 :).

Poučen negativnim iskustvom izbora mladih programera, a bogami i starijih, uz ideju Dušana Milanova počeo sam da na intervijuima zadajem zadatak da se napiše mali program za sortiranje tri promenljive. Recimo da imate tri celobrojne promenljive a, b i c i da želite da po zavrsetku programa/algoritma/koda važi da je a <= b <= c. Hoce reći da ih sortirate u neopadajući niz :).

Svačega sam se naslušao, neću ni da pokušavam da se setim svega toga. Zar vam deluje to kao težak zadatak ako mislite da se bavite programiranjem?
Ispada da jeste težak zadatak za mnoge. Ako mislite da ste programer, prestanite sa čitanjem ovde i pokušajte da sami napravite neku svoju verziju.

Pa da krenemo, po meni sortiranje tri promenljive je dosta složeno, pa bi ja da prvo uradimo sortiranje dve promljive:
void sort2(int* a, int* b)
{
  if(*a > *b)
    swap(a, b);
}
Naravno sada možemo da koristeći sort2 uradimo sort3:
void sort3(int* a, int* b, int* c)
{
  sort2(a, b);
  sort2(a, c);
  sort2(b, c);
}
Da li je to bilo teško? Po meni je dosta logično. sort2 je nekako intutivan, neću ni da pokušam da ga opravdam. sort3 je ono što bi prvo uradili i napisali, da uredimo svakog sa svakim. Naravno pitanje je da li to može bolje?

Ajde da prvo analiziramo ovaj kod, uzmimo da su a, b i c iz skupa {2, 3, 5} i različiti (što ne 4, eto iz hira, uzeo sam proste brojeve):
abcswaps
2350
2531
3251
3522
5232
5323
Uvek će biti 3 poredjenja, ali brojimo swapove, jedino ono na kraju mi ne pije vodu, ocigledno da moze samo sa jednim swapom.
Gde je problem? Za prvi sort2 treba izabrati nešto što je neminovno, ali što ne moze pokvariti redosled. Ako bi izabrali da prvo uradimo sort2(a, c) nećemo puno pokvariti. Dakle sada je progam ovakav:
void sort3(int* a, int* b, int* c)
{
  sort2(a, c);
  sort2(a, b);
  sort2(b, c);
}
Pogledajmo sada koliko će biti swapova:
abcswaps
2350
2531
3251
3522
5232
5321
Ovo sada već deluje optimalno po broju swapova, ali kako stoje stvari sa brojem poredjenja. Može se uraditi sa minimalno 2 poredjenja a ne uvek sa 3. Doduše izgubićemo optimalnopst po broju swapova. Kako znamo da ćemo izgubiti optimalnost po broju swapova, pa tako što poredjenje a i c mora doći na kraju. Dva prva poredjenja koja moramo da izvršimo su a sa b i b sa c. Ako nam je poredjenje skupo, primenićemo recimo:
void sort3(int* a, int* b, int* c)
{
  sort2(a, b);
  if(*b > *c)
  {
    swap(b, c);
    sort(a, b);
  }
}
Doduše opet u nekim slučajevima imamo testiranje viška, recimo za slučaj 2, 5, 3. Koje je rešenje, pa i nema ga :). Najbolje što možemo je da napustimo ovaj lep koncpet svodjenja sort3 na sort2 i napisemo mrežu testova.
void sort3(int* a, int* b, int* c)
{
  if(*a < *b)
  {
    /* a < b */
    if(*b < *c)
    {
      /* a < b && b < c => a, b, c */
    }
    else
    {   
      /* a < b && c < b */
      if(*a < *c)
      {
        /* a < b && c < b && a < c => a, c, b */
        swap(b, c);
      }
      else
      {
        /* a < b && c < b && c < a => c, a, b */
        swap(a, c);
        swap(b, c);
      }
    }
  }
  else
  {
    /* b < a */
    if(*a < *c)
    {
      /* b < a && a < c => b, a, c */
      swap(a, b);
    }
    else
    {
      /* b < a && c < a  */
      if(*b < *c)
      {
        /* b < a && c < a && b < c => b, c, a */
        swap(a, b);
        swap(b, c);
      }
      else
      {
        /* b < a && c < a && c < b => c, b, a */
        swap(a, c);
      }
    }
  }
}
Šta je sada zanimljivo, i dalje imamo utisak da može bolje, ali eto ja ne znam kako. Po broju swapova ne može, to je to, ali nekada imamo suvišne testove, recimo za slučaj 5, 3, 2. Ovaj kod je već dosta složen i nepregeldan. Da li je bolje ostaviti sladak i fin kod ili ići na totalnu optimizaciju.

I ovde stajem. Za razmišljanje je sledeće: kako uraditi sort4 recimo. Naravno koristeći sort2 i sort3 :). Za one baš napredne, nadjite paralelu izmedju raznih algoritama sortiranja i raznih realizacija sort4 (sortn). Možda o tome nekada.

P.S. Inače, većina zna, nisam baš neki pristalica C++, ali često čujem kako je std::sort iz C++ brži od qsort u C-u. Jeste, istina je, ne baš zbog toga što je C++ brži/bolji/moćniji jezik od C-a, nego zato što std::sort koristi introsort i direktno ugradjuje funkciju za komparaciju za razliku od qsort koji koristi quick sort i pozivanje funkcije za komparaciju :).

2014-08-21

Da krenemo ...

Ovaj blog  će generalno biti posvećen programiranju, za sada na srpskom, bez nešto previše mudrosti. Više kao mesto gde ću da se hvalim svojim nekim glupostima. Koga ne zanima, neka samo produži dalje, kritike neću da gledam :).

Svi vi koji ste gledali moj sajt, očekujte tako nešto i ovde.