2016-10-24

Webhooks na github-u

Kako sam se ozbiljno upustio u rad na starom projektu (https://chupcko.org/karel/), reših da ga stavim na github (https://github.com/chupcko/karel), pa ajde i da automatizujem dovlačenje current verzije na WEB sa github-a. Ovoga puta sam pitao nekoga ko zna to bolje od mene, šta da koristim. Miloš mi je samo kratko rekao: "webhooks".

Ideja je jednostavna, github poziva neki zadati URL kada se desi neki dogadjaj (recimo meni zanimljiv push). Namestio za minut jednu shell skriptu i jedan PHP koji je poziva, prijavio URL i sve to lepo radi. Ali, ček bre, svako može da ga pozove, ima li neke zaštite? Ima naravno, pa ajde da je implementiramo. Sve u svemu, evo moje verzije PHP-a:

<?php

  $secret = 'tajna';

  if
  (
    array_key_exists('REQUEST_METHOD', $_SERVER) === false or
    $_SERVER['REQUEST_METHOD'] !== 'POST' or

    array_key_exists('CONTENT_TYPE', $_SERVER) === false or
    $_SERVER['CONTENT_TYPE'] !== 'application/json' or

    array_key_exists('HTTP_USER_AGENT', $_SERVER) === false or
    strpos($_SERVER['HTTP_USER_AGENT'], 'GitHub-Hookshot/') !== 0 or

    array_key_exists('HTTP_X_GITHUB_EVENT', $_SERVER) === false or
    $_SERVER['HTTP_X_GITHUB_EVENT'] !== 'push' or

    array_key_exists('HTTP_X_HUB_SIGNATURE', $_SERVER) === false or
    $_SERVER['HTTP_X_HUB_SIGNATURE'] !== 'sha1='.hash_hmac('sha1', file_get_contents('php://input'), $secret)
  )
    die();

  system('/var/www/github.sh > /dev/null 2>&1 &');

?>
Možda nekome pomogne, ko zna?

2016-08-04

Jedan problem iz prakse programiranja u C-u

Često imam potrebu da nekoj funkciji prosledim niz stringova, nešto oblika char* names[]. Naravno ako već nemam niz stringova, napraviću neku tabelu, to jest dvodimenzionalni niz karaktera, nešto oblika char table[][10]. I da li će raditi, pa naravno da neće, čak i kompajler kaže da nešto nije u redu. Pa da, mi smo lokalno deklarisali veliki niz karaktera (grupisanih po 10), a funkcija očekuje niz pokazivača na karakter, to jest niz stringova. Eto belaja. Kako se ovo rešava, pa klasika, napravimo taj niz stringova, lepo ga napunimo i pozovemo funkciju. Dobro, nema tu puno spasa, možemo da paralelno održavamo te dve strukture i sve je super. Problem rešen.

Ali, desi mi se, s vremena na vreme, da je tabela statična, poznata u trenutku kompajliranja. Stvarno ću da pravim funkciju za punjenje names? Neću iskoristiti neki mehanizam, recimo makroe? Iskustvo me uči, ajde prvo napiši ručno pa da vidimo da li može neka automatizacija i koliki je problem ako ne može. I eto prve verzije:

char table[][10] =
{
  "a1",
  "a2",
  "a3",
  "a4"
};

char* names[] =
{
  table[0],
  table[1],
  table[2],
  table[3]
};
Čuj, upotrebljivo je, nije čak i neka velika frka dodavanje novog stringa u table, samo treba da nešto dopišem u names, dodam na kraj i to je to. Ali ajde igre radi da pokusamo da to malo automatizujemo. Koristićemo X makroe, i ajde da prvo napravimo fajl table.x u kojem ćemo nabrojati stringove, ali i neka njihova imena koja cemo koristiti u enum-u. Korišćenje enum-a je korisno, svako ko je pisao nešto ovako zna da vredi dodati enum sa indexima stringova u tabeli. Dakle fajl table.x:
#if defined(TABLE)

TABLE(A1, "a1")
TABLE(A2, "a2")
TABLE(A3, "a3")
TABLE(A4, "a4")

#endif
Ovo je prošlo lagano, kako sada pravimo enum? Pa definisaćemo makro TABLE, inkludujemo table.x i zavrsimo nabrajanje necim. Pa to ponovimo ponovo za sve ostalo. Evo kako naš primer izgleda sada. Ko želi neka iskoristi gcc -E opciju da proveri da li radi.
enum table_indexes
{
  #define TABLE(index, string) TABLE_INDEX_##index,
  #include "table.x"
  #undef TABLE
  TABLE_SIZE
};

char table[][10] =
{
  #define TABLE(index, string) string,
  #include "table.x"
  #undef TABLE
  ""
};

char* names[] =
{
  #define TABLE(index, string) table[TABLE_INDEX_##index],
  #include "table.x"
  #undef TABLE
  NULL
};
Meni radi, ne znam za vas, ali ajde da krenemo da cepidlačimo malo: novi fajl? Ma samo mi to treba, ajde da malo zabiberimo. Napravićemo makro FOR_EACH_TABLE koji kao argument ima ime novog makroa koji ćemo pozivati. Naravno \ na kraju celog makroa zahteva da iza bude jedan prazan red. Da se ne lažemo malo ko neće odvojiti ovaj makro praznim redom, a sada barem svi redovi "database-a" liče.
#define FOR_EACH_TABLE(call) \
  call(A1, "a1") \
  call(A2, "a2") \
  call(A3, "a3") \
  call(A4, "a4") \

enum table_indexes
{
  #define TABLE_INDEXES_CALL(index, string) TABLE_INDEX_##index,
  FOR_EACH_TABLE(TABLE_INDEXES_CALL)
  TABLE_SIZE
};

char table[][10] =
{
  #define TABLE_CALL(index, string) string,
  FOR_EACH_TABLE(TABLE_CALL)
  ""
};

char* names[] =
{
  #define NAMES_CALL(index, string) table[TABLE_INDEX_##index],
  FOR_EACH_TABLE(NAMES_CALL)
  NULL
};
I ovo možete proveriti, meni naravno radi :). Šta dalje? Pa ne znam ni sam, recimo onaj enum mi je višak (mada izuzetno koristan u realnosti, kao sto rekoh). Kada bi postojao neki način da makro broji. Ovde vec zalazimo u domen GCC-a. Ko ne želi da zna nešto više o GCC-u neka stane sa čitanjem, nastavak se odnosi samo na njihovu extenziju. Naime postoji predefinisani makro __COUNTER__ koji svakim pozivom se razvija u sledeći broj, počevši od 0. Ha, pa sada je ovo baš izuzetno lako:
#define FOR_EACH_TABLE(call) \
  call("a1") \
  call("a2") \
  call("a3") \
  call("a4") \

