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

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

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

Să începem ca de obicei cu …

Un pic de teorie

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

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

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

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

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

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

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

Rezolvare numere Fibonacci în Excel.

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

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

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

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

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

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

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

Problema FibFrog sau Salturile Fibonacci prin matrice

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

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

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

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

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

Reprezentare grafică, captură video Youtube.

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

Rezolvarea problemei în Excel pas cu pas.

Rezolvare problema FigFrog pas cu pas.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

     valori);

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

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

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

Aici varianta video pas cu pas.

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

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

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

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

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

Problema ChocolatesByNumbers

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

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

Parametrii:

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

Exemplu:

Să considerăm cazul în care:

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

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

Observații:

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

Rezolvare în Excel

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

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

=A2/GCD(A2;B2)

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

Exemple practice?

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

Problema CommonPrimeDivisors

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

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

Parametrii:

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

Exemplu:

Să presupunem că:

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

Analiza pentru fiecare pereche:

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

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

Rezolvare în Excel

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

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

Textul funcției:

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

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

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

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

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

Rezolvarea problemei în Excel

Rezolvarea aceasta este pentru prima pereche de numere.

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

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

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

1
0,598701901
0,409665529

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

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

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

Modele de algoritmi în #Excel – Sieve of Eratosthenes (11)

Acest articol este mai mult o continuare a articolului cu numere prime Modele de algoritmi în #Excel – Prime and composite numbers (10). Nu prea înțeleg eu ce le plac atât de mult matematicienilor numerele prime, dar eu îmi aduc aminte, fără prea multă nostalgie, despre genul ăla de probleme de la matematică: Demonstrați că 1013 este prim. Jdemii de împărțiri și iar împărțiri. Cred că asta era singura metodă.

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

Dar să continuăm un pic partea de data trecută de ….

Un pic mai multă de teorie

Menționam în articolul anterior că există mai multe metode de a determina dacă un număr este prim sau nu. Prima metodă antică de determinare a numerelor prime este cunoscută sub numele de Ciurul sau Sita lui Eratostene. La informatica de liceu acest algoritm este unul de bază și sunt tot felul de metode de rezolvare. Doar că în Excel nu putem trata valorile viitoare dintr-un vector sau tabel. Orice funcție, chiar dacă este dinamică sau nu, oferă rezultat doar pentru valoarea sau poziția curentă, chiar dacă se poate raporta la toate elementele vectorului sau doar anumite segmente. O funcție dinamică scrie pe tot blocul, fără să poată face iterații de a reveni la un rezultat deja calculat. Odată ce înțelegi asta, realizezi că aplicarea algoritmului lui Eratostene nu prea are aplicabilitate în programarea funcțională în Excel.

Alternativa de a genera toate numerele prime până la un N dat este foarte simplă în Excel.

Determinarea tuturor numerelor prime până la un N dat.

În rezolvarea problemei am utilizat o secvență cu start de la 2 în C2 după care o funcție pentru a calcula pe întreaga secvență fiecare număr prin inpărțire la numerele anterioare. Funcția propusă:

=LET(n; A2;
     arr; C2#;
     freq; LAMBDA(x; LET(arrn; TAKE(arr;x-2);
                         deimpartit; SUM(--(x/arrn-INT(x/arrn)=0));
                        --IFERROR(deimpartit; FALSE)));
    rez; MAP(arr; LAMBDA(v; IF(freq(v)=0;"Prim";"Compus")));
  rez)

în care am definit funcția recursivă freq pe care o apelez în MAP pentru a scana secvența generată în C2#. Variabila deimpartit este cheia în abordare pentru că verifică dacă valoarea curentă (x) împărțită la valorile anterioare arrn obținut prin acel TAKE() minus întregul împărțirii este egal cu zero. Valoarea inițială generează TRUE sau FALSE după caz, după care prin –() transform valorile TRUE, FALSE în 1 și 0 pe care le însumez. Acest artificiu îmi spune în MAP-ul de mai jos dacă numărul este prim sau nu prin raportare la valoarea 0. O nimica toată! :)

Normal metoda propusă este limitată la numărul de linii din Excel. Ca să pot totuși să fac anumite optimizări am încercat să aplic tehnicile din Ciurul lui Atkin, dar îmi dă cu multe virgule. Totuși am studiat un liceu economic și o facultate de contabilitate… nu mă ajută procesorul mai mult. :)

Ca să pot totuși optimiza prin implementarea algoritmului Prim-Dijkstra de raportare la radicalul numărului N dar și o oarecare forțare a ciurului lui Eratostene printr-o mică forma optimizată a lui Atkin de segmentare, plus optimizarea numerelor de verificat prin generare doar de secvențe de numere impare, am ajuns la o matrice pe care o denumesc Matrice de proiecție Eratostene – Dijkstra.

Matrice de proiecție Eratostene - Dijkstra

În propunerea am realizat o matrice cu numărul de linii ale sAll care este secvența de numere începând cu 3 și numărul de coloane echivalent radicalului din N-ul dat. Apoi împart fiecare valoare de pe sAll la fiecare valoare de pe sRadical. Funcția de generare a matricei este:

