Modele de algoritmi în #Excel – Greedy algorithms (16)

În acest articol propun un model de rezolvare a probelemelor de pe site-ul Codility: https://app.codility.com/programmers/lessons/16-greedy_algorithms/

Un algoritm greedy este o strategie de rezolvare a problemelor care face alegeri pas cu pas, selectând în fiecare moment opțiunea cea mai bună disponibilă, fără a lua în considerare deciziile viitoare.

Principiul de bază este: „Alege ceea ce pare a fi cea mai bună opțiune la fiecare pas, sperând că această strategie va duce la soluția optimă.”

Acești algoritmi sunt simpli și eficienți, dar nu garantează întotdeauna cea mai bună soluție globală.

În Excel nu avem posibilitatea de a face alegeri numaidecât locale, ci problemele trebuie trate la nivel global. Da o strrategie eficientă este să rezolvi pas cu pas problema, din aproape în aproape pentru a putea ajunge la o soluție integrată. Chiar și în rezolvările individuale trebuie să avem în vedere funcții reutilizabile și tratarea globală a vectorilor de input. De aceea în toată această tehnică este esențială funcția MAP() pentru parcurgerea element cu element a unui vector sau SCAN() tot pentru parcurgere globală, dar cu avantajul acumulării.

Un pic de teorie aplicată

În această secțiune doar voi reaminti cum funcționează o funcție MAP(), SCAN() și BYROW().

Foarte important: Nici una din funcții NU generează tabele. De aceea, în exemplele mele vedeți foarte mult TEXTJOIN() și TEXTSPLIT() ca să pot acumula rezultatele intermediare într-o singură valoare. Dar ca să le pot prelucra trebuie să le splitez ca numere.

MAP() este o funcție care parcurge un vector valoare cu valoare de sus în jos. În multe cazuri putem folosi MAP() pentru a scana un fake vector, generat de exemplu de o funcție SEQUENCE pentru a putea face căutare și înainte pe un vector de valori. De cele mai multe ori dacă dorim să obținem printr-o singură formulă un tabel trebuie să le încapsulăm într-un LET(). Nu am exemple în acest moment despre combinații între MAP() și SCAN(). De obicei eu personal aleg să le tratez separat sau să se separ în funcții recursive distincte.

Sintaxa generică este: =MAP(vector; LAMBDA(valoare; calcule))

Exemple funcția MAP()

Pentru D10 formula este:

=MAP(B10#;  LAMBDA(v; IF(v=1;INDEX(A10:A14;v); INDEX(A10:A14;v)+INDEX(A10:A14;v-1))))

Pentru E12 unde este o variantă cu mai puține inputs (doar variabila _a este input), formula este:

=LET(_a;A10:A14;
    seq;SEQUENCE(ROWS(_a));
    MAP(seq;LAMBDA(v; IF(v=1; 
                           INDEX(_a;v); 
                           INDEX(_a;v)+INDEX(_a;v-1)
 ))))

Tehnica aceasta de a folosi o secvență este des întâlnită în rezolvarea acestor probleme și nu numai.

Func’ia SCAN() este foarte utilă pentru prelucrări cu acumulare.

Sintaxa generică este: =SCAN(valoare_inițială; vector; LAMBDA(acumulator; valoare; calcule))

Exemplificare SCAN()

REPT() este o funcție de replicare a unui șir de un număr specificat de ori.

BYROW() este o funcție care tratează un tabel de date linie cu linie. Similar cu BYROW este BYCOL(). Când dorim să facem calcule simple, atunci putem specifica direct funcția după specificarea tabelului. Dacă avem veva mai mult de calcul, sintaxa generală este: =BYROW(tabel; LAMBDA(linie; calcule))

Exemplificare BYROW()

Funcția TAKE(), CHOOSECOL() sau INDEX() au un rol important în tratarea element cu element a unei linii.

Acestea fiind spuse o să încep atipic această rezolvare cu a doua problemă… pentru că este mai simplă.

Problema TieRopes

Presupune să se determine numărul de funii de o anumită lungime K specificată care pot fi create prin însumarea (înodarea) capetelor dintr-un șir de valori date (A). Valoarea capetelor nu poate depăși K dar funiile pot avea dimensiuni mai mari sau egale cu K. Ordinea de însumare este consecutivă nu se acceptă ca un segment A[0] să fie legat de un alt segment A[n].

Rezolvarea problemei în Excel este foarte simplă, dar rezultatele obținute prin rezolvare locală (subșiruri continue fără verificare globală înainte și înapoi) pot fi oarecum discutabile.

TieRopes rezolvare în Excel

Pornind de la șirul din B2, l-am descompus în A4 apoi am calculat secvența cu start de la 1 în B4. Cheia problemei este în E4 unde prin SCAN am comparat sumele acumulate până în prezent din vectorul A cu valoarea lui K (dimensiunea minimă a unei funii). Ca să pot oferi rezultatele în format de segmente, în H4 am utilizat următoarea formulă:

=MAP(G4#;LAMBDA(v; IF(v=1;
       TEXTJOIN(";";;FILTER($B$4#;($B$4#<=INDEX(F4#;v))));
       TEXTJOIN(";";;FILTER($B$4#;($B$4#<=INDEX(F4#;v))*(($B$4#>INDEX(F4#;v-1))))))))

În care scanez vectorul din G3 rezultat în urma prelucrărilor și pe baza valorii acestui vector concatenez cu TEXTJOIN() valorile de până în acel moment.

Formula integrată pornind de la șirul din B2 și valoarea lui K din C4 este:

=LET(_a; --TEXTSPLIT(B1;;",");
      seq; SEQUENCE(ROWS(_a)); _k; C4;
      _s; SCAN(0;_a; LAMBDA(a;v; IF(a+v>=_k;0;a+v)));
      _f; FILTER(seq; _s=0);
      seqf; SEQUENCE(ROWS(_f));
      result; MAP(seqf; LAMBDA(v; IF(v=1; 
             TEXTJOIN(";";;FILTER(seq;(seq<=INDEX(_f;v))));
             TEXTJOIN(";";;FILTER(seq;(seq<=INDEX(_f;v))*
                     ((seq>INDEX(_f;v-1))))))));
result)

Exemplificare practică

Presupunem că avem un lanț de aprovizionare logistic pentru un retailer care trebuie să transporte pachete mici de produse între depozite și punctele de vânzare. Aceste pachete au greutăți variabile, iar pentru a reduce costurile de transport, ele trebuie grupate în unități mai mari, dar respectând anumite restricții minime de greutate pentru eficiență.

Sintetizând descrierea trebuie să respectăm condițiile:

  • Fiecare palet să aibă o greutate minimă K pentru ca transportul să fie eficient (sub această valoare, paletul nu este rentabil).
  • Trebuie să formăm cât mai mulți paleți, pentru a maximiza utilizarea spațiului din camion și a reduce numărul de transporturi.

Exemplu concret de date:
Să presupunem că avem un set de 10 pachete cu următoarele greutăți (în kg): A=[3,5,1,6,2,4,8,3,7,2] și greutatea minimă necesară pentru transport eficient este K=10 kg.

Rezolvarea este foarte simplă:

Alocare colete pe peleti. Aplicabilitate TieRopes.

în care pentru a calcula numărul de kg pe fiecare palet am introdus o nouă variabilă în funcția prezentată anterior:

kgp; MAP(result; LAMBDA(v; SUM(INDEX(_a;--TEXTSPLIT(v;;";")))));

care presupune însumarea valorilor coletelor din șirul dat.

Problema MaxNonoverlappingSegments

Problema aceasta a fost un coșmar din punct de vedere al prelucrărilor. 100% rezolvarea ei în Excel este doar un exercițiu de manipulare de date și rezultate, fără a avea pretenții de optimizare.

Problema MaxNonoverlappingSegments este o problemă clasică de optimizare care face parte din categoria Greedy algorithms (algoritmi lacomi). Aceasta constă în selectarea unui număr maxim de segmente (intervale) care nu se suprapun, dintr-un set de segmente date.

Avem o serie de segmente definite prin capete (start și end points), fiecare segment fiind reprezentat printr-o pereche de numere: (A[i],B[i]) unde:

  • A[i] este punctul de început al segmentului.
  • B[i] este punctul de sfârșit al segmentului.

Obiectivul este să alegem cât mai multe segmente care nu se suprapun, adică nu au puncte comune, astfel încât să maximizăm numărul total de segmente selectate.

O chichiță a problemei este dată de faptul că rezultatul final trebuie să fie prezentat în segmente de câte 3 și în combinații de câte 3 trebuie să prezentăm output-ul.

În imagine este prezentată rezolvarea graduală a problemei, proiecții intermediare pas cu pas, până a ajunge la forma finală din aproape în aproape.

Problema MaxNonoverlappingSegments proiecție în Excel, pas cu pas.

Așa cum explicam în partea de teorie a acestui articol, cheia rezolvării constă în utilizarea funcției MAP și consțientizarea faptului că rezultatul trebuie să fie permanent o singură valoare.

Având în vedere că trebuie să returnăm valorile în seturi de câte 3 a trebuit să introduc și să salvez funcția _fTripleMatrix(elem) prezentată în aceste articole, care are următorul cod:

=LAMBDA(elem; LET(nrElemente; elem;
          combinatii; COMBIN(nrElemente; 3);
TripleG; SCAN("1;2;2"; SEQUENCE(combinatii);
   LAMBDA(a;v;
       LET(arr; --TEXTSPLIT(a;";");
           x; TAKE(arr;;1); y; TAKE(TAKE(arr;;2);;-1); z; TAKE(arr;;-1);
           newx; IF(AND(y=nrElemente-1; z=nrElemente);
                        IF(x<nrElemente-2; x+1; x); x);
           newy; IF(newx=x; IF(z=nrElemente;
                        IF(y<nrElemente-1; y+1; y);y); newx+1);
           newz; IF(newx=x; IF(z<nrElemente;z+1;y+2); newy+1);
newx&";"&newy&";"&newz)));
tabf; --TEXTSPLIT(TEXTJOIN("|";;TripleG);";";"|");
tabf))

în care elem este o valoare mai mare decât 3 și, așa cum explicam cu alte ocazii, poate avea un maxim de 189 pentru că numărul de combinații de 190 luate câte 3 depășește valoarea 2^20 care este numărul de linii dintr-o foaie de calcul Excel. Ca o curiozitate, în Google Spreadheet nu am reușit să generez o secvență de numere mai mare de 2^15.

Cheia rezolvării problemei este identificarea capetelor și intersecția lor cu alte segmente. Această secțiune este rezolvată în F5 și G5 printr-o funcție de mapare în care valoare 1 presupune intsecție cu alt interval, iar 0 lipsa intersecției. Dacă suma ambelor segmente se regăsește pe celălalt segment, atunci valoarea 2 ne spune că avem o intersecție. Valoarea 1 ne spune că acel segment nu se intersectează cu altul. Și de aici încep tot felul de calcule și filtre, mapări pe matricea de triplete dinamică și așa mai departe.

Formula finală integrată este:

=LET(_a;$A$2:$A$6;_b;$B$2:$B$6;segment;SEQUENCE(ROWS(_a));
      inter; MAP(segment;LAMBDA(v;LET(
                                   _va;INDEX(_a;v);
                                   _vb;INDEX(_b;v);
                                   _sa;MAP(_a;LAMBDA(_xa;_xa<=_vb));
                                   _sb;MAP(_b;LAMBDA(_xb;_xb>=_va));
                       tabi;BYROW(HSTACK(--_sa;--_sb);SUM);
                       TEXTJOIN(";";;tabi)
)));

    intersectii; MAP(inter; LAMBDA(_nv;
                      LET(_ts;--TEXTSPLIT(_nv;;";");seq;SEQUENCE(ROWS(_ts));
                           intersectii;TEXTJOIN(";";;FILTER(seq;_ts=2));
                           intersectii)));
    excluderi; MAP(inter; LAMBDA(_nv;
                      LET(_ts;--TEXTSPLIT(_nv;;";");seq;SEQUENCE(ROWS(_ts));
                          excluderi;TEXTJOIN(";";;FILTER(seq;_ts=1));
                          excluderi)));

    verif; HSTACK(VSTACK({"Segment"};segment);
                  VSTACK({"Intersectii"};intersectii);
                  VSTACK({"Excluderi"};excluderi);
                  VSTACK({"ArrayPoz"};segment-1));

    allExclude; BYROW(HSTACK(segment;excluderi); LAMBDA(r; TEXTJOIN(";";TRUE;r)));
    sorte; MAP(allExclude; LAMBDA(v; TEXTJOIN(";";;SORT(--TEXTSPLIT(v;;";")))));

   _freqBR; LAMBDA(x; LET(arr; --TEXTSPLIT(x;;";"); rr; ROWS(arr);
                          result; TEXTJOIN("|";;BYROW(_fTripleMatrix(rr); 
                                 LAMBDA(r; LET(_p1; TAKE(r; ;1); 
                                              _p2; INDEX(r; ;2); 
                                              _p3; TAKE(r; ;-1);
                                  TEXTJOIN(";";;INDEX(arr;_p1);INDEX(arr;_p2);INDEX(arr;_p3)))))); 
                          result));

    toatecomb; MAP(sorte; LAMBDA(v; _freqBR(v)));

    splitate; TEXTSPLIT(TEXTJOIN("-";;toatecomb);"|";"-");
    combunice; SORT(UNIQUE(TOCOL(splitate;3)));
    intersectiiUnice; FILTER(UNIQUE(intersectii); LEN(UNIQUE(intersectii))>1);

    checkIU; MAP(combunice; LAMBDA(v; LET(unice; intersectiiUnice; 
                                      IF(MIN(LEN(SUBSTITUTE(v; unice;"")))<5;FALSE;TRUE))));

   FILTER(combunice;checkIU)
)

O parte complexă din formulă este definirea funcției recursive _freqBR care înglobează funcția _fTripleMatrix() definită anterior. Am utilizat această tehnică pentru că în anumite moment anumite rezultate intermediare pot avea combinații diferite.

Cam atât pentru astăzi… Mai este o singură lecție pe site-ul Codility… dacă aveți alte colecții de probleme publice puteți să mi le oferiți în comentarii pentru a încerca abordarea lor. Eventual o resursă de probleme în română ar fi și mai fain.

Sper să fie util cuiva!

Competiție Excel pentru studenți – MECC 2025

Bun găsit cititorilor acestui blog. De curând mi-am asumat rolul de ambasador MECC pentru Universitatea Alexandru Ioan Cuza din Iași. Prin acest rol mi-am propus să popularizez competițiea mondială Microsoft Excel Colleciate Challenge organizată de University of Arizona împreună cu FMWC . Sponsorul principal este compania Microsoft.

Această competiție este destul de intensă, iar participarea studenților români lipsește cu desăvârșire, de aceea consider că acest demers este foarte important. Domeniile de studiu ale studenților sunt în special cele economice, de informatică, matematică și toate celelalte domenii care au în programa lor de pregătire colectare, manipulare și interpretare de date. Problemele pot fi rezolvate atât cu Excel clasic, Excel Modern cât și cu Python Anaconda integrat în Excel.

Detaliile despre organizare, înscriere, format sunt disponibile pe site-ul: https://mecc.college/

La competiție pot participa și elevii de liceu în anumite condiții documentate pe site. Pentru ei există o categorie de fișiere puțin mai simplă: Junior Varsity. Atenție la denumirea fișierelor.

Pe scurt, după înscriere, urmăriți calendarul competiției iar în momentul în care apar etapele de calificare veți primi un email. Competiția se derulează de obicei în zilele de weekend și se poate participa individual sau în echipe. Participarea este gratuită!

Pentru studenții de la UAIC, am creat o echipă de Teams în care vă puteți înscrie utilizând codul 5zx4gq8 Trebuie să deschideți Teams, Join or create team, Join team, Join a team with a code și tastați codul apoi Add team. Acolo veți găsi resurse de pregătire off-line sau în comun. Ca să pot primi informările automat cu privire la rezultate rog studenții să se înregistreze la instituție cu numele: Alexandru Ioan Cuza University indiferent de facultate.

Pentru studenții de anul I, FEAA de la CIG Seria 1 care fac laboratoarele cu mine, vor primi bonificații la laborator pentru obținerea de punctaj la etapa din 20-23 februarie 2025 și sau 27-30 martie 2025.

Pentru prezentarea competiției am decis crearea unei întâlniri pe Teams cu acces public. Prima întâlnire va avea lor în data de 17.02.2025 de la 13:00 la 14:30.

Agenda

  • Prezentarea competiției
  • Înregistrare și acces la materiale
  • Prezentare model de caz (Ianuarie 2025 – Original autor Harry Gross)
  • Explicații instruire – 1. Text_Functions (autor Harry Seiders)
  • Resurse de învățare

Întâlnirea are setată o secțiune de Q/A. În funcție de interes, stabilim următoarele întâlniri și discuții.

Sper să ajungă la cât mai mulți studenți.

Modele de algoritmi în #Excel – Caterpillar method (15)

Nu am idee cum s-ar traduce cel mai bine în limba română această metodă. Unii spun că ar fi metoda omidei alți autori metoda ferestrei glisante bidirecționale. Scopul meu este să rezolv în Excel problemele din această categorie, rezolvări care mi s-au părut relativ simple în Excel.

Mă apropii de finalul problemelor de pe Codility, iar astăzi vă propun câteva metode de rezolvare din cadrul probemelor Caterpillar method explicate la această adresa: https://app.codility.com/programmers/lessons/15-caterpillar_method/

Denumirea vine de la modul în care algoritmul își extinde și retrage fereastra de procesare, similar cu modul în care o omida se mișcă – înaintează secvențial, dar își ajustează poziția astfel încât să acopere eficient o zonă, fără suprapuneri inutile.

În Excel operațiunile acestea le putem face prin indexarea unui vector în funcție de diferite poziții curente de căutare, adresabile prin variabilele R și C ale lui MAKEARRAY() sau cele 3 valori ale matricelor de triplete TripleG (denumire pe care o dau eu acestei tehnici descrise în mai multe ocazii).

Dar înainte de a începe să prezentăm…

Foarte puțină teorie

Pentru a secvenția un vector sau un tabel în Excel avem la dispoziție mai multe funcții, modul în care le utilizează fiecare dintre noi depinzând de experiență, inspirația de moment sau cunoașterea lor.

Principalele funcții pe care le adresez în această secțiune sunt: TAKE(), CHOOSECOOLS(), COOSEROWS(), DROP(), INDEX().

Exemplificare funcții de păstrare a anumitor valori dintr-o matrice.

INDEX() în combinație cu MATCH() a fost mult timp considerat o alternativă la VLOOKUP(). Doar că această funcție poate face mult mai mult chiar dinainte de funcțiile dinamice, când artificiul suprem erau funcțiile CSE (Ctrl+Shift+Enter). Combinat cu SEQUENCE() în versiunile moderne de Excel nu mai este nevoie să introducem funcția cu CSE ci funcționează automat. Plus dinamica lui Sequence ne poate duce la soluții extraordinar de spectaculoase. Vezi exemplul din A14 unde aducem ultimele 3 numere din tabel de pe ultima coloană în ordine inversă.

Avantajul lui DROP și TAKE este că pot adresa atât linii cât și coloane prin parametrii 2 și 3. Coloanele și liniile pot fi adresate cu numere pozitive în ordinea coloanelor (1 prima coloana, 2 primele două coloane) cât și negative (-1 ultima linie/coloana, -2 ultimele două linii sau coloane). Choosecols sau Chooserows sunt oarecum mai precise pentru că adresează exact coloana sau linia specificată, dar pot fi și ele combinate cu SEQUENCE() pentru a adresa mai multe linii sau coloane. De exemplu în K6 putem face o optimizare cu sequence în forma: =CHOOSEROWS(COOSECOLS(vNumere;1); SEQUENCE(5;;2))

Personal consider că în cele mai multe cazuri INDEX() este de departe funcția câștigătoare, dar nu sunt de neglijat nici celelalte cazuri de utilizare.

Sortarea valorilor pe linie?!

Într-o zi una din firmele cu care lucrez pe partea de training privat de Excel mi-a trimis o agendă personalizată pentru un curs de Excel Avansat în care unul din puncte era sortarea valorilor pe linii. Eu când nu știu ceva, nu predau sau nu accept deloc clientul. Având în vedere că era totuși un client vechi și important am cerut clarificări… dar nu înainte de a mă uita pe internet dacă există așa ceva…

Ceea ce mi s-a confirmat și am găsit pe Internet mi s-a părut ceva simplist așa că am mers înainte. Dar cazul nu este deloc așa cum ar trebui.

Pentru versiunea manuală de sortare dacă vrei să faci sortarea pe linie:

Dacă vrei să sortezi valorile existente direct în tabel trebuie să parcurgi următorii pași:

  • Selectează rândul cu datele pe care vrei să le sortezi.
  • Mergi la „Home” sau „Data” → „Sort & Filter” → „Custom Sort”.
  • În fereastra de sortare, apasă pe „Options” și selectează „Sort left to right”.
  • Alege criteriul de sortare (ordine crescătoare sau descrescătoare) și apasă OK.

Având în vedere că în Excelul modern, operațiunile manuale sunt înlocuit cu funcții, operațiunea se poate realiza și prin funcția SORT() cu valoarea FALSE pentru parametrul 4.

Funcția de sortare pentru valorile pe o linie.

Și totuși dacă am dori să sortăm toate valoarile crescător pe fiecare linie în parte?

Lucrurile nu mai sunt la fel de simple pentru că funcția sort nu mai funcționează așa cum ne-am aștepta. În Excel, SORT() este dedicat implicit valorilor de pe o coloană, iar ca să ducem o linie în coloană folosim TRANSPOSE().

Exemplificare valori random:

Tabel cu numere sortate crescător pe linie.

Pentru această operațiune introduc în acest articol funcția _fSortRows() care are următorul corp:

=LET(arr; A2:F25;
     join; BYROW(arr; LAMBDA(r; TEXTJOIN(";";;r)));
     rez; MAP(join; LAMBDA(x; TEXTJOIN(";";;SORT(TRANSPOSE(--TEXTSPLIT(x; ";"))))));
     result; TEXTSPLIT(TEXTJOIN("|";TRUE;rez);";";"|");
  result
)

în care:

  • arr – este blocul de numere generat aleator cu RADARRAY();
  • join – unește toate valorile de pe linie pentru a le putea parcurge linie cu linie cu MAP() din rez;
  • result – este rezultatul cu artificiul de splitare a blocurilor de valori pe linii și coloane.

Această parte am sintetizat-o și într-un scurt clip video.

Acestea fiind spuse, să trecem la rezolvarea problemelor:

Problema AbsDistinct

Problema AbsDistinct presupune determinarea numărului de valori distincte dintr-un array, luând în considerare doar valoarea absolută a fiecărui element.

Exemplu
Dacă avem următorul array sortat: -5,-3,-1,0,3,6
Valorile absolute sunt: 0,1,3,3,5,6
Valorile distincte sunt: 0, 1, 3, 5, 6 (5 valori distincte).

Rezolvarea este foarte simplă în Excel:

în C2 am utilizat formula: =SORT(UNIQUE(ABS(A2:A7)))

în care ABS calculează absolutul fiecărui număr din vectorul A, UNIQUE() determină toate valorile unice, iar SORT() le sortează ascendent.

Această metodă poate fi utilizată în cazul tranzacțiilor bursiere pentru a identifica variațiile unice absolute ale unei acțiuni într-o perioadă de timp.

Problema CountDistinctSlices

În cardrul acestei probleme avem un șir de numere (A) și vrem să determinăm câte sub-secvențe distincte (numite și slice-uri) pot fi formate, astfel încât toate elementele din fiecare sub-secvență să fie distincte.

Scopul problemei este să aflăm câte sub-secvențe (slice-uri) distincte există, astfel încât niciun număr dintr-un slice să nu se repete.

Rezolvare CountDistinctSlices în Excel.

Rezolvarea problemei nu a fost chiar atât de simplă cum pare la final din cauză că nu am identificat de la început calea cea mai simplă de rezolvare, eu abordând matricial problema cu Makearray(). Doar că matricile nu rezolvă problema sau cel puțin nu am identificat calea corectă.

Rezultatul sugerat de cei de la Codility era dat de următoarele combinații unice: (0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2), (3, 3), (3, 4) and (4, 4) eu personal neînțelegând la început că slice (0,2) înseamnă valorile 3,4,5.

Ca să pot rezolva problema am introdus în E2 o formulă destul de abstractă pe care încercă să o explic:

=MAP(B2#; 
      LAMBDA(poz; 
           TEXTJOIN("|";;
           SCAN(0; DROP($A$2:$A$6;poz); 
                    LAMBDA(a;v; 
                         IF(a=0; v; 
                                 IF(IFERROR(TAKE(--TEXTSPLIT(a;";");;-1);v)=v;
                                  "";
                                   IF(a="";"";TEXTJOIN(";";TRUE;a;v))
                                   ) 
                            )
                           )
                )
                )
           )
)

Ca să pot scana blocul de la poziția 0, apoi de la poziția 1 și așa mai departe, a trebuit să definesc secvența din B2 pe care o parcurg cu MAP(). Apoi pentru fiecare poziție, returnez o combinație de valori de pe blocul A2:A6 pe care-l parcurg cu SCAN(). Partea frumoasă a acestei construcții este dată de faptul că în acumulatorul a introduc întotdeauna valorile în ordinea lor, ceea ce-mi permite să le iau cu acel TAKE() pentru a-l putea compara cu valoarea curentă V ca să identific dacă valorile sunt egale. Scan-ul funcționează la început pentru valoarea 0 a vectorului din B2#. Apoi map-ul duce Scanul mai jos pentru restul de slices.

În H2 folosesc artificiul de splitare: =TEXTSPLIT(TEXTJOIN(„|”;;E2#);;”|”)

Integrate toate operațiunile într-o singură formulă ar arăta:

=LET(_a; A2:A6; seqa; SEQUENCE(ROWS(_a);;0);
     perechi; MAP(seqa; LAMBDA(poz; TEXTJOIN("|";;
              SCAN(0; DROP(_a;poz); LAMBDA(a;v; IF(a=0; v; 
              IF(IFERROR(TAKE(--TEXTSPLIT(a;";");;-1);v)=v;"";
                   IF(a="";"";TEXTJOIN(";";TRUE;a;v)))))))));
     rezi; TEXTSPLIT(TEXTJOIN("|";;perechi);;"|");
     result; ROWS(rezi);
     result
)

în care în variabila prerechi calculez acel MAP cu SCAN integrat.

O variantă oarecum diferită a acestei soluții este să oprim SCAN-ul în momentul în care o valoare din A se repetă anterior. Exemplificare:

Rezolvare alternativă

În acest context când în prima rundă de scanare apare varianta 3 care este deja mai sus, se oprește scan și trece la secvența următoare. Pentru aceasta folosesc o funcție ușor diferită dar cu aceeași logică.

=LET(_a; A27#; seqa; SEQUENCE(ROWS(_a);;0);
     perechi; MAP(seqa; LAMBDA(poz; TEXTJOIN("|";;
                      SCAN(0;DROP(_a;poz);LAMBDA(a;v; IF(a=0;v; 
                        IF(IFERROR(SUM((--(--TEXTSPLIT(a;";")=v)))>0;0);"";
                            IF(a="";""; TEXTJOIN(";";;a;v)))))))));
     rezi; TEXTSPLIT(TEXTJOIN("|";;perechi);;"|");
     result; ROWS(rezi);
     UNIQUE(rezi)
)

În prima variantă folosesc doar ultima valoare din acumulatorul a iar în varianta 2 compar toate valorile din acumulator cu valoarea curentă. De reținut aici că nu putem utiliza un COUNTIF() într-un SCAN așa că a trebuit să introduc artificiul de a compara oricare valoare din A cu V și transformarea în valori 0,1.

Problema CountTriangles

Problema CountTriangles se referă la identificarea tripletelor dintr-un șir de numere care pot forma triunghiuri valide conform inegalității triunghiului.

Inegalitatea triunghiului afirmă că, pentru trei laturi a,b,c ale unui triunghi, trebuie să se respecte condiția:
a+b>c , b+c>a, c+a>b
Într-un șir sortat, regula devine mai simplă: dacă avem trei elemente A[i],A[j],A[k] unde i<j<k , triunghiul este valid doar dacă: A[i]+A[j]>A[k].

Ca să putem compara oricare 3 elemente dintr-un vector avem nevoie de matricea de triplete introdusă în articolul Modele de algoritmi in #Excel – Sorting (6). De asemenea, problema triunghiurilor a mai fost tratată în articolul menționat la Problema Triangle. De asemenea, a fost descrisă puțin mai pe larg în articolul Programarea funcțională în Excel Modern din pinmagazine.ro una din ultimele reviste de IT, pe care le cunosc eu, disponibilă și offline.

Scopul matricei de triplete este de a genera toate combinațiile unice de câte 3 valori corespunzătoare indexului valorilor pozițiilor unui vector A cu începere de la 1. În articolul din PIN Magazine scriam că această matrice este echivalentului unui triplu FOR din programare. Reamintesc funcția de generare a matricei de indecsi unici.

=LET(sir;R1;split; TEXTSPLIT(sir;;";"); nrElemente; ROWS(split);
          combinatii; COMBIN(nrElemente; 3);
TripleG; SCAN("1;2;2"; SEQUENCE(combinatii);
   LAMBDA(a;v;
       LET(arr; --TEXTSPLIT(a;";");
           x; TAKE(arr;;1); y; TAKE(TAKE(arr;;2);;-1); z; TAKE(arr;;-1);
           newx; IF(AND(y=nrElemente-1; z=nrElemente); 
                        IF(x<nrElemente-2; x+1; x); x);
           newy; IF(newx=x; IF(z=nrElemente; 
                        IF(y<nrElemente-1; y+1; y);y); newx+1);
           newz; IF(newx=x; IF(z<nrElemente;z+1;y+2); newy+1);
newx&";"&newy&";"&newz)));
tabf; --TEXTSPLIT(TEXTJOIN("|";;TripleG);";";"|");
tabf)

Această funcție este optimizată în raport cu numărul de elemente din șirul din R1 în cazul acesta pentru că suportă până la 185 de elemente în șir față de 101 în varianta din articolul 6.

Proiecție problemă în Excel

CountTriangles în Excel.

Formula din K3 este bazată pe o variantă a articolului 6 și 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 )));
     sVals; UNIQUE(MAP(vals; LAMBDA(v; TEXTJOIN(";"; ; SORT(--TEXTSPLIT(v;;";"))))));

     Split; --TEXTSPLIT(TEXTJOIN("|";1;sVals);";";"|");
     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; TAKE(FILTER(tabf; TAKE(tabf;;-1)=TRUE;"0");;3);

     triplef)

În care în prima parte se generează matricea de căutare, după care prin funcția recursivă fReqv se indexează vectorul de valori A cu scopul de a determina inegalitățile de tipul a+b>c solicitate de problemă. În felul acesta determinăm că anumite combinații de numere pot reprezenta triunghiuri din punct de vedere geometric.

Problema MinAbsSumOfTwo

Având în vedere că este o problemă cu două valori dintr-un vector care trebuie comparate, soluția optimă în Excel este utilizarea unui MAKEARRAY().

Problema constă într-un vector de numere a cător sumă în format aboslut trebuie să fie cea mai mică. Formal, dacă avem un array A de dimensiune N, trebuie să determinăm: min(|A[i]+A[j]|) unde 0≤i≤j<N.

Proiecția problemei în Excel:

Problema MinAbsSumOfTwo în Excel

în care formula din C3 este:

=LET(arr; A2:A6; rr; ROWS(arr);
    MAKEARRAY(rr;rr;LAMBDA(r;c; 
           IF(r>c;"";ABS(INDEX(arr;r)+INDEX(arr;c))))))

în care matricea generată cu MAKEARRAY indexează vectorul arr pentru fiecare combinație de linie și coloană.

O variație a acestei probleme care nu există pe Codility este să determin mimimul sau maximul absolut a trei valori din vector. Această problemî în Excel ar arăta:

în care am utilizat din nou matricea de triplete, dar de data aceasta cu scopul de a păstra toate valorile posibile în matrice nu doar valorile unice.

Formula din D2 devine astfel:

=LET(arr;A2:A6;
     seq; SEQUENCE(ROWS( arr));
     matrix; LET(vector; arr;
                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)*(CHOOSECOLS(tabi;3)<=nr));
   unicsm; CHOOSECOLS(unics;1;2;3);
   TAKE(tabi;;3));

summax; BYROW(matrix;
        LAMBDA(r; LET(x; TAKE(r;;1);
                      y; TAKE(TAKE(r;;2);;-1);
                      z; TAKE(r;;-1);
                    ABS(INDEX(arr;x)+INDEX(arr;y)+INDEX(arr;z)))));

HSTACK(matrix; summax))

Cam atât pentru astăzi… să aveți o săptămână productivă și

Sper să fie util cuiva!

Blog la WordPress.com.

SUS ↑