char table[][10] =
{
  #define TABLE_CALL(string) string,
  FOR_EACH_TABLE(TABLE_CALL)
  ""
};

char* names[] =
{
  #define NAMES_CALL(string) table[__COUNTER__],
  FOR_EACH_TABLE(NAMES_CALL)
  NULL
};
Nije loše, ali ja ću uvek izabrati varijantu sa enum-om, ostati lepo u standardnom C-u i završiti posao :)

P.S. Mogao sam da napravim gomilu makroa koji broje, ali nisam baš siguran da je pametno definisati jedno 1000 makroa da bi pokrio sve slučajeve. To je za jedan drugi post, jednom ...

2015-07-15

To goto or not to goto

Ovo nije post o tome da li da idem ili ne, jeste da je i potencijalni odlazak vezan za programiranje, ali ovo je priča o goto, omrženoj komandi. Svi koji se bave programiranjem su u ranoj fazi (osim ako nisu kao neki, započeli fortranom kao prvim jezikom) naučili da se goto ne koristi. Da samo loši programeri koriste goto. Da je to fuj-no-no-kakano i ko samo i pomisli na goto oćelaviće prerano, oslepeće, zavrsiće kao profesor informatike, ... .

Tako sam se i ja klonio goto komande, izbegavao je u širokom luku. Uvek sam bio svestan da postoji, nekada me je continue ili break podsećalo na goto, ali njih niko nije toliko ispljuvao :). I tako jednog dana sam radio nešto što se često radi kada se programira za kernel. Napisao "lep" kod (bez goto), ali ček, previše je, da kažemo, nečitljiv. Hm, ali ako bi iskoristio goto ... . Ne, ne, ne i ne. Kroz glavu mi prolazi ono ćelavenje, ćorav već jesam, ali profesor informatike ... . Imao sam strašnu dilemu, nisam znao šta da radim. Sa jedne strane "pravilo" kojim su me vaspitali, sa druge strane čitljivost i praktičnost koda.

