11 martie 2015

Probleme pentru interviu - Structuri de date

M-am apucat de Cracking the coding interview, cartea-minune care promite sa te trimita in bratele jobului bine platit pe care il visezi :)

It claims that it "gives you the interview preparation you need to get the top software developer jobs". Asa ca mi-a atras atentia si am inceput sa o parcurg. Din primele 4 seturi, care au tangenta cu structurile de date, am selectat cateva enunturi mai interesante, pentru "luare aminte". 

---------------------------- cap. 1 - siruri de caractere -----------------------------------------------------

* aflati daca s1 este o rotatie a lui s2, avand la dispozitie functia isSubstring(s1,s2) = true daca s1 subsir al lui s2.

Sol: daca s1 = "water", s2 = "terwa" => s1 este o rotatie a lui s2
s1+s1 = "waterwater" 
if s1.length () == s2.length && if isSubstring (s1+s1, s2) then true
else false

* să se înlocuiască fiecare spațiu dintr-un string cu %20 . Șirul este alocat fără limită superioară. A nu se folosi resurse suplimentare.

Sol: se iterează stringul pentru a vedea câte spații sunt. Se triplează numărul de spații pentru a găsi unde va fi poziția ultimului caracter. Apoi, de la coadă către capăt, se va copia caracter cu caracter, cu '%20' în loc de spațiu.

-------------------------- cap. 2 - liste inlantuite -----------------------------------------------------------

* aflati al k-lea element de la coada al unei liste simplu inlantuite (nu se stie dimensiunea ei)

Sol.1: se ajunge pana la capat recursiv si se intoarce incrementand pasii pana la locul de unde s-a pornit
int nthToLast (LinkedListNode head, int k) {
  if (head == null) return 0; // a ajuns la capat
  int i = nthToLast(head.next, k) + 1; // 1+ distanta urmatorului
  if (i == k) System.out.println(head.data);
}

Sol.2: cu doi pointeri aflati la distanta k unul de altul, se plimba pe lista, pana cand al doilea a ajuns la capat => returneaza informatia primului


* stergerea unui nod dintr-o lista simplu inlantuita, avand doar un pointer p la acel nod

Sol: se copiaza informatia din nodul urmator in cel curent si se sare peste nodul urmator catre celalalt
p.data = p.next.data;
p.next = p.next.next;


* intr-o lista inlantuita circulara, ret. nodul aflat la inceputul unei bucle (daca bucla exista)

Sol: se folosesc 2 pointeri. Unul merge cu pasul 1 (p1), celalalt sare din 2 in 2 pozitii (p2). Daca unul din pointeri ajunge la null => nu exista bucle
Altfel: pp ca lista incepe cu o parte liniara si apoi urmeaza bucla. Daca p1 ajunge la inceputul buclei aflat (sa spunem) la o distanta "k" fata de inceputul listei, p2 deja va fi parcurs k noduri in interiorul buclei. Distanta intre p1 si p2 = k = loop_size - k . Toate parcurgerile de acum inainte ale lui p1 si p2 se vor petrece in cadrul buclei: la fiecare un pas al lui p1, p2 parcurge 2 pasi. Dupa un calcul simplu, se poate vedea ca p1 si p2 se vor intalni dupa loop_size - k pasi simultani (la fiecare pas devin din ce in ce mai aproape). loop_size - k inseamna si k pasi distanta de inceputul buclei, care la randul ei se afla la k pasi distanta fata de startul listei (un desen ajuta f.mult). Se introduce un al treilea pointer p3 la startul listei, care se misca in acelasi timp cu p1, incepand de la momentul intalnirii lui p1 cu p2. Cand p1 si p3 se intalnesc (dupa k pasi) acela este inceputul buclei.


* verificare daca o lista e palindroma, cand nu ii cunoastem dimensiunea

