Modele de algoritmi în #Excel – Fibonacci numbers (13) – Partea 1 – FibFrog

De data aceasta mi-a dat greu! :) De aceea am decis să împart articolul despre numerele Fibonacci în două părți: una pentru problema FibFrog și a doua pentru Ladder. Pentru cititorii care au reușit să urmărească aceste serii complicate de programare funcțională în Excel, poate au observat că în articole sunt vehiculate permanent aceleași funcții, doar cu construcții și cazuri de utilizare diferite.

Pentru acest articol vă propun o metodă de rezolvare a problemei FibFrog de pe site-ul Codility: https://app.codility.com/programmers/lessons/13-fibonacci_numbers/. Trebuie știut că la data publicării acestui articol, nici un instrument de AI nu poate rezolva problema corect în Excel.

Să începem ca de obicei cu …

Un pic de teorie

Sunt foarte multe materiale de studiu pentru numerele Fibonacci pe Internet, ele stârnind interesul multor autori datorită aplicabilității pe scară largă a acestei secvențe.

Secvența Fibonacci este o succesiune recursivă definită de relația de recurență:

F(n)=F(n−1)+F(n−2)

unde F(0)=0 și F(1)=1. Aceasta are multiple aplicații în informatică, inclusiv în algoritmi de optimizare, structuri de date (heap Fibonacci), analiza complexității algoritmilor (divide et impera) și generarea de chei criptografice. Secvența este, de asemenea, utilizată în probleme de programare dinamică și este strâns legată de numărul de aur, având proprietăți logaritmice utile în analiza complexității algoritmilor.

Una din cele mai interesante metode de a calcula șirurile de numere este dată de formula:

Formula de calcul a numerelor Fibonacci. Sursa: https://codility.com/media/train/11-Fibonacci.pdf

Pentru a implementa această formulă în Excel utilizăm cu precădere funcția SQRT() pentru calculul radical și SEQUENCE() pentru valoarea lui n.

Rezolvare numere Fibonacci în Excel.

Având în vedere că în rezolvarea problemei am nevoie permanentă de această construcție introduc în acest articol funcția _fFibonacciSir() cu parametrul N pe care o voi reutiliza ulterior.

=LAMBDA(n; DROP((((1 + SQRT(5)) / 2) ^ SEQUENCE(n) - ((1 - SQRT(5)) / 2) ^ SEQUENCE(n)) / SQRT(5); 1))

Funcția DROP o utilizez pentru a elimina prima linie din șir, în așa fel încât să rămân cu o singură valoare 1 în rezultat.

În același timp o variație a acestei probleme este verificarea dacă un număr face parte dintr-un șir Fibonacci. Pentru a rezolva acest aspect introduc funcția _fFibonacciIS() cu paramentrul N, în care N este oricare număr natural pe care doresc să-l testez.

=LAMBDA(n; LET(_n; n; sir; _fFibonacciSir(_n+1); IF(ISNUMBER(MATCH(_n; sir;0)); TRUE; FALSE)))

Această funcție returnează valorile TRUE sau FALSE și reutilizează funcția _fFibonacciSir() pentru a calcula secvențele lui N. construcția _n1+1 din parametrul funcției este pentru a corecta problemele numerelor mici: 0, 1. În rezolvarea problemei 0 nu este considerat parte a acestei secvențe, chiar dacă în realitate el este primul număr. În versiunile viitoare voi aduce corecții dar în rezolvarea problemei am nevoie ca 0 să nu fie luat în seamă.

Un alt aspect important în aceste șiruri este raportul sau numărul de aur denumit φ (phi). Acest raport este întâlnit frecvent în lumea care ne înconjoară. Valoarea lui după al 14 element al șirului este 1,61803.

Problema FibFrog sau Salturile Fibonacci prin matrice

Fără a insista pe idioțeniile politice care spamează rețelele sociale, matricile în Excel au o putere deosebită de calcul vectorial și reprezintă o cale elegantă de a rezolva diferite tipuri de probleme.

FibFrog este o problemă care combină elemente din teoria numerelor (șirul Fibonacci) și grafuri. Scopul este să determini numărul minim de sărituri pe care o broască trebuie să le facă pentru a traversa un râu reprezentat de o serie de frunze și apă. Avem un șir binar A (de exemplu, [0, 1, 0, 0, 0, 1, 0]), unde: 1 reprezintă o frunză și 0 reprezintă apă. Broasca poate sări doar pe frunze (pe pozițiile marcate cu 1).

Dificultatea problemei este dată de restricția că lungimile săriturilor sunt restricționate la valor care aparțin șirului Fibonacci (F), adică {1, 2, 3, 5, 8, ...}.

Broasca începe la poziția -1 (deci înainte de prima frunză) și trebuie să ajungă după ultima poziție a șirului (pe N, unde N este lungimea șirului). Obiectivul principal este calcularea numărului minim de sărituri necesare pentru a traversa complet șirul de frunze și apă.

Dacă broasca nu poate traversa folosind sărituri din șirul Fibonacci, rezultatul trebuie să fie -1. Pentru a înțelege mai bine problema, cele mai bune explicații le-am găsit în sursa de mai jos.

Reprezentare grafică, captură video Youtube.

Sursa: Captură video Youtube: Fibonacci Frog Jump in Python and C++ Codility Coding Interview

Rezolvarea problemei în Excel pas cu pas.

Rezolvare problema FigFrog pas cu pas.