Bacio sam se dosta na istraživanje, čitao pametnice (koje su uglavnom smislili i pisali Linux kernel) koje savetuju da je ok koristiti tu i tamo goto zarad čitljivosti koda. I finalno sam presudio. Na ovom mestu i u ovoj situaciji goto je najbolje rešenje. Ovo sada nije da Vam se pravdam, samo nekada se treba zapitati da li su sve dogme dobre (reče čovek koji uporno radi u c-u).

Da izložimo slučaj, predstaviću dva koda napisana bez upotrebe goto, i jedan napisan sa goto, pa sami presudite šta je bolje, ili još bolje dajte predlog novog koda bez goto. Zadatak je bio da se odradi niz od recimo 5 (u mom realnom životu je bilo 12) operacija. Ali ako neka od njih ne uspe, potrebno je opozvati prethodno pozvane. Mozda zvuci složeno, ali da evo pojednostavimo: pozivamo doA(), pa doB(), ali ako nije uspeo doB(), potrebno je pozvati undoA(). I tako do naših 5 poziva.

response_t work(void)
{
  if(doA() == fail)
    return fail;
  if(doB() == fail)
  {
    undoA();
    return fail;
  }
  if(doC() == fail)
  {
    undoB();
    undoA();
    return fail;
  }
  if(doD() == fail)
  {
    undoC();
    undoB();
    undoA();
    return fail;
  }
  if(doE() == fail)
  {
    undoD();
    undoC();
    undoB();
    undoA();
    return fail;
  }
  return done;
}
Ovo smo baš hteli da napišemo, ali čemu ovoliko ponavljanja, ajde da to ugnjezdimo u pitalice.
response_t work(void)
{
  if(doA() != fail)
  {
    if(doB() != fail)
    {
      if(doC() != fail)
      {
        if(doD() != fail)
        {
          if(doE() != fail)
            return done;
          undoD();
        }
        undoC();
      }
      undoB();
    }
    undoA();
  }
}
return fail;
Čisto da jos jednom podsetim, moj realni, životni primer je imao 12 operacija, ovo je samo sa 5 i već vidite da nije jednostavno. Ajde da pokušamo koristeci goto:
response_t work(void)
{
  if(doA() == fail)
    goto failA;
  if(doB() == fail)
    goto failB;
  if(doC() == fail)
    goto failC;
  if(doD() == fail)
    goto failD;
  if(doE() == fail)
    goto failE;
  return done;
failE:
  undoD();
failD:
  undoC();
failC:
  undoB();
failB:
  undoA();
failA:
  return fail;
}
Šta Vam je čitljivije i šta zahteva najmanji mentalni napor da shvatite o čemu se radi. Naravno da se generiše uvek "isti" mašinski kod, ali baš me zanima šta bi ste Vi izabrali. goto ili ne goto?

P.S. Naravno u ovim primerima doX funkcije nemaju argumente i vraćaju uvek isti tip, ali u praksi su svakojake, sa različitim argumentima i različitim povratnim vrednostima :).

2015-06-25

A sada malo o C makroa ...

Šta najviše smeta C++-ašima u C-u. Pa makroi, i onda su ih prvo nazvali štetnim i lošim i zamenili nekim drugim mehanizmima (na sličan način štetnim, po meni). Da ne ulazim sada u tu priču, pokušaću da pokažem neke stvari koje se mogu uraditi standardnim makroima u C-u. Ovog puta izbeći cu priču o x-makroima, koga zanima neka pogleda https://en.wikipedia.org/wiki/X_Macro.