=MAKEARRAY(ROWS(E2#); ROWS(F2#); 
    LAMBDA(r;c; IF(r=c;"x";IF(INDEX(E2#;r)/INDEX(F2#;c)-  
                          INT(INDEX(E2#;r)/INDEX(F2#;c))>0;INDEX(E2#;r);0))))

în care r mă ajută să caut valorile de pe coloana sAll iar c valorile de pe sRadical. În cazul în care s/r sunt egale nu le tratez ci terurnez acel „x”. Dacă apar pe liniile matricei valori 0 înseamnă că acel număr are divizori înaintea sa. În U2 calculez apoi câți de zero sunt pe o linie cu un simplu countif() integrat într-un BYROW: =BYROW(K2#; LAMBDA(r; COUNTIF(r; 0))). Dacă numărul de 0 este 0 acel număr este prim.

Tehnica propusă funcționează destul de bine la valori mici… dar Excelul începe să returneze mesaje de eroaroare că nu poate procesa funcția la N-uri mai mari.

Într-o abordare oarecum mai fabuloasă… de dragul exercițiului am încercat să forțez un SCAN să facă treaba matricei, dar optimul în rulare este tot la numere mici.

Am încercat să abstractizez cât mai mult prin funcții recursive în funcții recursive și merge… dar tot pentru numere mici. În G avem lungimea în caractere a rezultatului. După anumită valoare a lui N nu mai rezultă nimic. Funcția de dragul exercițiului este:

=LET(_s1; E2#; _s2; F2#;
freq; LAMBDA(x;yarr; --(yarr/x-INT(yarr/x)<>0));
ffilter; LAMBDA(xarr;z; FILTER(xarr; freq(z; xarr)=1));
SCAN(""; _s2; LAMBDA(a;v; IF(v=3; TEXTJOIN(";";TRUE;ffilter(_s1; 3)); TEXTJOIN(";";TRUE;ffilter(TEXTSPLIT(a; ;";"); v))))))

SCAN-ul mă ajută să pot păstra rezultatele rulării anterioare, un fel de for cu acumulare… dar cum menționam și în articolele anterioare, SCAN nu returnează tabele, de aceea trebuie păstrate permanent datele concatenate după care le analizăm mai jos splitate. Funcția recursivă freq o folosesc în recursiva ffilter pentru a putea calcula în funcție de valoarea curentă de pe S2 împărțirea a cea a rămas și verificarea dacă este sau nu egală cu 0. ffilter păstrează doar rezultatele cu 1, adică numerele care în rularea anterioară nu au fost deja împărțite.

Până la limitarea de 15 cifre a unui N, ne confruntăm în modelele propuse cu limitarea de 2^20 impusă de numărul de linii Excel sau 2^2*2 dacă aplicăm tehnica doar cu impare.

Satisfăcut de rezultatul cercetării, închei acest pasaj cu un citat din Gabriel Liiceanu: „Trebuie, în tot ce trăiești, să descoperi aurul vieții, nu metalul de rând și nu zgura ei” (detalii … triste aici).

Acestea fiind scrise să trecem la probleme… care sunt mult mai simple decât o expune partea teoretică.

Problema CountNonDivisible

Aceasta este o problemă foarte simplă: Dacă ai un array A de N numere întregi, pentru fiecare element A[i], trebuie să determinăm câte dintre celelalte elemente din array nu sunt divizibile cu A[i].

Rezolvarea problemei:

Problema CountNonDivisible

În partea de proiecție am făcut o verificare dacă numărul 3 se împarte la oricare din valorile de pe vectorul A. În cazul în care este divizibil, restul este 0, ceea ce mă ajută să filtrez apoi în C2 calorile care sunt mai mari ca 0.

Integrată soluția este obținută prin funcția:

=LET(a; A2:A7;
     freq; LAMBDA(x; x/a-INT(x/a));
     MAP(a; LAMBDA(v; TEXTJOIN(", ";;FILTER(a; freq(v)<>0; "x")))))

în care recursiva face ce se întâmplă pe coloana B pentru fiecare x de pe A pe care-l parcurg cu MAP. Ca să pot returna valori pentru fiecare linie a trebuit să integrez în lambda acel TEXTJOIN().

Aplicabilitatea acestei probleme cred că ar fi în analiza de piață.

Problema CountSemiprimes

Semiprim este un numar rezultat din produsul a doua numere prime, nu neaparat distincte. Problema cere calcularea numărului de semiprime pentru un N dat și care se află în anumite intervale de valori P, Q specificate.

Propunere de rezolvare

Problema numărare semiprime.

Am numărul N = 26. Calculăm întâi prin metodele specificate numerele prime până la 26. După care calculăm matricea de semiprime, care este rezultatul înmulțirii fiecărui număr prim cu oricare din celelalte. Valoarea semiprimului nu trebuie să fie mai mare ca N.

Pentru asta în H7 am implementat următoarea funcție:

=LET(prime;C2#;
     rr;ROWS(prime);
     matrice; MAKEARRAY(rr; rr;
          LAMBDA(r;c; IF(INDEX(prime;r)*INDEX(prime;c)>A2; "";   
                        INDEX(prime;r)*INDEX(prime;c))));
coli; SORT(UNIQUE(TOCOL(matrice;3))); matrice)

în care utilizez numerele prime rezultate în vectorul C2# după care cu MAKEARRAY indexez fiecare număr și îl înmulțesc cu fiecare. Variabila finală este de fapt coli care transformă matricea într-un vector (TOCOL) de cu valori unice sortate crescător. Valoarea lui coli este în F7.

În J2 calculez apoi cu COUNT numărul de elemente care se regăsesc între P și Q dat. Funcția din J2:

=COUNT(FILTER($F$7#;($F$7#>=H2)*($F$7#<=I2);""))

este de fapt o filtrare cu dublă condiție care returnează un număr de linii. Dacă nu se îndeplinește condiția returnează nimic „”.

Și cam asta este rezolvarea. Simplu și rapid!

Cam atât pentru astăzi!

Sper să vă fie util!

Blog la WordPress.com.

SUS ↑