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!

Modele de algoritmi in #Excel – Prefix Sums (5.1)

Am revenit cu o propunere de rezolvare în Excel, din seria algoritmilor clasici. Doar nu credeați că am renunțat! :) Scuze dacă mai scriu greșit anumite cuvinte sau virgule… din viteză pentru a nu pierde idea. Dacă aveți observații sau comentarii, rog folosiți secțiunea dedicată.

În acest articol voi propune diferite metode de rezolvare a problemelor din categoria algoritmilor Prefix Sums, cunoscuți și sub numele de Scan, Cumulative Sum sau Inclusive Scan, se referă la o serie de metode eficiente pentru calcularea sumelor prefixelor unei secvențe de numere. Prefixul unei secvențe este definit ca suma elementelor din secvență până la un anumit punct.

Algoritmii Prefix Sums sunt utilizați frecvent în diverse domenii ale informaticii, cum ar fi:

  • Procesare secvențială: Algoritmii Prefix Sums pot fi utilizați pentru a calcula rapid sumele prefixelor unei secvențe de numere, ceea ce este util pentru diverse operații de procesare secvențială, cum ar fi căutarea binară, intervale de sume, și detectarea de elemente repetitive.
  • Baze de date: Algoritmii Prefix Sums pot fi utilizați pentru a implementa eficient indexuri de rang, care permit determinarea rapidă a numărului de elemente dintr-o secvență care sunt mai mici sau egale cu o anumită valoare.
  • Algoritmi de căutare: Algoritmii Prefix Sums pot fi utilizați pentru a accelera algoritmii de căutare, cum ar fi căutarea binară, prin precalcularea sumelor prefixelor.

Principiul de bază:

Ideea centrală a algoritmilor Prefix Sums este de a stoca o secvență suplimentară de numere numită „prefix sum array”. Această secvență conține suma elementelor din secvența originală până la un anumit punct. De exemplu, dacă secvența originală este [1, 2, 3, 4, 5], atunci prefix sum array-ul ar fi [1, 3, 6, 10, 15].

Odată ce prefix sum array-ul este calculat, se pot calcula sumele prefixelor oricărui interval din secvența originală cu o singură operație de scădere. De exemplu, pentru a calcula suma elementelor de la indicele 2 la indicele 4 (inclusiv), se calculează prefix sum array[4] – prefix sum array[1] = 15 – 3 = 12.

În Excel există o serie de funcții care ne ajută să implementăm nativ algoritmul, doar că problemele expuse sunt mai complexe pentru a putea fi rezolvate doar cu funcția SCAN().

În imagine este prezentat un exemplu prin care putem utiliza funcția SCAN pentru a determina suma elementelor de pe un array cu SCAN() sau suma tuturor elementelor dintr-un vector. Există mai multe metode și funcții pe care le putem utiliza pentru a aduce date de la o poziție la altă poziție a unui vector: OFFSET(), DROP(), INDEX().

În funcția =SCAN(0;A2:A9;LAMBDA(a;v;a+v)) scanăm vectorul A2:A9 și aplicăm funcția LAMBDA() în care avem parametrii a – acumulator și v – value (cu referire la valoarea de pe linia curentă). În acest exemplu adunăm cele două valori, pe linia următoare, a va aduna la suma de pana acum suma valorii curente (v).

În același fel funcționează și REDUCE() doar că în loc să calculeze pentru fiecare valoare a vectorului, aplică funcția fiecărui element din vector.

Ca să putem elabora un vector dinamic pentru prefix sums avem nevoie de o poziție de start și de final din șir. În celulele D4, E4, F4, G4 am prezentat diferite funcții care pornesc de la același start și au același număr de celule de extras (3). Diferența între funcții este că Offset() pornește de la 0 în abordarea unui bloc de celule la fel cu DROP().

În continuare voi propune diferite metode de rezolvare în Excel a problemelor de pe site-ul https://app.codility.com/programmers/lessons/5-prefix_sums/