U jednoj od brojnih razmena misljenja sa dragim Mrkijem (uvek nesto naučim posle bar dva minuta priče sa njim) došli smo do retoričkog pitanja: Kako razrešavas u C-u a->b->c->d->e, ali da vrati NULL ako je bilo koji od a, a->b, a->b->c, a->b->c->d baš NULL. Prva ideja sa nekom funkcijom koja bi ... pa ček kako ćes funkciji proslediti ime? Ne ide. Ajde da napišemo konkretno neki kod, pa da vidimo kako bi to moglo da se ulepša, generalizuje, makroizuje.

if
(
  a == NULL ||
  a->b == NULL ||
  a->b->c == NULL ||
  a->b->c->d == NULL
)
  result = NULL;
else
  result = a->b->c->d->e;
Prvo što primećujem je da možemo ovu pitalicu da zamenimo sa omiljenim tenarnim ?:, ali ajde pre toga da razvijem gomilu or-ova. Zašto, pa eto, iskustvo mi govori da to treba uraditi :).
if(a == NULL)
  result = NULL;
else if(a->b == NULL)
  result = NULL;
else if(a->b->c == NULL)
  result = NULL;
else if(a->b->c->d == NULL)
  result = NULL;
else
  result = a->b->c->d->e;
Sada možemo da iskoristomo ?: i dobijemo izraz, koji se može koristiti, pa i dodeliti u neki result:
(a == NULL ? NULL : (a->b == NULL ? NULL : (a->b->c == NULL ? NULL : (a->b->c->d == NULL ? NULL : a->b->c->d->e))))
Kako bi bilo lepo da ovo mozemo da pozovemo recimo sa N(a, b, c, d, e). Eh, steta sto C makroi nisu tako moćni. Ili možda jesu?

Šta primećujem, da ako bi uzeli prva dva argumenta (a i b), zatim proverili da li je a jednako NULL pa vratili ili NULL ili rekurzivni poziv (umesto prva dva argumenta imamo a->b i ostatak). Treba još da obezbedimo izlazak iz rekurzije, što je jednostavno, kada ima samo dva argumenta zaustavi se tu. Ali ono sto je teži deo posla, to je sto C makroi ne podržavaju rekurziju. Šta uraditi?

Ajde da napišemo nekoliko funkcija, dogovorimo se da svaka poziva prehodnu, nazovimo ih N_2, N_3, N_4, ... dokle tako, pa ajde da se dogovorimo i da kazemo da nikada neće biti vise od 16 promenljivih u ovom nizu. Da ispišemo te makroe, barem do N_5:

#define N_2(a, b) (a == NULL ? NULL : a->b)
#define N_3(a, b, c) (a == NULL ? NULL : N_2(a->b, c))
#define N_4(a, b, c, d) (a == NULL ? NULL : N_3(a->b, c, d))
#define N_5(a, b, c, d, e) (a == NULL ? NULL : N_4(a->b, c, d, e))
Odlično, probamo sa gcc -E (prepuštam vama da proverite, ja verujem sebi, za sada :) ). Radi! Dobro, može ovako da se ispiše do N_16, pa i do N_100 ako zatreba i eto. Nije baš praktično, moramo da pozovemo tačnu funkciju, i složeno je za pisanje. Pa ajde da optimizujemo nekako. Primetimo da sve posle trećeg argumenta samo ponavljamo, dakle super, moze moja omiljena forma: ellipsis. Ne znam da li ste primetili, ali ja baš volim ellipsis ...
#define N_2(a, b, ...) (a == NULL ? NULL : a->b)
#define N_3(a, b, ...) (a == NULL ? NULL : N_2(a->b, __VA_ARGS__))
#define N_4(a, b, ...) (a == NULL ? NULL : N_3(a->b, __VA_ARGS__))
#define N_5(a, b, ...) (a == NULL ? NULL : N_4(a->b, __VA_ARGS__))
Ah, kako me nervira ovo ponavljanje oblika (x == NULL ? NULL : y), zameniću ga makrom _N. O istom trošku dodaću i N_0 i N_1, neka se nadju, zatrebaće možda.
#define _N(x, y) (x == NULL ? NULL : y)
#define N_0(...) NULL
#define N_1(a, ...) a
#define N_2(a, b, ...) _N(a, a->b)
#define N_3(a, b, ...) _N(a, N_2(a->b, __VA_ARGS__))
#define N_4(a, b, ...) _N(a, N_3(a->b, __VA_ARGS__))
#define N_5(a, b, ...) _N(a, N_4(a->b, __VA_ARGS__))
Ovo već liči na nešto, kada bi mogli nekako da izračunamo broj argumenata zadatih makrou i pozovemo odgovarajući makro. Nešto oblika:
#define N(...) N_##NUMBER(__VA_ARGS__)(__VA_ARGS__)
Na žalost, ne postoji ništa što moze da izracuna broj argumenata, osim da to sami ne napišemo kao makro. A i tada, nece doci do zamene, pa ajde da ubacimo koji makro više (više ćete saznato o tome kada naučite kako makroi rade, i kada/kako vrše zamenu):
#define N__(n, ...) N_##n(__VA_ARGS__)
#define N_(n, ...) N__(n, __VA_ARGS__)
#define N(...) N_(NUMBER(__VA_ARGS__), __VA_ARGS__)
Sve je to lepo, ali i dalje nam nedostaje makro NUMBER koji računa koliko je bilo argumenata.