Sol: se poate refolosi ideea a doi pointeri care merg unul cu pasul 1, celalalt din 2 in 2.
Pentru fiecare pas al lui p1, se salveaza informatia nodului parcurs intr-o stiva, timp in care p2 tot sare mai departe. Cand p2 a ajuns la null, p1 se opreste din push() si incepe sa faca pop(), continuand parcurgerea in lista si comparand elementul extras cu informatia nodului curent. La nepotrivire => false, altfel, daca a ajuns la null => true. (Daca lista are nr. impar de noduri, pt. p2 trebuie facuta verificarea ca p2!=null && p2.next==null) .


-------------------------- cap. 3 - stive & cozi ------------------------------------------------------

* def. o stiva s1 care are in plus o functie min ce returneaza elementul minim (conditie: O(1) pt push, pop, min)

Sol.1: pt fiecare element sa se pastreze, pe langa valoare, si un minim curent => push(value, newMin), unde newMin = Math.min (value, min()) si min() = peek().min; Pb.: consuma mult spatiu

Sol.2: o noua stiva s2 care pastreaza minimele, in care se adauga numai valori din ce in ce mai mici. 
=> push :   s1.push(value);    if(value < s2.peek()) s2.push(value);
     pop:      val = s1.pop();       if(val == s2.peek())   s2.pop();
     min:      s2.peek()


* Pb. turnurilor din Hanoi

Sol.: Despre asta am mai scris aici, dar era o rezolvare doar de suprafata. Pentru rezolvarea cu stive, pp ca fiecare turn este o stiva, iar discurile sunt elementele ce trebuie mutate de pe stiva 1 pe stiva 3, prin intermediul stivei 2. Ideea porneste de la cazurile de baza: un singur disc se muta direct de pe s1 pe s3. Pentru 2 discuri, primul se muta pe s2 intermediar, al doilea direct pe s3, iar apoi cel de pe s2 direct pe s3. Pentru 3 discuri si mai mult, se muta primul disc direct, apoi se presupune ca pasii sunt la fel ca in cazurile anterioare deja rezolvate, deci se poate apela la recursivitate, dar de data asta schimband rolurile intre s2 si s3.

void move (Stack from, Stack via, Stack to) {
  n = from.size();
  if (n == 1) {
    // caz de baza
    disk = from.pop();
    to.push(disk);
  }
  else if (n == 2) {
    // al doilea caz de baza
    disk2 = from.pop();
    via.push(disk2);
    disk1 = from.pop();
    to.push(disk1);
    disk2 = via.pop();
    to.push(disk2);
  }
  else {
    disk = from.pop();
    move(from, to, via);
    // totul e mutat in via
    to.push(disk);
    // se muta celelalte discuri din via in to
    move(via, from, to);
  }
}


* implementarea unei cozi folosind 2 stive

Sol: enqueue() sa se faca la o stiva, dequeue() la cealalta. Cand s2 se goleste, se rastoarna elementele din s1 in s2 - astfel cele mai proaspete elemente din s1 vor deveni cele mai tarzii pentru s2.


* sortarea unei stive, a.i. cele mai mari elemente sa fie in varf. Se poate folosi cel mult inca o stiva, fara nici un fel de alta structura auxiliara.

Sol: s1 = stiva de sortat; s2 = o stiva auxiliara mereu sortata
Vrem sa mutam varful lui s1 in s2 => il scoatem (elem) din s1, apoi scoatem elemente din s2 punandu-le in s1, pana cand am gasit "locul" lui elem (elem >= s2.peek()). Numaram de cate ori s-au mutat elemente din s2 in s1, ca sa stim cate punem la loc. Deci, punem elem in s2 si peste el atatea elemente din varful lui s1 cate s-au scos. 
Pentru sortarea intregului s1, se repeta pasii de mai sus pana cand s1 devine goala.
Asadar: push - in s1 si apoi cum am spus mai sus, peek/pop se ofera din s2. 


---------------------------- cap. 4 - arbori binari & grafuri -------------------------------------------------

* crearea unui arbore binar de cautare cu inaltime minima, pornind de la un sir sortat

Sol: In arborele binar de cautare, left <= root < right .  Root = mijlocul sirului.

TreeNode createMinBST (int arr[], int start, int end) {
  if (end < start) return null;
  int mid = (start + end) / 2;
  TreeNode n = new TreeNode (arr[mid]);
  n.left = createMinBST(arr, start, min-1);
  n.right = createMinBST(arr, mid+1, end);
  return n;
}