Problema PassingCars

Problema PassingCars este o problemă clasică de algoritmică care se referă la calcularea numărului de perechi de mașini care se depășesc reciproc pe o șosea cu o singură bandă. Mașinile se deplasează în ambele direcții și pot depăși alte mașini mai lente. Problema constă în determinarea numărului de perechi de mașini care se depășesc reciproc într-un anumit interval de timp sau pe o anumită porțiune de drum.

Problema PassingCars ]n Excel

În propunerea de rezolvare, ca să înțeleg modul de abordare al problemei în vederea determinării numărului de perechi, am dus șirul în 2D F2xG1 după care am aplicat o combinație de funcții (I3) pentru a calcula minimul între linii și coloane. În funcție există un hardcode (7 de la numărul coloanei și 2 de la numărul de linii) dar este și problema că returnează și o combinație eronată, perechea 1-2 care nu este corectă.

În zona F9 – M14 am schimbat modul de implementare, transformând șirul în direcția Est (G10) prin splitarea șirului din B2. Apoi în coloana Start (F10) determin o secvență de numere de la 0 pe baza numărului de linii a vectorului G10#. Pentru a reprezenta ordinea mașinilor dinspre Vest (H10) ca să le pot compara cu cele care vin din Est am folosit sortarea descendentă (-1) a valorilor de pe Est (G10#) după cele de pe Start (F10). Pentru a determina punctul final am inversat șirul start prin sortarea descendentă (-1). Conform cerințelor problemei, ne interesează când mașinile care vin din Est ( valoarea 0) se intersectează cu cele care vin din Vest (valoarea 1). Ca să pot realiza acest lucru a trebuit să fac o filtrare a valorilor de pe coloana Start cu cele de pe coloana Est. Din cauza faptului că șirul de Start începe de la 0 iar eu intenționez să folosesc funcția de căutare INDEX() a trebuit să adaug valoarea 1 funcției din J9: =TRANSPOSE(FILTER(F10#;G10#=0)+1). Această transpozare a valorilor rezultat mă ajută să determin ceea ce se întâmplă în zona J10.

Funcția din J10 generează numărul de perechi pentru primul 0 din direcția Est. Apoi pe K10 aplic aceeași funcție.