În B4 am descompus șirul dat în B2 cu funcția: =–TEXTSPLIT(B1;;”,”) apoi în A4 am calculat secvența de poziții a fiecărei frunze cu start de la 0: =SEQUENCE(ROWS(B4#);;0)

În D4 am calculat secvența de numere corespondent pozițiilor din A4 prin reutilizarea funcției _fBibonacciSir() definită anterior. M-am raportat la numărul maxim de poziții și am filtrat doar numerele care răspund criteriului: =LET(fib; _fFibonacciSir(MAX(A4#)); FILTER(fib; fib<=MAX(A4#)))

În E4 am calculat poziția frunzelor prin filtrarea vectorului Poz din A4 cu valorile 1 de pe vectorul A din B4. Ca să răspund cerințelor problemei, am adugat valoarea -1 ca fiind malul de start și valoarea maximă din poziții ca fiind malul destinație. =VSTACK(-1;FILTER(A4#;B4#=1);MAX(A4#)+1). În VSTACK() am adăugat 3 termen: valoarea -1, valorile filtrate din poziții și maxim poziții +1.

Cheia problemei este în H3 unde am proiectat o matrice RxC în care antet de coloane și linii sunt valorile frunzelor rezultate în E4. Formula utilizată este:

=LET(arr; E4#; _rows; ROWS(arr);
     diff; MAKEARRAY(_rows;_rows; LAMBDA(r;c; IF(c>=r; "";INDEX(arr;r)-INDEX(arr;c))));
     fScanCol; LAMBDA(c; MAP(c; LAMBDA(v; IF(_fFibonacciIS(v); v; ""))));
     fin; VSTACK(HSTACK(""; TRANSPOSE(arr));HSTACK(arr; fScanCol(diff)));
fin)

în care un rol deosebit îl are funcția recursivă fScanCol care apelează funcția creată anteriror: _fFibonacciIS(). Rolul său este de a păsta în matrice doar valorile din șirul Fibonacci.

Variabila diff este utilizată pentru a genera matricea ințială prin care scad valorile corespondente din vectorul arr pe toate combinațiile posibile. Ca să pot afișa antetele matricei (linii și coloane) am folosit o combinație de VSTACK() cu HSTACK().

A doua cheie este în H13 unde am extras toate combinațiile optime de salturi.

=LET(tab;H3#;_hr;CHOOSEROWS(tab;1);_hc;CHOOSECOLS(tab;1);
     tabi;SCAN(0;_hc; LAMBDA(a;v;LET(colc;XMATCH(v;_hr;0);
                                 poz;XMATCH(1;--ISNUMBER(CHOOSECOLS(tab;colc));0;-1);
                                 valc;INDEX(_hr;poz);
                                 return;CONCAT(v;";";valc);
                                 return)));
     valori;DROP(DROP(tabi;1);-1);
     splited; --TEXTSPLIT(TEXTJOIN("|";;valori);";";"|");
     splited)

Chiar dacă pare complicată, formula nu face decât să aducă valorile din antetul de linie și coloană specifice valorilor rezultat de pe fiecare coloană. Cheia este valoarea -1 din variabila poz care caută valoarea specifică de jos în sus pe vector-ul _hc care este de fapt prima coloană din matrice. colc este un artificiu care rulează scan-ul în matrice de fapt, coloană cu coloană. Nu aveam cum să rulez funcția BYCOL() pentru că nu o pot integra în SCAN(). Ca să păstrez doar pozițiile în forma start;final am utilizat două funcții DROP() prin care am eliminat prima și ultima linie din rezultat. Ca să pot vedea valorile tabelar am implemententat variabila splited care folosește artificiul de splitare linie cu linie.

Rezultatul final al funcției din H13 este identic cu cel din K13, unde am făcut de fapt reunificarea pentru a putea parcurge aceste valori cu scopul de a le extrage pe cele care îndeplinesc criteriul cerut.

Aparent cea mai grea chestie a fost să găsesc calea de a scoate doar perechile corecte de salturi. Pentru asta am încercat diferite abordări dar în final am ajuns tot la un SCAN() pe care l-am implementat în N13:

=SCAN(""; K13#; LAMBDA(a;v; IFERROR(IF(AND(a=""; ISNUMBER(TAKE(--TEXTSPLIT(v;";");;-1)));v; IF(TAKE(--TEXTSPLIT(v;";");;1)=TAKE(--TEXTSPLIT(a;";");;-1);v;a));-1)))

În această formulă parcurg vectorul K13# și descompun fiecare valoare F1-F2 pentru a o compara cu cele anterioare. Acumulatorul (a) este reprezentat permanent de combinația câștigătoare anterioară. Dacă valoarea de pe prima combinație nu este un număr atunci rezultatul funcției este -1 așa cum cere problema.

Toate funcțiile și calculele intermediare integrate vor duce la formula finală:

=LET(sir; B1; _A; --TEXTSPLIT(sir;;","); poz; SEQUENCE(ROWS(_A);;0);

     fReqFS; LAMBDA(n; DROP((((1 + SQRT(5)) / 2) ^ SEQUENCE(n) - ((1 - SQRT(5)) / 2) ^ SEQUENCE(n)) / SQRT(5); 1));
     fReqFIS; LAMBDA(n; LET(_n; n; sir; fReqFS(_n+1); IF(ISNUMBER(MATCH(_n; sir;0)); TRUE; FALSE)));

     fibo; LET(fib; fReqFS(MAX(poz)); FILTER(fib; fib<=MAX(poz)));

frunze; VSTACK(-1;FILTER(poz;_A=1);MAX(poz)+1);
matrix; LET(arr; frunze; _rows; ROWS(arr);
     diff; MAKEARRAY(_rows;_rows; LAMBDA(r;c; IF(c>=r; "";INDEX(arr;r)-INDEX(arr;c))));
     fScanCol; LAMBDA(c; MAP(c; LAMBDA(v; IF(fReqFIS(v); v; ""))));
     fin; VSTACK(HSTACK(""; TRANSPOSE(arr));HSTACK(arr; fScanCol(diff)));
fin);

perechi; LET(tab;matrix;_hr;CHOOSEROWS(tab;1);_hc;CHOOSECOLS(tab;1);
     tabi;SCAN(0;_hc; LAMBDA(a;v;LET(colc;XMATCH(v;_hr;0);
                                 poz;XMATCH(1;--ISNUMBER(CHOOSECOLS(tab;colc));0;-1);
                                 valc;INDEX(_hr;poz);
                                 return;CONCAT(v;";";valc);
                                 return)));
     valori;DROP(DROP(tabi;1);-1);

     valori);

pok; SCAN(""; perechi; LAMBDA(a;v; IFERROR(IF(AND(a=""; ISNUMBER(TAKE(--TEXTSPLIT(v;";");;-1)));v; IF(TAKE(--TEXTSPLIT(v;";");;1)=TAKE(--TEXTSPLIT(a;";");;-1);v;a));-1)));
unice; UNIQUE(pok);
"Nr perechi: " & ROWS(unice)& ": "&TEXTJOIN(" | ";;unice)
)

în care nu am mai folosit funcțiile definite la început ci le-am introdus ca recursive în această formulă pentru a evita erorile de a nu avea funcțiile definite / salvate.

Cam atât pentru această parte. Voi reveni cu un video pas cu pas pentru explicații suplimentare și cu partea a doua în viitor.

Aici varianta video pas cu pas.

Sper că v-a plăcut! :)

Modele de algoritmi în #Excel – Euclidean algorithm (12)

A trecut ceva vreme de când nu am mai scris pe această temă, mai concret câteva luni de când am scris propunerea de rezolvare pentru Ciurul lui Eratostene. Modele de algoritmi în #Excel – Sieve of Eratosthenes (11)

În acest articol voi propune o rezolvare pentru problemele de pe site-ul Codility de la această adresă: https://app.codility.com/programmers/lessons/12-euclidean_algorithm/

Având în vedere că nu am prea multă teorie nu mai facem o secțiune separată pentru această componentă. Amintim doar că Algoritmul Euclidean este utilizat pentru calcularea celui mai mare divizor comun (GCD) al două numere printr-un proces iterativ sau recursiv de împărțiri succesive; însă, în combinație cu alte metode, poate ajuta la testarea primalității sau la factorizarea numerelor. Detalii foarte faine despre algoritm găsiți la adresa: https://ro.wikipedia.org/wiki/Algoritmul_lui_Euclid

Problema ChocolatesByNumbers

Ai N bucăți de ciocolată aranjate într-un cerc. Numeri aceste bucăți de la 0 la N-1. Începi să mănânci bucăți de ciocolată de la bucata numerotată 0. După ce mănânci o bucată, sari peste M bucăți și mănânci următoarea (adică sari la bucata 0 + M mod N, și tot așa).

Întrebarea este: Câte bucăți de ciocolată vei mânca în total până când te întorci la o bucată pe care ai mai mâncat-o înainte?

Parametrii:

  • N: Numărul total de bucăți de ciocolată (N ≥ 1).
  • M: Numărul de bucăți peste care sari după ce mănânci o bucată (M ≥ 1).

Exemplu:

Să considerăm cazul în care:

  • N = 10 (ciocolată aranjată în cerc cu 10 bucăți)
  • M = 4 (sari peste 4 bucăți după ce mănânci una)

Secvența bucăților mâncate va fi: 0 → 4 → 8 → 2 → 6 → 0. Vei mânca 5 bucăți de ciocolată înainte să te întorci la bucata 0.

Observații:

  • Vei mânca fiecare bucățică o singură dată înainte de a reveni la o bucățică deja consumată.
  • Problema are o legătură cu cel mai mare divizor comun (GCD) dintre N și M.

Rezolvare în Excel

Pas cu pas în rezolvarea problemei, concret începem de la C5 care calculează restul împărțirii numărului N la secvența de numere componente. În felul acesta pentru valorile 0 identificăm divizorii. Procedăm la fel pentru numărul M în celula D5. Ca să aflăm divizorii filtrăm secvența de numere componente pentru toate valorile 0. În felul acesta obținem în E5 si F5 divizorii celor două numere. În G5 căutăm cel mai mare număr comun din cele două șiruri după care indexăm lista divizorilor cu valoarea rezultat și obținem astfel valoarea 2. În context în I5 calculăm numărul N împărțit la cel mai mare divizor comun.

Nu mi-a luat foarte mult timp rezolvarea, dar am fost oarecum dezamagit de mine când am aflat că în Excel există built-in funcția GCD() care returnează exact valoarea mea din H5. În același timp funcția GCD() permite calcularea divizorului comun al mai multor numere. Toată problema se rezumă acum la formula:

=A2/GCD(A2;B2)

De multe ori spun că dacă nu suntem suficient de bine pregătiți riscăm să pierdem foarte mult timp reinventând roata… în cazul acesta roata pătrețelelor de ciocolată. :)

Exemple practice?

Problema se aplică cu succes rezolvării tuturor problemelor ciclice: planificarea reviziei unor mașini de pe un lanț de producție sau în vânzări, planificarea campaniilor de marketing sau promoțiilor specifice.

Problema CommonPrimeDivisors

Această problemă face parte tot din categoria problemelor care implică divizori comuni și numere prime. Scopul acestei probleme este de a determina dacă două numere împărtășesc aceiași divizori primi.

Avem doi vectori de numere întregi, A și B, ambele de lungime N. Pentru fiecare pereche de numere A[i] și B[i], trebuie să verifică, dacă aceștia au exact aceiași divizori primi. Dacă da, se returnează valoarea 1 pentru acea pereche, iar dacă nu returnăm valoarea 0. Rezultatul final este numărul total de perechi de numere care au aceiași divizori primi.

Parametrii:

  • A și B: doi vectori de lungime N cu numere întregi pozitive.

Exemplu:

Să presupunem că:

  • A = [15, 10, 9]
  • B = [75, 30, 5]

Analiza pentru fiecare pereche:

  1. Pentru A[0] = 15 și B[0] = 75:
    • Divizorii primi ai lui 15 sunt 3 și 5.
    • Divizorii primi ai lui 75 sunt 3 și 5.
    • Cei doi au exact aceiași divizori primi → răspunsul este 1.
  2. Pentru A[1] = 10 și B[1] = 30:
    • Divizorii primi ai lui 10 sunt 2 și 5.
    • Divizorii primi ai lui 30 sunt 2, 3 și 5.
    • Aceștia nu au exact aceiași divizori primi → răspunsul este 0.
  3. Pentru A[2] = 9 și B[2] = 5:
    • Divizorii primi ai lui 9 sunt 3.
    • Divizorii primi ai lui 5 sunt 5.
    • Nu împărtășesc aceiași divizori primi → răspunsul este 0.

Rezultatul final al problemei este 1 pentru vă avem o singură pereche care împarte aceeași divizori primi.

Rezolvare în Excel

Ca să putem rezolva un pic mai simplu și mai rapid propun două funcții noi, ca rezultat al cercetărilor din articolele anterioare.

Funcția fPrim() – calculează dacă un număr este prim sau compus. Pentru managementul numelor și funcțiilor eu folosesc Excel Labs, un proiect Microsoft Garage.

Textul funcției:

=LAMBDA(n; LET(
    a; --n;
    radical; SQRT(a);
    sec; SEQUENCE(INT(radical) - 1; ; 2);
    unmod; a / sec - INT(a / sec);
    TAKE(IF(FILTER(unmod; unmod = 0; "x") = "x"; "Prim"; "Compus"); 1; 1)
))

Funcția are parametrul n care este numărul pe care dorim să-l verificăm. După care se calculează radicalul din acest număr și se realizează un secvență din 2 în 2 a valorilor rezultat. După care calculăm un fel de mod în variabila unmod prin care aflăm restul pe secvențe întregi.

Funcția fPrime() – care determină toate valorile prime de la 2 până la un anumit număr specificat.

=LAMBDA(x; LET(_n; x; toate; VSTACK({2;3};SEQUENCE(_n/2;;5;2));
ftPrim; LAMBDA(n; LET(
    a; --n;
    radical; SQRT(a);
    sec; SEQUENCE(INT(radical) - 1; ; 2);
    unmod; a / sec - INT(a / sec);
    TAKE(IF(FILTER(unmod; unmod = 0; "x") = "x"; "Prim"; "Compus"); 1; 1)
));
test; MAP(toate; LAMBDA(v; IF(v<5;"Prim";ftPrim(v))));
prime; FILTER(toate;test="Prim"); prime))

în această funcție nu am reiterat fPrime() descris anterior ci am făcut o variabilă recursivă ftPrim care înglobează textul funcției. În această funcție pornesc secvența generată de la 5 la care concatenez valorile 2 și 3 cu ajutorul funcției VSTACK{} în așa fel încât să am șirul complet de la 2 încolo.

Rezolvarea problemei în Excel

Rezolvarea aceasta este pentru prima pereche de numere.

În C2 determinăm cel mai mare divizor comun, apoi în D2 calculăm restul împărțirii la cele două numere. În F2 și I2 aplicăm funcția fPrime() descrisă anterior pentru a calcula toate numerele prime de la 2 la numărul N și M, iar pentru fiecare din ele calculăm restul împărțirii numărului N și M la numerele prime rezultate, obținând astfel valorile 0 pentru divizori, după care filtrez cei doi vectori rezultat în H2 și K2.

La final pentru perechea 1 dată aplic funcția F.TEST() descrisă în articolul: Excel – Compararea a două coloane cu F.TEST()

Dacă cele două rezultate sunt egale obțin valoarea 1. Dacă nu obțin o valoare între 0 și 1. Pentru valorile date tabelul rezultat este format din valorile. Ceea ce înseamnă că numărând valorile 1 vom obține o singură valore.

1
0,598701901
0,409665529

Funcția finală integrată pentru a rezolva această problemă:

=LET(tab; A2:B4;
    rez; BYROW(tab; LAMBDA(r; LET(_n; TAKE(r;;1); _m; TAKE(r;;-1); vGCD; GCD(_n;_m); vMod; MOD(_m;_n);
      PrimeN; fPrime(_n); ModPrimeN; MOD(_n; PrimeN); DPN; FILTER(PrimeN; ModPrimeN=0);
      PrimeM; fPrime(_m); ModPrimeM; MOD(_m; PrimeM); DPM; FILTER(PrimeM; ModPrimeM=0);
      F.TEST(DPN; DPM))));
SUM(--(rez=1))
)

Cam asta ar fi pentru astăzi. Sper să fie util cuiva!

Modele de algoritmi in #Excel – Sorting (6)

După o perioadă de pauză în care au fost multe de făcut, dar și datorită unor probleme de optimizare a codurilor și formulelor, reiau seria de articole din categoria algoritmilor clasici cu propuneri de implementare / rezolvare în Excel.

În articolul de astăzi voi trata colecția de probleme Sorting de pe Codility: https://app.codility.com/programmers/lessons/6-sorting/

Un pic de teorie

Algoritmii de sortare sunt proceduri care rearanjează elementele unei liste (sau altor structuri de date) într-o anumită ordine, de obicei crescătoare sau descrescătoare. Acești algoritmi sunt esențiali în informatică și matematică datorită utilizării lor în diverse aplicații și probleme. Cei mai cunoscuți algoritmi de sortare, la nivel general sunt:

  1. Bubble Sort – Compara și interschimbă elementele adiacente, trecând repetat prin listă până când aceasta este sortată. Chiar dacă este simplu și ușor de înțeles el este ineficient pentru listele mari.
  2. Insertion Sort – Inserează fiecare element din lista nesortată în poziția sa corectă din lista sortată cu aceleași avantaje și dezavantaje pentru listele mari ca Bubble sort.
  3. Selection Sort – Găsește elementul minim din lista nesortată și îl pune la început. Repetă pentru fiecare element.
  4. Merge Sort – Împarte lista în două jumătăți, sortează fiecare jumătate și apoi le îmbină într-o listă sortată, un pic mai stabil pentru liste mari.
  5. Quick Sort – Alege un element pivot, împarte lista în subliste cu elemente mai mici și mai mari decât pivotul și sortează recursiv sublistele. Este unul din celele mai eficiente pentru liste mari dar performanța este una redusă.
  6. Heap Sort – Construiește un heap maxim din listă și apoi extrage elementele unul câte unul pentru a obține lista sortată.
  7. Counting Sort – Numără numărul de apariții ale fiecărui element și folosește aceste informații pentru a construi lista sortată.
  8. Radix Sort – Sortează elementele în funcție de cifrele lor, utilizând un sortare stabilă la fiecare pas.
  9. Bucket Sort – Împarte lista în mai multe segmente, sortează fiecare segment individual și apoi le îmbină.

În Excel, în versiunea clasică există doar operațiuni pentru sortare, avantajul major al Excelului modern (365) este faptul că transformă multe operațiuni în funcții. De exemplu avem funcția SORT() sau SORTBY() utilizate pentru sortarea vectorilor. De asemenea, foarte utile sunt funcțiile: UNIQUE() pentru determinarea valorilor unice dintr-un interval precum și funcțiile LARGE() prin intermediul căreia putem identifica cele mai mari n valori dintr-un vector sau funcția SMALL() care ne permite identificarea celor mai mici n valori dintr-un vector.

În imagine câteva detalii legate de utilizarea LARGE și SMALL

Funcțiile LARGE și SMALL în Excel.

Ca să putem selecta mai multe rânduri dintr-un vector putem utiliza funcția ROW() sau SEQUENCE(). A nu neglija funcția INDEX() care încă este destul de puternică și des utilizată pentru a axtrage valori de pe diferite poziții dintr-un vector.

Problema Distinct

În problema Distinct ni se solicită să determinăm numărul de elemente unice dintr-un vector de valori.

În rezolvarea problemei ar trebui să determinăm fiecare număr unic, să facem un COUNTIF() după fiecare număr pe vectorul de valori după care să însumăm valorile intermediare.

Rezolvarea în Excel este în schimb mult mai simplă:

Așa cum explicam în partea de teorie funcția UNIQUE() îmi determină valorile unice, iar cu COUNT() determin numărul lor. În cazul în care nu avem Excel 365 putem merge pe abordarea din metoda clasică cu însumarea din funcția specificată. Ca să determinăm care sunt exact numerele unice, o tehnică de multe ori necesară în verificarea și validarea seturilor de date atunci putem utiliza combinația de SORT() cu UNIQUE().

Problema MaxProductOfThree

Aceasta este o problemă destul de interesantă a cărei cerință este să determini produsul maxim a trei valori dintr-o listă dată, în care intervalul de valori este negativ și pozitiv. Ca metodă de rezolvare, ar trebui sortat crescător intervalul de valori după care se înmulțesc cele mai mari 3 numere.

Propunere de rezolvare inițială:

Funcția LARGE() din E2 și E3, preia automat cele mai mari 3 numere din șir fără să fie nevoie de sortare. După care aplicăm funcția PRODUCT() (un fel de SUM() mai puțin utilizat) pentru a înmulți cele 3 numere rezultat. Dezavantajul acestei abordări este că nu pot extrage din rezultat care sunt pozițiile numerelor din RANK decât dacă fac o nouă funcție de căutare pe intervalul de valori.

În zona G:J am încercat o abordare manuală de generare a tuturor combinațiilor posibile unice de la 1 la 6. În cazul în care ai mai multe numere această abordare devine complet ineficientă pentru a fie executată manual. Vezi zona N:O în care sunt explicate în funcție de numărul de elemente, numărul de combinații unice, dar și celelalte combinații, ridicarea la a doua și a treia care determină numărul maxim de combinații pe care le putem obține din numărul de elemente dat.

În J2 am introdus un clasic INDEX() care îmi caută în funcție de tripletă valorile pe vector și le înmulțește între ele, valoarea finală fiind 60 în imputul dat.

Doar că matematica are detaliile ei, pe care dacă nu le verificăm bine riscăm să cădem în capcana datelor de intrare. Se face că în aritmetică, două numere negative când se înmulțesc, obținem un număr pozitiv. Dar numerele negative nu sunt tratate corect cu funcția LARGE() ceea ce înseamnă că ar trebui să complicăm formulele inițiale din E2 ca să conțină și un SMALL() după care să facem un maxim între produsul celor mai mici două numere cu cel mai mare număr, cu produsul celor mai mari 3 numere.

Exemplu de schimbare date de intrare cu două valori mari negative.

În imagine se observă că modelul manual generează valori corecte, pe când modelul simplu din E2 intoarce doar produsul celor mai mari 3 numere, în cazul meu pozitive.

Având în vedere că o generare manuală a unui tabel de triplete nu este acceptabil de la un anumit număr de valori dintr-un vector, am procedat la implementarea unei funcții separate:

Funcție rezolvare problemă MacProductofThree

În care funcția concretă este:

=LET(vector; A2:A7;
     nr; ROWS(vector);
     matrix; MAKEARRAY(nr^3; 3; LAMBDA(i;j; IF(j = 1; INT((i-1)/nr^2)+1; IF(j = 2; MOD(INT((i-1)/nr); nr)+1; MOD(i-1; nr)+1))));
     fReq; LAMBDA(x; AND(INDEX(x;1)<INDEX(x;2);INDEX(x;2)<INDEX(x;3)));
     verif; BYROW(matrix; LAMBDA(r; fReq(r )));
     tabi; HSTACK(matrix; verif);
     unics; FILTER(tabi; TAKE(tabi;;-1)=TRUE);
     unicsm; CHOOSECOLS(unics;1;2;3);
     fReqv; LAMBDA(x; INDEX(vector;INDEX(x;1))*INDEX(vector;INDEX(x;2))*INDEX(vector;INDEX(x;3)));
     vals; BYROW(unicsm; LAMBDA(r; fReqv(r )));
MAX(vals))

în care variabilele matrix, verif, tabi, unics, unicsm le utilizez doar pentru a crea în mod dinamic tabelul de triplete constând în combinațiile unice pe care le pot lua pozițiile numerelor. Acest tabel îmi este necesar pentru a face ulterior indexul din funcția recursivă fReqv. Funcția o folosesc ulterior în variabila vals pentru a putea să calculez pe tabelul unicsm (tripletele unice) linie cu linie cu BYROW().

La final afișez MAX(vals) pentru a determina care este valoarea maximă a produsului a trei numere din șir.

Mi-a fost destul de greu să ajung la formula din matrix. Clar numărul de combinații optime este dat de numărul de elemente din vector, la numărul de elemente combinate. În cazul nostru 3. În schimb pentru a putea determina toate combinațiile posibile, singura metodă pe care am găsit-o este să generez un tabel care are numărul de linii egal cu fiecare număr din șir de câte poziții poate lua pe 3 coloane. De exemplu dacă vectorul A este format din valorile 1, 3, 8, atunci valoarea matrix va cuprinde toate combinațiile lui 1 cu el însăși și cu cu celelalte valori. Exemplificarea din imagine este mult mai concludentă:

Valoarea intermediară a matrix.

Observăm din imagine că funcția de generare a acestei matrici cuprinde toate valorile posibile. Matricea este doar pentru căutare ulterioară pe A, prin combinația poziției numerelor. Fiecare număr apare de 9 ori pe prima coloană reprezentând numărul de element la puterea a doua. Scopul final este să identific toate combinațiile crescătoare și să elimin duplicatele prin funcțiile următoare. Tabelul final de căutare fiind în variabila unicsm care are un număr de linii egal cu numărul de combinații: 3 combinat cu 3 = 1.

Variabila unicsm.

Acest tip de problemă are o mare aplicabilitate în diverse domenii de activitate: analiză financiară, optimizarea producției, logistică, ect cu scopul de a maximiza profitul rezultat în urma activității prin combinarea elementelor optime.

De exemplu, dacă am avea un portofoliu de acțiuni cu diferite randamente, putem calcula combinația maximă dintre ele prin utilizarea funcției propuse:

Model de problemă analiză portofoliu de acțiuni cu MaxProdofThree

În funcția de mai sus am înlocuit MAX(vals) cu FILTER(HSTACK(unicsm;vals);vals=MAX(vals)) în așa fel încât să păstrez ca rezultat valorile combinațiilor maxime ca produs.

Un artificiu foarte util pentru a nu ne trezi cu surprize pentru valorile negative, este acela de a transforma procentul în valoare prin adunarea valorii 1 la randament. De exemplu pentru prima valoare formula utilizată este: =1+VALUE(C29). Asta îmi permite să exclud valorile negative din combinație și să păstrez pentru acest caz particular doar valorile pozitive.

Problema Triangle

Problema Triangle este oarecum asemănătoare cu cea anterioară doar că de data aceasta rezultatul nu mai este maximul numerelor ci un rezultat de constatare, dacă 3 numere dintr-un șir pot forma un triunghi. Nu ne referim la cazuri specifice de triunghiuri isoscel, dreptunghic sau echilateral ci doar la cazul în care numerele prezentate (P, Q, R) respectă următoarele reguli:

0<=P<Q<R<N
P+Q>R
Q+R>P
R+P>Q

adică suma oricăror două numere trebuie să fie mai mare decât un al treilea. N reprezintă numărul de elemente din vectorul de analiză. Ca o particularitate a problemei, toate numerele din A trebuie să fie diferite între ele.

Soluția propusă de mine pornește de la funcția de la problema MaxProductOfThree.

Problema Triangle.

Rezultatul este numărul de triplete posibile și care este aceasta. Codul funcției prezentat mai jos:

=LET(vector; A2:A7;
     nr; ROWS(vector);
     matrix; MAKEARRAY(nr^3; 3; LAMBDA(i;j; IF(j = 1; INT((i-1)/nr^2)+1; IF(j = 2; MOD(INT((i-1)/nr); nr)+1; MOD(i-1; nr)+1))));
     fReq; LAMBDA(x; AND(INDEX(x;1)<>INDEX(x;2);INDEX(x;2)<>INDEX(x;3);INDEX(x;1)<>INDEX(x;3)));
     verif; BYROW(matrix; LAMBDA(r; fReq(r )));
     tabi; HSTACK(matrix; verif);
     unics; FILTER(tabi; TAKE(tabi;;-1)=TRUE);
     unicsm; CHOOSECOLS(unics;1;2;3);
     fReqv; LAMBDA(x; CONCAT(INDEX(vector;INDEX(x;1));";";INDEX(vector;INDEX(x;2));";";INDEX(vector;INDEX(x;3))));
     vals; BYROW(unicsm; LAMBDA(r; fReqv(r )));
     Split; --TEXTSPLIT(TEXTJOIN("|";1;vals);";";"|");
     fverif; LAMBDA(x;AND(INDEX(x;1)+INDEX(x;2)>INDEX(x;3);
                     INDEX(x;1)+INDEX(x;3)>INDEX(x;2);
                     INDEX(x;2)+INDEX(x;3)>INDEX(x;1)));
     verift; BYROW(Split; LAMBDA(r; fverif( r)));
     tabf; HSTACK(Split; verift );
     triplef; FILTER(tabf; TAKE(tabf;;-1)=TRUE;"0");
     rezfin; IF(ROWS(triplef)>1;"Rezultat: 1 - Tripleta:" & TEXTJOIN(";";TRUE; XMATCH(TAKE(triplef;1;3);vector)-1);"Rezultat: 0");
     rezfin)

În care tehnica de a obține toate combinațiile unice posibile de poziții din șir este dată de variabilele și funcțiile recursive până la vals. Artificiul esențial în această funcție, este acela de a împărți valorile în LET() prin unificarea inițială a lor din combinația funcțiilor din variabila Split. Această tehnică am învățat-o de la Diarmuid Early fost campion mondial la Excel.

Cheia problemei este în funcția recursivă fverif care este utilizată în BYROW-ul de la verift pentru a compara sumele valorilor din tabelul Split prin adunarea valorilor indexate de pe cele trei coloane. AND()-ul va returna TRUE sau FALSE pe care le unific în tabf cu HSTACK. Ulterior filtrez în triplef doar valorile TRUE din tabf.

Identificarea tringhiurilor dreptunghice

Cred că sunt puțini cei care nu au spus niciodată că anumite formule din matematică nu le folosește la nimic. :) Una din experiențele mele în acest sens cu copiii mei a fost să le demonstrez în nenumărate rânduri utilizatea Teoremei lui Pitagora. Nu am reușit niciodată! :)

Pe scurt în această teoremă, dacă A^2+B^2=C^2 înseamnă că triunghiul este unul dreptunghic (unghi de 90 de grade pentru cei care au uitat).

Ca să identificăm dintr-un șir de valori, care oricare trei numere pot forma un triunghi dreptunghic, am extins problema Triangle descrisă anterior și am modificat funcția recursivă fverif pentru a răspunde noii condiții de sumă a pătratelor.

Teorema lui Pitagora în Excel.

În zona GHI identificăm numerele din șirul A care pot forma tringhiuri dreptunghice.

3^2+4^2=9+16=25=5^2

5^2+12^=13^2

Funcția utilizată în acest caz este:

=LET(vector; A2:A7;
     nr; ROWS(vector);
     matrix; MAKEARRAY(nr^3; 3; LAMBDA(i;j; IF(j = 1; INT((i-1)/nr^2)+1; IF(j = 2; MOD(INT((i-1)/nr); nr)+1; MOD(i-1; nr)+1))));
     fReq; LAMBDA(x; AND(INDEX(x;1)<>INDEX(x;2);INDEX(x;2)<>INDEX(x;3);INDEX(x;1)<>INDEX(x;3)));
     verif; BYROW(matrix; LAMBDA(r; fReq(r )));
     tabi; HSTACK(matrix; verif);
     unics; FILTER(tabi; TAKE(tabi;;-1)=TRUE);
     unicsm; CHOOSECOLS(unics;1;2;3);
     fReqv; LAMBDA(x; CONCAT(INDEX(vector;INDEX(x;1));";";INDEX(vector;INDEX(x;2));";";INDEX(vector;INDEX(x;3))));
     vals; BYROW(unicsm; LAMBDA(r; fReqv(r )));
     Split; --TEXTSPLIT(TEXTJOIN("|";1;vals);";";"|");
     fverif; LAMBDA(x;OR(INDEX(x;1)^2+INDEX(x;2)^2=INDEX(x;3)^2;
                     INDEX(x;1)^2+INDEX(x;3)^2=INDEX(x;2)^2;
                     INDEX(x;2)^2+INDEX(x;3)^2=INDEX(x;1)^2));
     verift; BYROW(Split; LAMBDA(r; fverif( r)));
     tabf; HSTACK(Split; verift );
     triplef; FILTER(tabf; TAKE(tabf;;-1)=TRUE;"0");
     fSort; LAMBDA(row;TEXTJOIN(";";TRUE;SORT(TRANSPOSE(row))));
     tabs; BYROW(triplef; LAMBDA(r; fSort(r)));
     pita; TEXTSPLIT(TEXTJOIN("|";1;UNIQUE(tabs));";";"|");
     pita)

În care recunoașteți codul de la problema Triangle cu modificarea specificată pentru fverif, doar că de data aceasta am folosit funcția OR() pentru a identifica oricare din combinațiile dintre cele 3 numere. De asemenea, specific acestei probleme, ca să pot face sortare în LET cu BYROW trebuie să folosim artificiul amintit anterior cu splitarea după join. Funcția recursivă fSort îmi crează un tabel cu toate valorile sortate crescător dar ca să le pot trata am nevoie de ea în BYROW().

Înainte de splitare, variabila tabs are următoarea formă:

Forma intermediară a soluției în variabila tabs.

În cazul în care șirul de numere nu are valori care să îndeplinească condiția de triunghi dreptunghic, atunci rezultatul returnat va fi zero, ca parte a variabilei triplef.

Problema Triangle poate fi aplicată în diferite domenii de activitate cu efect de optimizare a costurilor, prețurilor sau evaluare a diferitelor riscuri. În analiza pieței imobiliare, algoritmul Triangle poate fi folosit pentru a evalua relațiile dintre prețurile proprietăților în trei locații diferite. De exemplu, dacă prețurile locuințelor în trei cartiere formează un triunghi, se poate evalua dacă aceste prețuri sunt în echilibru sau dacă există anomalii care ar putea sugera oportunități de investiție sau riscuri. În piețele financiare, portofoliile de investiții sunt adesea analizate pentru a evalua riscul și rentabilitatea. Folosind un algoritm Triangle, se pot evalua trei acțiuni sau active diferite pentru a determina dacă formarea lor (în termeni de valori sau randamente) formează un „triunghi” de risc echilibrat. Un portofoliu ideal ar trebui să aibă un echilibru între risc și rentabilitate, iar algoritmul poate ajuta la identificarea combinațiilor care nu sunt optim echilibrate.

În domeniul logisticii, determinarea rutelor optime pentru transportul mărfurilor poate beneficia de algoritmi de tip triunghi. De exemplu, dacă trebuie să transportați mărfuri între trei depozite, algoritmul poate ajuta la determinarea rutei optime care minimizează costurile și timpul, verificând dacă distanțele dintre depozite formează un triunghi optim din punct de vedere logistic. În managementul resurselor umane, performanța angajaților poate fi analizată în grupuri de câte trei pentru a vedea dacă există un echilibru în echipă. Dacă performanța a trei angajați formează un „triunghi”, managerii pot evalua dacă acest triunghi este echilibrat sau dacă există nevoi de formare suplimentară pentru a echilibra performanțele.

Problema NumberOfDiscIntersections

Problema NumberOfDiscIntersections presupune să găsești numărul de perechi de discuri care se intersectează într-un plan 1D. Fiecare disc este definit prin centrul său (o poziție pe axa x) și raza sa (o lungime care se extinde la stânga și la dreapta de la centru).

Discurile sunt date sub forma unui array A, unde fiecare element A[i] reprezintă raza discului centrat în punctul i.

Trebuie să recunosc că problema mi-a dat bătăi de cap, pentru că nu am înțeles de la început cerința. Numărând intersecțiile de pe reprezentarea grafică, luând în calcul și punctele de tangență, tot nu reușeam să ajung la valoarea 11 propusă de autori.

Propunere de rezolvare problema NumberofDiscIntersections.

Consultând diferite site-uri specializate am identificat faptul că în această problemă cei de la Codility au luat în calcul de fapt componența fie și parțială a unui disc într-un alt disc. De exemplu discul 5 nu conține nici un alt disc, dar el este numărat ca apartenență atât în discul 1 (cel mai mare cât și în discul 4.

În momentul în care înțelegi că nu este vorba de intersecția liniilor între ele, treaba devine relativ simplă. Eu am rezolvat cu un SUMPRODUCT() comparând valorile de start cu valorile de final și invers, adăugând în ecuație poziția în calcul.

Ca să ajung la rezultat a trebuit să transform comparația fiecărui element cu celălalt element într-o matrice de elemente, artificiul aici fiind funcția TRANSPOSE().

Pasii de rezolvare a problemei.

Partea de secvență o folosesc pentru a limita valorile calculate pentru intersecție doar pentru partea în care linia curentă este mai mică decât transpusul său.

După multe chinuri în înțelegerea problemei am ajuns la următoarea funcție:

=LET(sir;B1;
    raza;--TEXTSPLIT(sir;;",");
    centru;SEQUENCE(ROWS(raza);1;0;1);
    start;centru-raza;
    final;centru+raza;
    tabi;HSTACK(start;final);
    tFinal;TRANSPOSE(final);
    tStart;TRANSPOSE(start);
    intersections;BYROW(tabi;LAMBDA(_;
                        SUMPRODUCT(
                           --(start<=tFinal);
                           --(final>=tStart);
                           --(SEQUENCE(ROWS(raza))<TRANSPOSE(SEQUENCE(ROWS(raza))))
                          )
                  ));
MAX(intersections))

Din cauză că formula se execută pe un tabel, obțin valoarea 11 pentru fiecare linie din raza. Atunci la final am aplicat funcția MAX() pentru a obține o singură valoare.

În funcția prezentată nu am mai folosit un recursiv, ci am folosit o funcție LAMBDA() cu paramentru null (_) pentru că am input din variabila tabi, ci o folosesc doar pentru a parcuge linie cu linie tabelul inițial.

Nu sunt foarte mulțumit de ce a ieșit, din cauză că am lucrat prea mult la ea, iar unul din princiile mele de lucru în Excel este: Dacă ceva îți ia prea mult timp pentru a găsi o soluție, înseamnă că ceva nu faci bine.

Posibil să existe mai multe metode de aplicare a acestei probleme, dar dacă ne referim la intersecții, cel mai probabil problema ar trebui să ia în calcul amplasamentele pe o hartă care de obicei are coordonate X, Y, nu doar o axă OX ca în această problemă.

Cam atât pentru acest articol! Dacă aveți alte variante de rezolvare vă rog să utilizați secțiunea comentarii. Pentru pasionați vă aștept cu o rezolvare pentru problema intersecției cercurilor în format XY.

Sper să fie util cuiva!

Blog la WordPress.com.

SUS ↑