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!

Modele de algoritmi in #Excel – Counting Elements (4)

Acest articol continuă seria de articole cu propuneri de rezolvare a algoritmilor clasici din informatică în Excel. În continuare voi explica și rezolva probleme specifice de la adresa: https://app.codility.com/programmers/lessons/4-counting_elements/

Algoritmii de pe Codility și poate de pe multe alte site-uri sunt adresați în special limbajelor de programare, Excelul nefiind un instrument dedicat rezolvării acestor tipuri de probleme, dar în versiunile de cloud Microsoft Excel 365 conține suficiente funcții care îmbinate pot rezolva probleme destul de complexe. În această serie de articole vreau și eu să văd până unde se poate ajunge.

Puțină teorie

În Excel sunt implementate foarte multe funcții de căutare, de la cele mai simple la combinații care permit abordarea vectorilor (celule de date) în diferite formate.

În primul rând pentru a răspund multor probleme de informatică trebuie să descompunem un șir de elemente în componentele sale, de cele mai multe ori pe baza unui delimitator.

În imagine câteva exemple de funcții de căutare și sortare uzuale în Excel 365:

Funcții de căutare în Excel.

Vă reamintesc că pentru apelarea dinamică a unui vector vom utiliza formatul Celula# în toate funcțiile din exemplu fiind referit vectorul A5# care presupune oricâte linii o fi având acesta, nefiind cunoscută din stard dimensiunea șirului de valori din A2.

Un aspect de noutate teoretică în abordarea acestui articol este generarea de tabele de date (matrici) cu ajutorul funcției MAKEARRAY():

Makearray() în Excel.

în care avem parametrul 1 numărul de linii care să se genereze, parametrul 2, numărul de coloane și funcția LAMBDA aplicabilă, în care parametrul r este echivalent liniei curente din matrice iar c este echivalentul coloanei curente.

În acest articol referim algoritmul clasic Counting Elements care este o tehnică utilizată în informatică și în special în algoritmică pentru sortare și căutare. Acesta este utilizat pentru a număra numărul de apariții ale fiecărui element dintr-o colecție de date și pentru a efectua diverse operații pe baza acestor numărători. În primă lectură putem crede că ar fi vorba de un COUNTIF() pe un vector de valori pe baza unor valori unice.

În articolul Modele de algoritmi in #Excel – Vectori (2.2) am descris în ultimul update cum determinăm printr-o funcție numărul de apariții a fiecărui element.

Algoritmul Counting Elements este adesea utilizat ca parte a altor algoritmi mai complecși sau în combinație cu alte tehnici de algoritmă pentru a rezolva diverse probleme.

Să trecem la probleme!

Problema FrogRiverOne

Recunosc că până acum mi-a fost cel mai greu de înțeles ce ar dori să calculeze acest algoritm.

FrogRiverOne este o problemă clasică de algoritmă și este parte a algoritmului Counting Elements. Această problemă implică o broască care dorește să traverseze un râu folosind frunze care plutesc pe suprafața apei. Scopul nostru este să determinăm cea mai mică poziție pe malul râului (secunda) de unde broasca poate sări astfel încât să ajungă dincolo de râu într-un timp minim.

Mai specific, avem un râu cu o lățime dată (X) și un șir de numere (A) care reprezintă pozițiile frunzelor care plutesc pe suprafața râului. Fiecare număr din șir reprezintă o poziție pe malul râului, corespondent cu secunda / timpul necesar până când frunza ajunge la acea poziție. Broasca poate sări doar atunci când există cel puțin o frunză pe fiecare poziție a râului, de la 1 la X, unde X este lățimea râului.

De exemplu, având un râu de lățime 5 și șirul [1, 3, 1, 4, 2, 3, 5, 4], broasca poate traversa râul atunci când există frunze pe pozițiile 1, 2, 3, 4 și 5. Astfel, cel mai mic număr de pași necesari pentru ca broasca să traverseze râul este 5, iar aceasta poate sări de pe poziția 6 locul în care apare pentru prima dată valoarea 5, celelalte valori de la 1 la 5 fiind deja întâlnite până la acea secvență.

Pentru a realiza acest lucru, putem folosi un tabel de frecvență pentru a număra câte frunze avem pe fiecare poziție a râului. Apoi, parcurgem șirul de frunze și verificăm când toate pozițiile de pe malul râului sunt acoperite. Odată ce aceasta se întâmplă, găsim cea mai mică poziție de pe malul râului și returnăm acest rezultat.