=IF(INDEX(G10#;J9)=0;IF(H10#=1;IF(INDEX(F10#;J9)<I10#;INDEX(F10#;J9)&”-„&I10#;””);””);””)

Problema acestei metode de implementare este dată de faptul că dacă aș avea mai mulți de 0 în șirul original, atunci ar trebui să copii manual formula în dreapta. Pentru a expune perechile unice în ordinea lor folosesc în M10 o combinație de TOCOL() cu UNIQUE() și SORT() apoi număr aceste valori în Q10.

Treaba oarecum mai dificilă a fost să integrez toate aceste coloane și formule de comparație într-o singură funcție complet dinamică. Am ajuns cu greu la rezultat, din aproape în aproape.

=LET(est;--TEXTSPLIT(B2;;",");
     start;SEQUENCE(ROWS(est);;0);
     vest;SORTBY(est;start;-1);
     final;SORT(start;;-1);
     fRecursive; LAMBDA(x;y;FILTER(x;y=0));
     cols; fRecursive(start; est)+1;
     tabi;IFNA(HSTACK(start;est;vest;final;cols);"");
     tabperechi; MAKEARRAY(ROWS(est); ROWS(cols);
           LAMBDA(r;c; IF(INDEX(est;INDEX(cols; c))=0;
                    IF((INDEX(vest; r))=1;
                    IF(INDEX(start;INDEX(cols; c))<INDEX(final; r);
                    INDEX(start;INDEX(cols; c))&"-"&INDEX(final; r);"");"");"")));
      peru; LET(s; SORT(UNIQUE(TOCOL(tabperechi;1))); FILTER(s; s<>""));
      nr; ROWS(peru);
	IFNA(HSTACK(VSTACK("Perechi: ";peru); VSTACK("Nr: "; nr)); ""))

Primele variabile sunt echivalentul din explicațiile anterioare. Prima provocare a fost să determin valorile echivalente din J9, valori care se regăsesc în variabila cols. Doar că nu pentru a putea calcula această variabilă aveam nevoie de o funcție Lambda() recursivă definită în variabila fRecursive. Această funcție o folosesc pentru a face filtrarea valorii echivalente pozițiilor 0 din șirul spre Est. fRecursive doar definește ce să facă funcția mai jos și care sunt parametrii. Ca să pot vedea rezultatele intermediare am creat un tabel în variabila tabi care adună coloană cu coloană variabilele definite anterior. IFNA() în folosesc în acest context pentru a elimina erorile NA determinate de faptul că numărul de elemente a lui cols poate fi mai mic decât numărul de elemente a șirului.

Rezultatul până la tabi este:

variabila tabi din funcția de rezolvare.

Marea provocare a apărut la procedura de calculare a perechilor pe datele din tabi. Ca să pot determina perechile de mașini după algoritmul explicat pentru celula J10, a trebuit să revin la funcția MAKEARRAY() pentru ca calcula o matrice dinamică cu numărul de linii exchivalent numărului de elemente ale șirului și numărul de coloane echivalent numărului de 0 întâlnit. Rezultatul intermediar din variabila tabperechi este:

variabila tabperechi.

Apoi în variabila peru am determinat perechile unice, în variabila nr am determinat numărul lor, după care am făcut o combinație de HSTACK cu VSTACK pentru a genera un tabel dinamic, cu tot cu capul de tabel.

Dincolo de aplicabilitatea în monitorizarea traficului auto, PassingCars poate fi adaptat în domeniul economic pentru:

  • Analiza cererii și ofertei pe piețele bursiere – Numărarea de câte ori o ofertă de cumpărare satisface o cerere de vânzare sau invers.
  • Gestionarea stocurilor – Monitorizarea cererilor de produse într-un depozit și numărarea momentelor în care sosirile de stocuri (oferte) satisfac cererile.
  • Fluxul de aprovizionare într-o fabrică – Cererile pentru piese specifice și momentele în care acestea sunt furnizate.

Problema CountDiv

Este o problemă clasică de algoritmică care se referă la calcularea numărului de divizori ai unui număr dat într-un interval specificat. De exemplu, dacă intervalul este [a, b] și numărul dat este n, problema solicită determinarea numărului de divizori ai lui n care se află în intervalul [a, b].

Exemplu: Considerați intervalul [1, 10] și numărul dat 20. Divizorii lui 20 în acest interval sunt 1, 2, 4, 5 și 10. Prin urmare, numărul de divizori ai lui 20 în intervalul [1, 10] este 5.

În analiza de date, se poate folosi pentru a număra evenimente periodice într-un interval de timp. De exemplu, dacă avem date despre evenimente care se întâmplă la fiecare 10 zile și vrem să știm câte astfel de evenimente au avut loc într-o anumită perioadă de timp.

Exemplu: Dacă avem evenimente la fiecare 10 zile și dorim să știm câte evenimente au avut loc între ziua 15 și ziua 95.
A = 15, B = 95, K = 10

Rezolvare problema CountDiv in Excel

Pentru a afla numărul de evenimente care se întâmplă într-o lună (A 1 – start, B 31 final) odată la 4 zile (K 4) trebuie să caclulăm întregul împărțirii lui B la K apoi întregul împărțirii lui A-1 la K după care facem diferența între rezultate.

Dacă dorim să aflăm zilele din calendar pentru care trebuie să programăm acele acțiuni, am propus în G2 formula:

=SEQUENCE(;E4;IF(B3>=B1;B3;IF(MOD(B1;B3)=0;B1;B1+(B3-MOD(B1;B3))));B3)

pentru generare a unei secvențe de numere pornind de la numărul de divizori (E4) în secvențe de câte K (B3). Apoi în pentur a determina numărul de la care se pornește trebuie să fac un calcul comparativ între A și K:

  • Daca K>=A atunci primul divizor devine K;
  • Daca K<A atunci :
    • daca restul impartirii lui A la K = 0 (MOD(A;K) atunci primul divizor este A;
    • daca nu sunt divizibile atunci formula de calcul al primului divizor din interval devine A + (K – MOD(A;  K))

Condiția principală este ca A să fie mai mare ca B.

Ca să pot folosi într-o singură funcție personalizată, același algoritm poate fi implementat într-o funcție lambda care se poate salva în Name manager (Ctrl+F3). Funcția propusă ar fi:

=LAMBDA(A;B;K;SEQUENCE(;INT(B/K)-INT((A-1)/K);IF(K>=A;K;IF(MOD(A;K)=0;A;A+(K-MOD(A;K))));K))(B1;B2;B3)

în care B1, B2, B3 sunt celulele de testare pentru funcție. În această lambda putem identifica mai bine algoritmul de calcul explicat în pseudocod anterior.

În gestiunea stocurilor, algoritmul poate fi folosit pentru a determina câte reaprovizionări au avut loc într-un anumit interval de timp. De exemplu, dacă un produs trebuie reaprovizionat la fiecare 7 zile, putem calcula câte reaprovizionări au avut loc într-un interval de timp specificat. Alt exemplu din producție, dacă un echipament necesită mentenanță la fiecare 30 de zile și dorim să știm câte sesiuni de mentenanță au avut loc între ziua 50 și ziua 200.

Problema GenomicRangeQuery

Algoritmul GenomicRangeQuery este utilizat pentru a găsi factorul minim de impact al nucleotidelor într-o anumită secvență de ADN, în intervalele specificate. Avem o secvență de ADN reprezentată de un șir de caractere care conține literele A, C, G și T. Aceste litere au factorii de impact corespunzători: A = 1, C = 2, G = 3, T = 4. Trebuie să răspundem la mai multe interogări de tip interval, unde pentru fiecare interogare, ni se cere să găsim factorul minim de impact al nucleotidelor într-un sub-interval al secvenței de ADN. Un șir de caractere S reprezentând secvența de ADN. Două array-uri P și Q de aceeași lungime, unde fiecare pereche (P[i], Q[i]) definește un interval [P[i], Q[i]] în șirul S. Pentru fiecare interogare (P[i], Q[i]), se va returna minimul factorilor de impact al nucleotidelor din intervalul [P[i], Q[i]].

În contextul geneticii, termenul „factor de impact al nucleotidelor” nu este unul uzual. Însă, în multe probleme algoritmice, acest termen se referă la o reprezentare numerică simplificată pentru a evalua rapid o secvență genetică. Totuși, dacă ne referim la interpretarea nucleotidelor și la impactul lor în biologie, putem discuta despre semnificația biologică a diferitelor nucleotide (baze ADN: adenina (A), citozina (C), guanina (G), timina (T)) și cum anumite secvențe pot afecta funcționarea genelor.

Propunerea de rezolvare în Excel presupune descompunerea șirurilor de intrare, așezarea lor pe diferite celule și metode de extragere și funcții de căutare.

Intrările în model sunt ADN, S – secvența P și Q cele două perechi din secvență pe care le căutăm. A9, D3, H3, I3 sunt funcții de descompunere a textului pe linii. în J3 folosesc o metodă cu OFFSET(). Cei care au mai lucrat în trecut cu el, au permanent tendința de a se duce spre zona de vectori dinamici sprea această funcție. Doar că mie mi-a spus odată un expert în Excel că funcțiile de acest tip sunt funcții volatile și nu se recomandă a se utiliza. Și totuși, le folosesc de câte ori am nevoie și nu găsesc altă soluție mai rapidă. DROP() este un pic mai complex pentru că trebuie să extragi un segment din vector, pozițiile de deasupra și de dedesupt.

Rezolvarea în descompuneri în diferite zone și funcții de căutare este destul de simplă în Excel. Problema este când încerci să le unifici… pentru că sunt prea mulți vectori de parcurs în cadrul aceleeași operații. Vă prezint în continuare funcția din B15. Nu sunt extraordinar de mândru pentru că m-am chinuit prea mult… iar eu am în Excel un principiu: Dacă te muncești prea mult ca să faci un lucru, înseamnă că ceva nu faci bine.

=LET(vtab;
     LET(pa;B3;qa;B4;
       fRecursive;LAMBDA(p;q;
	    LET(vP;--TEXTSPLIT(p;;",");
		    vQ;--TEXTSPLIT(q;;",");
			vPQ;HSTACK(vP;vQ);
			vRows; BYROW(vPQ;LAMBDA(r;TAKE(r;;-1)-TAKE(r;;1)+1));
			vStart;vP+1;
			HSTACK(vStart;vRows)));
		fRecursive(pa;qa));
	vCod;MID(B1;SEQUENCE(LEN(B1));1);
	vPoz;SEQUENCE(LEN(B1));
	vADN;MID(B2;SEQUENCE(LEN(B2));1);
	vvADN;XLOOKUP(vADN;vCod;vPoz;9999);
	ac;MAX(CHOOSECOLS(vtab;2));
	ar;ROWS(CHOOSECOLS(vtab;1));
	tabfin;MAKEARRAY(ar;ac;
		LAMBDA(r;c;
			LET(pv;INDEX(vtab;r;1);
				dv;INDEX(vtab;r;2);
				MIN(INDEX(vvADN;SEQUENCE(dv;;pv))))));
	valfin;BYROW(tabfin;LAMBDA(r;MIN(r)));
	valfin)

Marea problemă a fost să pot face un tabel intermediar cu valorile de start în căutare și lungimea segmentului care va trebui extras. Cele două coloane din imaginea de mai jos, echivalentul variabilei vtab pe care o definesc în LET cu ajutorul funcției recursive care se numește fRecursive.

Rezultatul variabilei vtab

Uneori când sunt prea mulți vectori de parcurs, atunci segmentăm funcția în rezultate intermediare prin intermediul altor funcții LET() dar în care de multe ori definim astfel de funcții recursive. Funcțiile recursive pot fi definite și extern în Name manager (Ctrl+F3).

Cheia în funcția fRrecursive din definirea vtab este obținerea lungimii șirului de extragere (cea de-a doua coloană). Pentru a face această operațiune am folosit variabila vRows care prin BYROW() calculează diferența dintre coloanele P și Q (funcțiile TAKE) la care adaugă valoarea 1, pentru că în parcurgerea vectorilor din Excel cu funcții INDEX() de cele mai multe ori pornim de la valoarea 1.

Ulterior după ce a fost definit tabelul vtab folosim maximul din a doua coloană în variabila ac și numărul de linii rezultat în variabila ar. Aceste două variabile le folosim mai jos în variabila tabfin în care creem un tabel de ac coloane și ar linii pentru care aplicăm lambda care ne ajută la indexarea valorilor din segmentul ADN stocate în variabila vvADN.

valori intermediare stocate în tabfin

valfin este ultima variabilă prin care BYROW() calculează minimul de pe fiecare linie.

Cel mai greu în rezolvare am ajuns la cele două valori coloane din vtab.

Analiza pieței de acțiuni

Dincolo de această problemă, algoritmul se poate aplica în domeniul economic pentru a ajuta la decizia de a investi sau nu într-un pachet de acțiuni. Se dă un șir de prețuri zilnice ale unei acțiuni și vrei să găsești prețul minim dintr-un anumit interval de zile pentru a lua decizii de investiții.
Variabilele de intrare:

  • S (prețuri acțiuni): Un șir de prețuri zilnice ale acțiunilor.
  • P și Q (intervale de interogare): Două liste care conțin perechi de început și sfârșit pentru fiecare interval de zile în care vrei să analizezi prețul minim.

S = [23, 29, 21, 32, 25, 28, 24] (prețurile zilnice ale acțiunii)
P = [1, 2, 4] (începutul intervalelor de interogare)
Q = [3, 5, 6] (sfârșitul intervalelor de interogare)

Explicația lui P și Q.
Intervalul 1: de la ziua 1 la ziua 3
Intervalul 2: de la ziua 2 la ziua 5
Intervalul 3: de la ziua 4 la ziua 6

GenomicRangeQuery analiza pieței de actiuni.

Pentru a calcula prețurile minime trebuie să știm că nu mai avem din funcția anterioară variabilele specifice pentru vADN si vvADN. Noua funcție care este derivată din cea anterioară este:

=LET(
	vtab;LET(pa;B35;qa;B36;
	fRecursive;LAMBDA(p;q;
		LET(vP;--TEXTSPLIT(p;;",");
			vQ;--TEXTSPLIT(q;;",");
			vPQ;HSTACK(vP;vQ);
			vRows;BYROW(vPQ;LAMBDA(r;TAKE(r;;-1)-TAKE(r;;1)+1));
			vStart;vP+1;
			HSTACK(vStart;vRows)));
		fRecursive(pa;qa));
	vvADN;--TEXTSPLIT(B34;;",");
	ac;MAX(CHOOSECOLS(vtab;2));
	ar;ROWS(CHOOSECOLS(vtab;1));
	tabfin;MAKEARRAY(ar;ac;
		LAMBDA(r;c;LET(pv;INDEX(vtab;r;1);
						dv;INDEX(vtab;r;2);
						MIN(INDEX(vvADN;SEQUENCE(dv;;pv))))));
	valfin;BYROW(tabfin;LAMBDA(r;MIN(r)));
valfin)

Dincolo de dificultatea de implementare în Excel, GenomicRangeQuery este un algoritm foarte important de umrărit pentru că poate aplica (dincolo de minim) orice funcție asupra unor intervale de date definite ceea ce poate permite rezolvarea unor probleme de complexitate oarecum mai ridicată.

Problema MinAvgTwoSlice

Algoritmul MinAvgTwoSlice este o soluție la o problemă de găsire a unei subsecvențe continue de dimensiune minimă (de cel puțin două elemente) într-un șir de numere întregi, astfel încât media aritmetică a acestei subsecvențe să fie cât mai mică posibil. Problema este oarecum asemănătoare cu GenomicRangeQuery doar că de data aceasta trebuie să calculăm media aritmetică pe toate intervalele posibile, nu doar pe câteva intervale specificate.

Propunere de rezolvare

Propunere rezolvare MinAvgTwoSlice in Excel

În rezolvarea problemei prin descompuneri, ca să pot face un tabel de căutare din două dimensiuni specifice lungimii șirului A, am introdus în E3 funcția: =SEQUENCE(;B4-1;2) în care în B4 avem lungimea șirului, -1 ca să pot avea același număr de elemente pornid de la 2. Aceste valori le folosesc pentru a specifica faptul că în algoritm nu pot porni de la 1:1 ci de la 1:2. La D4 am folosit funcția =SEQUENCE(B4-1) cu start de la 1 (nespecificat) dar ca să aibă același număr de elemente cu numărul de coloane. Folosesc -1 pentru că un șir intermediar nu poate porni de la ultima poziție a sa, cerința algoritmului fiind să ne raportăm la minim două elemente. In E4 am introdus apoi funcția de medie dinamică pe baza indexării liniilor cu coloanele. =IF($D4>=E$3;””;AVERAGE(INDEX($A$4#;$D4):INDEX($A$4#;E$3))). Rezultatul intermediar care ar apărea în J7 înainte de a face media este prezentat în imagine. Ca să pot afla apoi celula unde se află aceasă valoare minimă, am utilizat în S4 o funcție pe care o dezvoltasem mai demult pentru căutarea adresei unei celule dintr-un tabel pe baza unei valori specificate. Rezultatele intermediare pentru această funcție sunt specificate în P4, Q4 și R4.

=LET(aria; E4:J9;
	ca; MIN(COLUMN(aria))-1;
	ra; MIN(ROW(aria))-1;
	tc; TOCOL(aria;1);
	unde; MATCH(L4;tc;0);
	col; LET(cs; COLUMNS(aria); 
			 c; MOD(unde;cs); 
			 IF(c=0; cs; c))+ca; 
	row;ROUNDUP(unde/COLUMNS(aria);0)+ra;
	cel; CHAR(64+col)&(row);
cel)

Trebuie menționat că limită a acestei funcții că ea funcționează doar pentru tabele de date poziționate în sectorul de coloane A:Z.

Variabila aria conține tabelul de date pe care dorim să-l analizăm pentru a căuta valoarea pe care am obținut-o în celula L4. Acolo este trecută valoarea de căutare, stocată în variabila unde. Variabila unde scanează valoarea din L4 pe coloana obținută din aducerea la o dimensiune a tabelului aria. Ca să determin coloana din foaia de calcul în care se află valoarea folosesc un let care calculează poziția celulei în aria la care adună valoarea variabilei ca, care reprezintă numărul coloanei din foaia de calcul la care începe aria. Valoarea rezultat este stocată în variabila col. Aceeași procedură un pic simplificată este și la determinarea valorii variabilei row care este o rotunjire în sus (ROUNDUP) a locului unde se află valoarea în vectorul pe coloană împărțit la nnumărul de coloane al ariei, la care se adaugă valoarea variabilei ra care calculează linia la care începe aria în foaia de calcul. Ulterior calculez variabila cel prin care determin caracterul alfabetic de la A la Z. Litera A are codul 65 de aceea încep de la 64 în CHAR().

Pentru a întoarce rezultatul din E20 care include media minima și slice echivalent în poziții din șir folosesc următoarea funcție:

=LET(sir;A4#;
	vLen;ROWS(sir);
	tab; MAKEARRAY(vLen;vLen;
		LAMBDA(r;c;IF(r>=c;"";AVERAGE(INDEX(A4#;r):INDEX(A4#;c)))));
	vmin; MIN(tab);
	tc; TOCOL(tab);
	unde; XMATCH(vmin; tc;0);
	vRow; ROUNDUP(unde/COLUMNS(tab);0);
	vCol; LET(cs; COLUMNS(tab); c; MOD(unde;cs); IF(c=0; cs; c));
	
	"Min AVG: "&vmin&" Slice ("&vRow-1&", "&vCol-1&")"
)

Reprezentarea variabilei tab este în imaginea de la soluția problemei în aria E12:K18. Celelalte variabile sunt rezultate intermediare explicate în problemă.

În economie acest algoritm ar putea fi utilizat în analiza costurilor de producție. De exemplu într-o fabrică se înregistrează costurile zilnice de producție. Un manager ar putea încerca să identifice media cea mai mică pe cel mai mic interval de zile, unde costurile au fost cele mai mic. În felul acesta poate corela cu alte date pentru a identifica ce s-a făcut diferit pentru a obține costuri mai scăzute.

Exemplu de problemă pentru un șir de date:

Exemplu analiză costuri de producție.

Cam asta a fost pentru acest articol. Multă treabă mai este…

Sper să fie util cuiva!

Blog la WordPress.com.

SUS ↑