* aflarea succesorului unui nod dintr-un arbore binar, metoda inordine

Sol: preordine = RSD; inordine = SRD; postordine = SDR
Deci, succesorului lui R (radacina) este nodul D (dreapta).
La o simulare pe hartie, se observa ca exista doua cazuri:
- nodul are un copil pe aripa dreapta => succesorul este cel mai din stanga nod frunza al copilului de pe partea dreapta
- nodul nu are un copil pe aripa dreapta => SRD curent este incheiat, intreg SRD-ul reprezinta parcurgerea aripii drepte ale unui predecesor => succesorul va fi ultimul stramos pe a carei ramuri drepte se afla nodul. 
Un desen cu numere de ordine ajuta.


* Primul stramos comun a doua noduri p, q dintr-un arbore binar, avand acces la nodul radacina.

Sol.1: Se verifica daca p, q se afla pe aceeasi parte (sa spunem stanga) a radacinii curente, printr-o parcurgere oarecare pt a afla daca un nod apartine unui arbore. Daca se afla pe parti diferite, radacina este stramosul comun. Altfel, se repeta recursiv procesul, in functie de partea pe care se afla ambele noduri: daca e stanga, noua radacina e acel nod stang, altfel, cel drept.

Sol.2: Parcurgerea arborelui o singura data. 
Result commonAncestor ( root, p, q) {
  if (root == null) return new Result(null, false);
  if (root == p && root == q) return new Result(root, true);
  Result rx = commonAncestor (root.left, p, q);
  if (rx.isAncestor) return rx;
  Result ry = commonAncestor (root.right, p, q);
  if (ry.isAncestor) return ry;
  if (rx.node != null && ry.node != null) return new Result(root, true);
  else if (root == p || root == q) {
    boolean isAnc = rx.node != null || ry.node != null ? true : false;
    return new Result (root, isAnc);
  }
  else return new Result (rx.node != null ? rx.node : ry.node, false);
}


* Toate caile dintr-un arbore binar ale caror sume sunt egale cu o anumita valoare V . 

Sol: "does this node end with a sum?"
La fiecare nivel se propaga un vector de noduri repr. calea de la radacina pana la nodul curent si nr. nivelului.
Nodul curent se adauga la cale. Apoi, de la cel mai recent la cel mai vechi, se aduna valorile nodurilor din cale, iar cand se obtine V => se afiseaza intreaga cale de la nodul curent la acel nod cu care s-a completat suma. 
Se repeta recursiv pentru copilul pe partea dreapta si cel pe partea stanga, cu nivelul++ si noua cale.

28 februarie 2015

Nachos: Acquire & Release implementation for Lock

Adaptata la solutia de aici & aici.

#define FREE false
#define BUSY true

Lock::Lock(char* debugName)  {
    name=debugName;
    value=FREE;
    queue=new List;
}

void Lock::Acquire() {
    IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts
    // if lock not available...
    if (value == BUSY) {
queue->Append (currentThread); // add to queue & go to sleep
currentThread->Sleep();
    }
    // here thread is waken up, the real acquire begins
    value=BUSY;
held=currentThread;  // save the thread which got the lock
    interrupt->SetLevel(oldLevel); // re-enable interrupts
}

void Lock::Release() {
    IntStatus oldLevel = interrupt->SetLevel(IntOff);
    Thread* thread = (Thread *) queue->Remove();
    if (held==currentThread) { // this is necessary to avoid a crazy thread releasing others
        if (thread!=NULL) {
            scheduler->ReadyToRun(thread);    // add it to ready queue
}
        else { // if queue is empty
           value=FREE;
        }
    }
    interrupt->SetLevel(oldLevel);
}


Meanings:
FREE : there is no other thread inside the Lock
BUSY: there is at least one thread either in the queue or in the process of acquiring


// there is a slightly equivalent solution
// however I think the previous one is more meaningful
// variable held is kind of useless here