Ideja je jednostavna, ako iza __VA_ARGS__ navedemo redom brojeve, pa uzmemo neki na fiksnoj poziciji i dobićemo duzinu. Naravno treba namestiti da lepo radi.

#define NUMBER_(x5, x4, x3, x2, x1, n, ...) n
#define NUMBER(...) NUMBER_(__VA_ARGS__, 5, 4, 3, 2, 1)
Ovo računa broj argumenata samo do duzine 5, ali eto, lako se da produžiti. Ne radi za prazan ulaz, ali i to se može srediti vezivanjem __VA_ARGS__ za prethodni argument. Na žalost to nije deo standarda, ali naravno gcc to guta, nadam se da će uskoro i to da prihvate u standardu. Naime x, ##__VA_ARGS__ će se razviti u samo x (nema zareza) ako je __VA_ARGS__ prazno, inace normalno se radi konkatenacija.
#define NUMBER_(x, x5, x4, x3, x2, x1, n, ...) n
#define NUMBER(...) NUMBER_(x, ##__VA_ARGS__, 5, 4, 3, 2, 1, 0)
Ha, imamo sve, sada možemo da spojimo, i da proširimo do recimo 16:
#define NUMBER_(x, x16, x15, x14, x13, x12, x11, x10, x9, x8, x7, x6, x5, x4, x3, x2, x1, n, ...) n
#define NUMBER(...) NUMBER_(x, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define _N(x, y) (x == NULL ? NULL : y)
#define N_0(...) NULL
#define N_1(a, ...) a
#define N_2(a, b, ...) _N(a, a->b)
#define N_3(a, b, ...) _N(a, N_2(a->b, __VA_ARGS__))
#define N_4(a, b, ...) _N(a, N_3(a->b, __VA_ARGS__))
#define N_5(a, b, ...) _N(a, N_4(a->b, __VA_ARGS__))
#define N_6(a, b, ...) _N(a, N_5(a->b, __VA_ARGS__))
#define N_7(a, b, ...) _N(a, N_6(a->b, __VA_ARGS__))
#define N_8(a, b, ...) _N(a, N_7(a->b, __VA_ARGS__))
#define N_9(a, b, ...) _N(a, N_8(a->b, __VA_ARGS__))
#define N_10(a, b, ...) _N(a, N_9(a->b, __VA_ARGS__))
#define N_11(a, b, ...) _N(a, N_10(a->b, __VA_ARGS__))
#define N_12(a, b, ...) _N(a, N_11(a->b, __VA_ARGS__))
#define N_13(a, b, ...) _N(a, N_12(a->b, __VA_ARGS__))
#define N_14(a, b, ...) _N(a, N_13(a->b, __VA_ARGS__))
#define N_15(a, b, ...) _N(a, N_14(a->b, __VA_ARGS__))
#define N_16(a, b, ...) _N(a, N_15(a->b, __VA_ARGS__))
#define N__(n, ...) N_##n(__VA_ARGS__)
#define N_(n, ...) N__(n, __VA_ARGS__)
#define N(...) N_(NUMBER(__VA_ARGS__), __VA_ARGS__)
Ah, a pa zato sam definisao N_0 i N_1 ... sada je i meni jasno :). Ostaje da sami proverite da li radi i da krenete da ozbiljno razmi[ljate o makroima u C-u.

P.S. Ako ste dokoni, uradite na slican nacin makro MAP(f, ...) koji na niz argumenata primenjuje (makro) f().

2015-01-01

A sada malo sed-a

Svako ko je uzeo da uči shell scripting, pre ili kasnije upozna magičnu komandu sed. Malo ko zna sta znači njeno ime, kako je nastala, ali to je nešto što već može da se nadje svuda po netu. Uglavnom svi koriste kao u sledećem primeru
echo mama | sed 's/m/t/g'
Na stranu što to može da se uradi i nekom drugom jednostavnijom komandom (mislim na ovaj konkretni primer). Ali tu staje prica oko sed-a. Oni malo napredniji nauče sed komandu d, pa napišu nešto ovako:
last | sed '1,10\!d'
Dobro, možda im nije zatrebala sva ta genijalnost koju nosi u sebi komanda sed. A sada pravi razlog ovog posta.

Pre neki dan videh na facebook-u ovaj strip:

i u mom redovnom ..., da kažemo lošem ponašanju gde nipodaštavam one koji znaju manje od mene (znam da nije lepo, ali eto trudim se da ih prodrmam :) ) dodjemo do toga da
rm -rf /
neće da radi. Zašto, prosto postoji u samom kodu za rm komandu zaštita koja ne dozvoljava da argument bude / kao i neki alternativni načini zadavanja slash-a.