Rezultate intermediare:

Propunere de rezolvare FrogRiverOne cu MAKEARRAY în Excel.

în C6: =SEQUENCE(;B1) am generat secvența de pași (1, X) care reprezintă lățimea râului.

în B7: =TRANSPOSE(B3#) am returnat șirul de numere (frunzele) în ordinea lor de apariție.

în C7 am realizat un tabel în care verific fiecare valoare din B7# și poziția sa în zona C6#. Fiecare linie din tabel reprezintă o secundă în acest algoritm.

Funcția pentru generare dinamică tabel:

=MAKEARRAY(COLUMNS($B$3#);$B$1;
     LAMBDA(r;c;
		LET(valc; INDEX($C$6#;;c);
		    valr; INDEX($B$7#;r);
			IF(valr=valc;1;0))))

în care folosesc funcția INDEX() pentru a aduce valoarea de pe linie și de pe coloană pentru a le compara ulterior. Dacă cele două valori X și frunza sunt egale atunci returnez 1, dacă nu 0.
În imagine un exemplu vizual:

Exemplificare vizuală MakeArray()

Pasul următor este să determin numărul de apariții pe fiecare coloană în așa fel încât odată apărută valoarea 1 pe linia 1 să fie vizibilă și pe linia 2, ar dacă apare de două ori să se adune.

Practic pentru am mers pe abordarea de a folosi funcția BYCOL() cu SCAN() doar că cele două funcții nu se pot utiliza împreună în cadrul unui LET() cu alte valori dinamice. Și de aici au pornit exporările pentru a găsi o soluție. Salvarea a venit din articolul: Unable to use BYCOL and SCAN in the same formula

Așadar pentru a calcula pe coloană numărul de apariții a fiecărei valori, am folosit în M7: funcția:

=LET(n; ROWS(C7#);
     m; COLUMNS(C7#);
     rows; MAKEARRAY(n; m; LAMBDA(r;c; r));
     cols; MAKEARRAY(n; m; LAMBDA(r;c; c));
     MAP(rows; cols;
         LAMBDA(r;c;
            SUM(INDEX(C7#; 1; c):INDEX(C7#; r; c)) ))
   )


în care: n este numărul de linii ale tabelului inițial și m, numărul de coloane. După care realizez două tabele independente unul cu liniile și altul cu coloanele în așa fel încât să le pot parcurge cu MAP(). În partea de calcul fac o sumă pe două rezultate din INDEX() până la celălalt INDEX().

Din păcare versiunea aceasta este prea abstractă pentru a fi integrată mai tarziu într-o funcție finală, așa că am folosit mai jos versiunea un pic mai complexă.

Ca să pot determina linia (secunda) în care sunt acoperite toate pozițiile râului (X-urile), am folosit în V7 funcția: =BYROW(M7#;LAMBDA(r;IF(COUNTIF(r;0)=0;”x”;0))) în care identific linie cu linie dacă numărul de 0-uri (poziții neacoperite) este egal cu 0. Atunci marchez cu „x”. Poziția lui x pe această coloană rezultat este de fapt numărul secundei în care broasca poate traversa râul. În W7 determin secunda: =LET(poz; IFNA(MATCH(„x”;V7#;0);-1); IF(poz=-1;-1;poz-1)) în care MATCH() caută primul x în rezultatul din V7 apoi dacă nu se găsește nici un X înseamnă că problema este fără soluție de aceea trebuie să returneze, conform algoritmului, valoarea -1.

Alternativa la funcția din M7 este:

=LET(set; C7#; 
	m; COLUMNS(set); 
	seq; SEQUENCE(m;1);
	CUMULATE; LAMBDA(x; SCAN(0; x; LAMBDA(acc;item; acc+item)));
	DROP(REDUCE(0;seq; LAMBDA(acc;idx; HSTACK(acc; CUMULATE(INDEX(set;;idx)))));;1)
)

Având în vedere că funcția nu-mi aparține am lăsat întocmai numele variabilelor. În această funcție determinăm setul de valori ca rezultat din C7# după care calculăm în m numărul de coloane apoi generăm o secvență seq cu numărul de coloane m. Cheia este în variabila CUMULATE care declară un fel de funcție recursivă de scanare, utilizată în DROP-ul cu REDUCE din partea finală a LET-ului.

În mod cumulat funcția finală de rezolvare a problemei este:

=LET(Xs; SEQUENCE(;B1);
sir; TRANSPOSE(--TEXTSPLIT(B2;","));
tabi; MAKEARRAY(ROWS(sir);$B$1;LAMBDA(r;c;LET(valc; INDEX(Xs;;c);valr; INDEX(sir;r);IF(valr=valc;1;0))));
tabfin; LET(set; tabi;
m; COLUMNS(set);
seq; SEQUENCE(m;1);
CUMULATE; LAMBDA(x; SCAN(0; x; LAMBDA(acc;item; acc+item)));
DROP(REDUCE(0;seq; LAMBDA(acc;idx; HSTACK(acc; CUMULATE(INDEX(set;;idx)))));;1)
);
verif; BYROW(tabfin; LAMBDA(r; COUNT(FILTER(r;r=0))));
rezfin; LET(poz; IFNA(MATCH(0;verif;0);-1); IF(poz=-1;-1;poz-1));
rezfin)

în care:

  • Xs este lățimea râului din B1;
  • sir este A-ul, lista de valori / poziții în care cad frunzele în fiecare secundă;
  • tabi este primul tabel de tip matrice în care avem fiecare frunză cu poziția ei, echivalentul tabelului din C7#;
  • tabfin este matricea de calcul cumulativă a aparițiilor frunzelor la fiecare salt, echivalentul tabelului din M7;
  • verif este variabila care calculează numărul de apariții ale lui 0 pentru fiecare linie din tabfin;
  • rezfin este valoarea cea mai mică a secundei in care broasca poate traversa râul. Rezfin întoarce valoarea -1 în cazul în care nu sunt frunze pentru toate pozițiile în șirul specificat.


Problema PermCheck

Avem la dispoziție un șir A format din N numere întregi. Scopul este să determinăm dacă acest șir reprezintă o permutare validă a numerelor de la 1 la N.

O permutare validă înseamnă că șirul conține exact o dată fiecare număr întreg de la 1 la N, fără a exista duplicări și fără a lipsi vreun număr. În plus, ordinea numerelor în șir nu contează, astfel că o permutare validă poate avea numerele în orice ordine.

Pentru a rezolva această problemă, putem folosi o abordare simplă și eficientă. Vom construi un set care să conțină numerele de la 1 la N și vom parcurge șirul A, verificând dacă fiecare număr întreg este prezent în set. Dacă toate numerele de la 1 la N sunt prezente în șirul dat și nu există duplicări, atunci considerăm că șirul este o permutare validă și returnăm 1. În caz contrar, returnăm 0 pentru a indica că șirul nu este o permutare validă.

Această abordare are o complexitate a timpului de O(N), unde N reprezintă lungimea șirului dat A, deoarece trebuie să parcurgem șirul o singură dată și să verificăm fiecare element în set.

Personal am ales să rezolv problema prin includerea duplicatelor și în șirul original și în șirul de comparare, pentru a o face puțin mai complicată.

Problema PermCheck în Excel.

În exemplul meu, sir2 este o permutare (aceleași elemente ca și valoare și număr de apariții) a șirului 1. Șir 3 nu este o permutare pentru că lipsește valoarea 2, iar șir 3 nu are același număr de elemente ca Sir1.

Pentru a rezolva problema de permutări pentru două șiruri date folosind o singură funcție atunci putem folosi:

=LET(so; SORT(--TEXTSPLIT(A2;;","));
     sc; SORT(--TEXTSPLIT(A5;;","));
     IFS(SUM(--IFNA(so=sc;0))<>ROWS(so);0;SUM(--(so=sc))=ROWS(so);1))

în care so este șirul original iar sc este șirul de comparare. Ca funcție de verificare am folosit funcția IFS() pentru a determina în primă fază daca nummărul de linii a celor două șiruri este egal, apoi a doua condiție este de a verifica dacă fiecare linie a șirurilor, sortate crescător este identic. După cum observăm din imagine, în comparația S1 vs S3 vedem că valoarea 2 este lispă în Sir 3 ceea ce rezultă că S3 nu este o permutare de S1.

În practică PermCheck poate fi folosit pentru a verifica dacă un utilizator a ales dintr-o listă de valori toate opțiunile disponibile indiferent de ordinea lor. Rezolvarea mea corespunde și șirurilor cu valori unice.

Problema MaxCounters

MaxCounters este foarte asemănătoare cu FrogRiverOne doar că de data aceasta ni se solicită calcularea numărului maxim de apariții a unui element într-un total într-un anumit număr de iterații (N).

Pentru rezolvarea pas cu pas am descompus A-ul în C2# apoi am generat două secvențe pe orizontală și verticală, după care compar coloanele din rezultatele intermediare după care adaug valoarea 1 la cea anterioară.

Problema solicită determinarea maximului de apariții ale unui număr ceea ce în cazul nostru este de 4.

Funcția adaptată după același algormitm ca la FrogRiverOne este:

=LET(Xs; SEQUENCE(;B1);
    sir; TRANSPOSE(C2#);
    tabi; MAKEARRAY(ROWS(sir);$B$1;LAMBDA(r;c;LET(valc; INDEX(Xs;;c);valr;        INDEX(sir;r);IF(valr=valc;1;0))));
    tabfin; LET(set; tabi;
                m; COLUMNS(set);
                seq; SEQUENCE(m;1);
                CUMULATE; LAMBDA(x; SCAN(0; x; LAMBDA(acc;item; acc+item)));
                DROP(REDUCE(0;seq; LAMBDA(acc;idx; HSTACK(acc; CUMULATE(INDEX(set;;idx)))));;1)
);
 MAX(tabfin))

în care tabfin calculează tabelul echivalent din C5.

Problema MissingInteger

Problema MissingInteger implică găsirea celui mai mic număr întreg pozitiv care lipsește dintr-un șir dat de numere întregi. În cazul în care șirul este consecutiv, atunci va returna următoarea valoare întreagă consecutivă.

În rezolvarea pas cu pas:

Problema MissingInteger

în B2: am descompus șirul original și l-am sortat creascător: =SORT(UNIQUE((–TEXTSPLIT(B1;;”,”))))

În C2: am generat șirul cu valorile întregi cuprinse între minimul și maximul din B2: =SEQUENCE(MAX(B2#);;MIN(B2#))

În D2: am calculat dacă există sau nu fiecare număr din C2# în B2# =IFNA(XMATCH(C2#;B2#;0);”x”)

Rezolvarea dintr-o singură funcție este foarte asemănătoare problemei PermMissingElem explicată în articolul: Modele de algoritmi in #Excel – Time complexity (3)

Funcția finală este:

=LET(arr; SORT(--TRIM(TEXTSPLIT(B1;;",")));
	  all; SEQUENCE(MAX(arr)-MIN(arr)+1; ;MIN(arr); 1);
      vCheck; IFNA(XMATCH(all; arr;0 ); "x");
TEXTJOIN(", ";;FILTER(all;vCheck="x"; IF(MIN(all)<0;1;MAX(all)+1))))

în care:

  • arr este valoarea șirului A specificat, sortat ascendent, echivalentul lui B2;
  • all este valoarea șirului întreg, echivalentul lui C2;
  • vCheck este vectorul de comparație șiruri, prin returnare valoare „x” în cazul în care unul sau mai multe numere întregi sunt lipsă.
  • funcția TEXTJOIN() este folosită pentru a afișa toate numerele întregi lipsă dintr-un șir dat.

Această metodă este foarte utilă în practică pentru a determina de exemplu dacă avem numere lipsă dintr-un șir dat (exemplu coduri, sau zile calendaristice, etc)

Cam asta a fost pentru acest algoritm. Sper să fie util cuiva!

Nu uitați să folosiți secțiunea de comentarii dacă aveți opinii legate de subiect sau propuneri de îmbunătățire.

Modele de algoritmi in #Excel – Vectori (2.1)

Pentru aceast articol am ales să rezolv problema vectorilor (arrays) în Excel. Sursa de inspirație este https://app.codility.com/programmers/lessons/2-arrays/cyclic_rotation/

Un pic de teorie

Termenul de vector este des întâlnit în matematică/geometrie, fizică și nu numai. În Excel sau informatică în general se folosește de cele mai multe ori termenul englezesc array. De multe ori problemele de vectori au legătură cu șirurile de numere sau caractere în general.

În Excel un vector poate fi un șir de caractere/numere parsabile sau o linie, sau o coloană cu valori multiple, sau chiar un bloc de căsuțe NxM care poate fi parcurs. Problemele de vectori se referă în general la manipularea acestora (parcurgere, extragere valori, numărare elemente, etc).

Dacă avem un șir de valori într-o celulă, acesta poate fi descompus după unul sau mai mulți delimitatori. Una din funcțiile de bază pentru descompunerea unui șir în celule este: TEXTSPLIT() dacă identificăm unul sau mai mulți delimitatori sau o combinație de MID() cu SEQUENCE() prezentată în articolul trecut în cazul în care nu avem un delimitator definit.

Exemple:

Funcțiile TEXTSPLIT() și MID() cu SEQUENCE()

În formula din C2 folosim TEXTSPLIT() cu delimitatorul , (virgulă). De menționat că se pot pune mai mulți delimitatori în cadrul aceleiași formule.

În C5 am folosit funcția MID() combinată cu SEQUENCE() de LEN() de valoare pentru a descompune literă cu literă textul. UPPER() este pentru a transforma toate literele în litere mari.

O funcție matematică foarte importantă în acest context este funcția MOD() care determină restul împărțirii a două numere între ele. Aceasta ne ajută în general în probleme cu ciclicitate pe același interval de valori. Restul împărțirii unui număr la el însuși este întotdeauna 0: MOD(n;n) = 0. Dacă dorim să forțăm o secvență de numere în care trebuie să obținem valoarea numărului, atunci va trebui să facem un artificiu matematic pentru acest lucru: MOD(n-1; n)+1 = n.

Funcția MOD() în Excel. Exemplu cu -1+1

în care: avem o secvență de 16 numere în care dacă rulăm MOD(n;8) obținem valori de la 1 la 7 după care 0. Dacă dorim să realizăm numere repetitive de la 1 la 8 atunci va trebui să utilizăm: MOD(n-1;8)+1 în care n-1 va determina pentru n=8 valoarea 7 care în MOD cu 8 devine 7 la care adăugăm 1 pentru a se transforma în 8.

CyclicRotation în Excel

Așa cum specificam la începutul seriei, provocarea este de a rezolva probleme clasice de algoritmică în Excel. În zona de vectori o problemă clasică este aceea de a roti repetitiv elementele sale după un anumit K dat, în care K este numărul de rotații. Numărul de valori ale vectorului care se rotesc este în această problemă valoarea 1, ceea ce înseamnă că la fiecare execuție nouă ultima valoare devine prima în vector.

CycleRotation in Excel, model de implementare pas cu pas.

în care:

B5: =TEXTSPLIT(A2;”,”) – descompun șirul într-un vector pe linie.

B6: =LET(arr; B5#; last; TAKE(arr;;-1); firsts; TAKE(arr;;COLUMNS(arr)-1); HSTACK(last;firsts))

În care preiau rezultatul split de pe rând anterior. Vectorii dinamici rezultați din execuția funcțiilor dynamic array se pot apela cu formatul celula_start# referind toate liniile sau coloanele rezultat. Funcția TAKE() cu parametrul -1 o folosesc pentru a prelua ultima valoare din dreapta vectorului. Următoarea funcție TAKE() care compune variabila firsts este folosită pentru a prelua numărul de elemente ale vectorului determinat de funcția COLUMNS() -1 care este cel preluat deja în variabila last . Pentru a da noua stare a șirului folosesc funcția HSTACK() în care combin cele două variabile rezultat.

Formula din B6 se copie în jos cu drag-and-drop pentru oricâte iterații sunt necesare. Eu am afișat numărul de iterații pe coloana A printr-o funcție SEQUENCE( de K).

C2: și E2: sunt doar formule care nu au implicații în acest model de rezolvare.

Unificarea într-o singură funcție

Pentru a rezolva problema de rotație pentru orice șir de numere la orice K dat, inclusiv K negativ, atunci când vectorul s-ar muta în sens descerscător am propus o rezolvare care ia în calcul restul împărțirii K la lungimea vectorului. În imagine:

C2: =COLUMNS(B5#) – determinarea lungimii vectorului descompus.

E2: =MOD(B4-1;C2)+1 – Funcția MOD de determinare a restului împărțirii.

Logica este că la fiecare pas se realizează incrementarea cu 1 a vectorului față de K. Asta înseamnă că șirul ajunge în poziția inițială în momentul în care lungimea vectorului este egală cu K sau multipli de K. Ceea ce rezultă că mișcările efective sunt doar în zona de rest a împărțirii lui K cu numărul de coloane ale blocului.

Funcția propusă:

=LET(arr; TRIM(TEXTSPLIT(A2;","));
vk; B4;
vMod; MOD(vk;COLUMNS(arr));
IF(vMod=0;arr;HSTACK(TAKE(arr;;-vMod);TAKE(arr;;COLUMNS(arr)-vMod))) )

În care:

  • arr – este variabila care stochează descompunea șirului într-un vector. Forma din imaginea de mai sus este reprezentarea în localizarea Romanian a blocului.

Hint: într-o funcție oarecum mai complexă dacă dorim să vedem rezultate intermediare ele apar ca tool-tip pe funcție în Excel sau se pot afișa direct în construcția formulei folosind tasta F9:

Afișare valori intermediare în timpul depanării unei funcții.
  • vk – preia valoarea lui K din celula B4
  • vMod – calculează restul împărțirii dintre K și numărul de coloane a șirului arr
  • IF-ul tratează excepțiile când vMod ia valoarea 0 ceea ce înseamnă că lungimea vectorului este egală cu K, deci afișez vectorul, iar dacă vMod nu este 0 se afișează cele două șiruri construite cu HSTACK() asemănător cu versiunea din B6 dar puțin mai concentrată funcția.

În formatul cu K negativ, șirul se descompune din stânga spre dreapta, prima valoare din stânga devenind ultima valoare din dreapta. Șirul: {1, 2, 3, 4, 5, 6, 7, 8} devine în -2 rotații: {3, 4, 5, 6, 7, 8, 1, 2}.

Rotația șirurilor de K ori în seturi specifice

Oare de ce nu am complica puțin problema?! Adică în loc de valoarea 1 să introducem un număr nou care să reprezinte un set de numere cu care să treacă la iterația următoare. Aici probocarea a fost puțin mai mare pentru că lucrurile merg destul de rău cu numere de rotații și seturi de numere care depășesc lungimea șirului.

Rotație șiruri după un K și set cunoscut.

Pentru rezolvare a trebuit să calculez și MOD-ul de set ca rest al împărțirii setului dat la lungimea șirului, în așa fel încât dacă setul este mai mare ca șirul să se rotească doar restul valorilor rămase. De exemplu dacă am avea un șir: {1, 2, 3, 4, 5, 6, 7, 8} într-un set de 9 ar însemna să ne întoarcem de la capătul șirului cu numărarea până la nouă, ceea ce înseamnă că avem un rest de 1. Șirul rezultat într-o rotație (K=1) ar fi:  {8, 1,  2,  3,  4,  5,  6,  7}

Ca să pot calcula totuși într-o singură funcție avem nevoie de MOD(ModK * ModSet; lungimea șirului). Funcția din G2 este în exemplul meu: =MOD(E2*F2;C2)

Rotațiile pe fiecare K în seturi specifice numere naturale pozitive sau negative, se pot rezolva cu funcția:

=LET(arr;TEXTSPLIT(A2;",");
vk;B4;
vSet;B5;
vLen;COLUMNS(arr);
vModk;MOD(vk;vLen);
vModSet;MOD(vSet;vLen);
vMod;MOD(vModk*vModSet;vLen);
final;IF(vMod=0;arr;HSTACK(TAKE(arr;;-vMod);TAKE(arr;;vLen-vMod)));final)

în care:

  • arr – preia șirul splitat;
  • vk – preia valoarea lui K;
  • vSet – preia valoarea setului;
  • vLen – calculează lungimea vectorului;
  • vModK – calculează restul înmpărțirii lui K la lungime vector;
  • vModSet – calculează restul înmpărțirii setului la lungime vector;
  • vMod – calculează restul împărțirii celor două resturi anterioare la lungime;
  • final – este calcului de final în care folosesc aceeași tehnică specificată anterior.

Cel mai dificil a fost să ajung la acel MOD de MOD-uri, în rest manipulare de valori simplă.

Dincolo de exercițiu matematic, la ce putem folosi? Eu mă gândesc la criptografie dar și la schimbul de ture pentru angajați sau alocarea de tickete și poate și altele.

În articolul viitor voi trata tot o problemă de vectori, poate puțin mai simplă: OddOccurrencesInArray

Sper să fie util cuiva!

Blog la WordPress.com.

SUS ↑