void Lock::Acquire() {
    IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts
    // if lock not available...
    while (value == BUSY) {
queue->Append (currentThread); // add to queue & go to sleep
currentThread->Sleep();
    }
    // here thread is waken up, the real acquire begins
    value=BUSY; 
    held=currentThread;
    interrupt->SetLevel(oldLevel); // re-enable interrupts
}

void Lock::Release() {
    IntStatus oldLevel = interrupt->SetLevel(IntOff);
    if (held==currentThread) {
    Thread* thread = (Thread *) queue->Remove();
        if (thread!=NULL) {
            scheduler->ReadyToRun(thread);    // add it to ready queue
}
        value=FREE;
    }
    interrupt->SetLevel(oldLevel);
}

-------------------------

Some differences Lock vs. Semaphore
* Semaphore has a counter of resources whereas Lock is either busy or free. Lock is used for Mutual Exclusion only, i.e, only one thread can access the shared resources at any time.
* Lock has owner whereas Semaphore does not have any owner.
* Lock does not track number of resources/ number of waiting tasks, whereas Semaphore does.
* Only the thread which is the owner of the Lock can Release the lock on resource. Semaphore can be accessed by any thread.
* Semaphore is a shared object while Lock is not.

24 ianuarie 2015

Exemple, exercitii in SML

tuple:    (1, 2, 3);
record: {unu=1, doi=2, trei=3};
list:       [1, 2, 3];

list concat:     [1,2] @ [2,3];
list append:    1::2::3::nil;
string concat: "ab" ^ "bc";

;; value declaration
val a = 2; val (a, b, c) = (1, 2, 3);
;; wildcard
val (a, _, c) = (1,2,3);

;; functie fara nume
(fn x => x+1);
;; functie cu nume
f: val f = (fn x => x+1); f 2;
;; aceeasi functie
fun g (x) = x + 1;

map f [1,2,3];

;; functie cu cazuri speciale
fun f 1 = 1 | f (x) = x + 1;

;; infix
fun minus (a,b) = a - b;
infix minus;
6 minus 2;

;;functii recursive
fun fact (x) = if x=0 then 1 else n*fact(n-1);
;; SAU
val rec fact = (fn x => if x=0 then 1 else fact(n-1));

;; functii mutual recursive
fun odd (x) = ..... and even (x) = ..... ;

;; andalso, orelse - operatii logice doar pe booleeni

;; functie locala
local ... in ... end;

-----------------------------------------------

1) un numar e putere a lui 2

fun pow2 1 = true
| pow2 x = (x mod 2 = 0) andalso pow2 (x div 2);

pow2 8;
pow2 10;


2) inserarea unui nr. la o lista sortata a.i. lista ramane sortata

fun insert (x, []) = x::nil
| insert (x, y::ys) =
if x < y then x::y::ys else y::insert(x,ys);

insert (3, []);
insert (1, [2,3,4]);
insert (5, [4,7,9]);


3) sortare prin insertie (folosind functia de mai sus)

fun insertionSort [] = nil
| insertionSort (m::myList) = insert (m, insertionSort myList);

insertionSort [5,2,7,4,1,0,8,9];


4) toate submultimile unei multimi

local
    fun add x ys = x :: ys
in
    fun subsets1 []      = [[]]
      | subsets1 (x::xr) = subsets1 xr @ map (add x) (subsets1 xr)
end;

subsets1 [1,2,3];


5) o structura de date pt. coordonate in 2D si aflarea distantei dintre 2 puncte

(* coords = datatype name
Cords = constructor *)
datatype coords = Cords of real * real;

val p1 = Cords (1.2, 3.4);

fun sq (x:real) = x * x;

fun distance (Cords(x1, y1):coords, Cords(x2, y2):coords) =
Math.sqrt (sq (x2-x1) + sq (y2-y1));

distance (Cords (1.0, 2.0), Cords (1.1, 2.1));

(* alta s.d. pt o persoana *)
datatype person = Pers of string * string * int;
val x = Pers ("anna", "ehcat", 25);


6) Variabile-referinta
val i = ref 10; 
inc(i);
dec(i);
i := 20;


7) alt exercitiu cu structuri de date