I naravno kako sam vec drčan, postavih zadatak: obrisati sve diskove ali iz sed-a :). Pre sam već koristio sed da pronadje apsolutnu putanju pozvanog shell skripta (molim one koji se bave jezikom da se ne uzbudjuju, znam da je plurale tantum, ali ne ide mi da kazem shell skripte kada je samo jedna skripta :) ). To se nalazi na mom sajtu kao primer lepog sed-a:

#!/bin/sh
echo $0|sed -e"
      \|^/|!{h
      s|.*|pwd|;e
    G;s|\n|/|};:x
      s|/[^/]*/\.\./|/|
      s|/\./|/|
      s|//|/|
      s|^/\.\.||;tx
      s|/[^/]*$||"
E sada, ako sam rekao da može, onda moram to i da pokažem. Evo ga sed-a koji prikazuje aktivne stalne diskove na sistemu, naravno ne brišem ih, ne želim da budem destruktivan, zaustavio sam se kod toga da ih samo prikaže:
#!/usr/bin/sed -f
1{s|.*|find /sys/block -type l|;e
s|/sys/block/||g;H;:l g;/^\n/!b e;s|^\n\([a-z0-9]*\).*$|cat /sys/block/\1/uevent|;e
/DEVTYPE=disk/!b n;g;s|^\n\([a-z0-9]*\).*$|cat /sys/block/\1/capability|;e
/^50$/!b n;g;s|^\n\([a-z0-9]*\).*$|cat /sys/block/\1/removable|;e
/^0$/!b n;g;s|^\n\([a-z0-9]*\).*$|cat /sys/block/\1/size|;e
/^0$/b n;g;s|^\n\([a-z0-9]*\).*$|\1|;p;:n g;s|^\n[a-z0-9]*\(.*\)$|\1|;h;b l;:e q}
Neću objašnjavati kako tačno radi, to prepuštam onima koji žele da uče, ali ukratko ovaj sed simulira shell i koristi find i cat komande, kao i /sys filesystem. Po startovanju trazi enter da bi zapoceo rad.

Eto, možda će neko sada posvetiti više vremena tome da prouči sed.

A ja, pokušaću da implementiram neku igricu jednostavnu u sed-u.

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 :).