datatype item
  = Vgn of string * real
  | Vgt of string * real
  | Omn of string * real;

fun isVegan (Vgn _) = true
| isVegan _ = false;

fun findVegan [] = []
| findVegan (m::mylist) = if isVegan m then m::findVegan(mylist) else findVegan mylist;

(*
findVegan [Vgn ("apple", 2.7), Vgt ("milk", 1.2), Omn ("beef", 5.5), Vgn ("coconut", 3.3)];
*)

(* FIND THE CHEAPEST OMNIV. FOOD *)

fun findCheapO ([], product) = product
| findCheapO ((Omn (name, price))::rest, Omn(name2, price2)) =
if price < price2 then findCheapO (rest, Omn(name, price))
else findCheapO (rest, Omn(name2, price2))
| findCheapO (m::other, cheapest) =
findCheapO(other, cheapest);

fun findCheapestOmni mylist = findCheapO (mylist, Omn("dummy", 999.9));

val superStore = [Vgn ("apple", 2.7), Vgt ("milk", 1.2), Omn ("beef", 5.5), Vgn ("coconut", 3.3), Omn("fish", 3.2), Omn("eggs", 7.8)];

(*
findCheapestOmni superStore;
*)

(* FIND THE PRICE OF AN ORDER *)

fun getPrice (Vgn (name, price)) = price
| getPrice (Vgt (name, price)) = price
| getPrice (Omn (name, price)) = price;

fun getName (Vgn (name, price)) = name
| getName (Vgt (name, price)) = name
| getName (Omn (name, price)) = name;

datatype order = Ord of string * int;

fun lookup (myorder, []) = ~1.0
| lookup (Ord(name, quant), m::mylist) =
if name = getName (m) then getPrice(m)
else lookup(Ord(name, quant), mylist);

(*
lookup(Ord("beef", 2), superStore);
*)


(* input: an order & a menu
output: total cost of the order *)

fun cost (order, []) = 0.0
| cost (Ord(name, quant), mylist) =
let val price = lookup (Ord(name, quant), mylist)
in price * real(quant)
end;

(*
cost(Ord("beef", 2), superStore);
*)

(* input: a list of orders & a menu
output: the total cost of all the orders
*)

fun totalCost ([], menu, cost1) = cost1
| totalCost (orderList, [], cost1) = cost1
| totalCost (ord::orderList, menu, cost1) =
totalCost (orderList, menu, cost(ord, menu) + cost1);

totalCost ([Ord("beef", 2), Ord("fish", 3)], superStore, 0.0);

------------------------------

8) al n-lea element dintr-o lista

fun nth ([], n) = ~99999
| nth (mylist, 0) = ~99999
| nth (m::mylist, 1) = m
| nth (m::mylist, n) = nth (mylist, n-1);

nth ([5,8,3,1,0], 4);


9) elementul de la jumatatea listei

fun middle ([]) = ~99999
| middle (mylist) = nth (mylist, length (mylist) div 2 + 1);

middle ([1,2,3,4,5,6]);


10) produsul cartezian al doua multimi

fun cartesian (nil, blist) = nil
| cartesian (alist, nil) = nil
| cartesian (a::alist, b::blist) =
(a,b) :: cartesian (a::nil, blist) @ cartesian(alist, b::blist);

cartesian (["a","b","c"],[1,2]);

10 ianuarie 2015

Pe ce se duce taxa de studii intr-un semestru... (studiu de caz) - intr-o universitate din Louisiana, grad school

INSURANCE $ 479.00
INTL SVC 20.00
NONRES FEE 6200.00
REG.FIXED 45.00
GSO FEE 15.00
TUITION
2504.80
STU UNION
23.00
STU TECH 50.00
FIXED FEE 31.60
ENERGY FEE 55.00
STU HLTH
20.00
PARK/TRANS
50.00
RECREATION 20.00
STU UNION
55.00
ART MUSEUM
5.00
ACAD EXCEL 100.00
AUX IMPROV 15.00
BAND ASSOC
5.00
CHEERLEADR
4.00
OPER. FEE 51.00
LYCEUM 0.61
STU UNION
20.00
STU HLTH
5.00
S G A 7.50
ARTS FEE
7.00
AUX OPER 100.00
GRAD FEE
300.00
GRAD ENHAN 10.00
MAP
75.00
BLDG USE N 40.00
GENERAL FE 103.49

TOTAL:$ 10417.00

Observatii:
* Tuition fee (taxa de scolarizare propriu-zisa) este $2504, cam un sfert din costul total. Trei sferturi merg pentru altele...
* nu e vorba ca ai acces gratuit la gym ("facilitati"), ci toata lumea plateste gym-ul in mod obligatoriu, e optiunea ta daca ai chef sa mergi sau nu (RECREATION 20)
* platesti majoretele si banda care intoneaza imnul universitatii la meciurile sportive ale universitatii indiferent daca esti fan sau nu (CHEERLEADR $4 , BAND ASSOC $5)
* pentru o noua cladire inaugurata in campus (in cazul de fata STU UNION), contribuie toti studentii - in cazul de fata cu $ 23+55+20 sau mai pe scurt, $98.
* pt faptul ca esti graduate student, mai platesti o taxa de $300 (ca sa compenseze aparentele avantaje, cum ar fi oferte mai bune la campus housing sau meal plans). In plus, grad school din cadrul universitatii are propriul ei staff care vegheaza la grad students matters, iar ei trebuie platiti
* daca esti student international, din start platesti $6200 (NONRES FEE) in plus, ceea ce face suma totala mai mult decat dubla fata de ce plateste un rezident
* OIA (Office for International Affairs), care vegheaza interesele studentilor internationali, este si ea platita cu $20 de catre fiecare student strain (INTL SVC)
* $50 pt parcari si transport (PARK/TRANS) - se platesc parcarile staff-ului si ale unor studenti, in jurul cladirilor facultatii. Transportul cu shuttle se plateste si el, in cazul de fata acesta tine in principal cat pana la gym si inapoi in campus (2 km distanta). In majoritatea caminelor, parcarea este taxata separat. Mai sunt cateva cladiri speciale doar pt parcare, acelea de asemenea se platesc separat si sunt foarte scumpe.
* student health ($ 20+5 = $ 25) este o taxa pentru Student wellness center, si este independenta de asigurarea medicala obligatorie (INSURANCE $479)
* se plateste ART MUSEUM si ART FEE - de aceea intrarea la muzeul de langa universitate e gratuita, nu pt ca am fi noi studenti si deci speciali
* o serie de taxe cu nume care mai de care sa para diferite sunt: REG FIXED, FIXED FEE, OPER. FEE, LYCEUM, AUX OPER, MAP, GENERAL FEE. 
* plus altele pe care nu le-am enumerat, precum organizatiile de studenti GSO (graduate student organization) si SGA (student guvernment assoc), care sponsorizeaza studenti din cand in cand (cu sume mult sub ce au contribuit ca taxa de scolarizare, ce-i drept), ca sa se dea senzatia ca studentul este sprijinit.

Bineinteles ca totul este profitabil cand esti graduate assistent si ai bursa, insa nu vreau sa ma gandesc la ceilalti, prin ce costuri trec. Iar tinand cont ca in Louisiana este mai "ieftin", atunci imaginati-va cat de scump este in alte state. Nu degeaba tinerii americani muncesc de la 18 ani, cu munca pe primul loc si facultatea pe al doilea. Nu degeaba, ei nu mai sunt interesati de mastere sau doctorate. Toate micile avantaje pe care noi le percepem ca dovezi de superioritate ale invatamantului american, si care se rezuma la mai multa finantare, sunt de fapt platite la greu, obligatoriu, de studentii convinsi ca ei trebuie sa fie proud (Ragin' Cajuns in cazul de fata).

PS:
Pentru cei care nu sunt siguri,
* tuition = scolarizare
* gym = sala de sport, complex sportiv
* graduate student = masterand sau doctorand
* campus housing, dorms = cazare in campus
* meal plans = program culinar
* shuttle = autobuz de dimensiuni mai mici, maxi taxi