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!

Modele de algoritmi in #Excel – Iteratiile (1)

Acest articol face parte dintr-o serie de articole dedicate implementării de algoritmi clasici în Microsoft Excel 365.

Sursa de inspirație este site-ul: https://app.codility.com/programmers/lessons/1-iterations/binary_gap/

Doar puțină teorie

Iterațiile sau structurile de control clasice includ:

  • IF cu sintaxa pseudocod: dacă condiție atunci acțiune_adevăr altfel acțiune_fals
  • FOR cu sintaxa pseudocod: for variabilă in interval_de_valori acțiune
  • WHILE cu sintaxa pseudocod: while condiție acțiune

În Excel în schimb nu există nici FOR nici WHILE implementată ceea ce face oarecum diferită abordarea. O simulare a unui FOR este întâlnită uneori prin utilizarea funcției SEQUENCE() combinată cu funcția SCAN() sau REDUCE(). De exemplu dacă am avea un pseudocod de tipul: pentru fiecare număr de la 1 la 9 adunați numărul cu suma numerelor anterioare. Exemplu:


Problema supusă rezolvării este BinaryGap

Aceasta presupune transformarea unui număr zecimal în număr binar după care trebuie determinate numărul maxim de secvențe de 0 care apar în numărul binar, urmate de valoarea 1.

În exemplu este prezentat numărul 9 care în binar este 1001 și care are un număr de 2 de 0 consecutivi urmați de valoarea 1. În numărul 32 care este în binar: 100000 numărul de 0 nu este urmat de 1 ceea ce înseamnă că rezultatul va fi 0.

Problema de pe Codility oferă un set de numere de test [1..2,147,483,647]. Doar că în Excel ultimul număr zecimal care poate fi transformat în binar este numărul 511, de la 512 încolo obținem eroarea #NUM!

În imagine propuneri de rezolvare.

Menționez că localizarea pe calculatorul meu este localizarea în română ceea ce face ca separatorii de formule să fie ; (punct și virgulă) nu , (virgula).

Ca să transform numărul în binar folosesc funcția DEC2BIN(). Funcția din celula B2 este: =DEC2BIN(A2)

Dacă aș dori să văd câți de 0 sunt în total în număr în D2 am introdus funcția LEN() pentru a determina lungimea șirului rezultat în binar și SUBSTITUTE() pentru a înlocui numerele de 1 cu nimic. Funcția din D2 este: =LEN(B2)-LEN(SUBSTITUTE(B2;”0″;””)).

Dacă ar trebui să determin doar numărul maxim de 0 consecutivi atunci folosesc în E2 funcția: =MAX(SCAN(0;MID(B2;SEQUENCE(LEN(B2));1); LAMBDA(a;v; IF(v=”1″; 0; a+1))))

În care:

SCAN() scanează un array de valori rezultat prin descompunerea valoare cu valoare a numărului binar din B2 pe baza lungimii sale SEQUENCE(LEN(B2)). Funcția SCAN() are nevoie de o funcție LAMBDA pentru calculul valorii finale. În Lambda avem variabilele a – acumulator, și v- valoarea curentă. Dacă valoarea curentă este 1 ca text, pentru vă provine din MID(), atunci acumulatorul va lua valoarea 0, altfel va lua valoarea iterată cu 1. La final aplic un MAX() pentru a determina exact care este cea mai mare secvență de valori 0.

Pentru numărul 401 care în binar este 110010001 rezultatul SCAN() este:

În exemplul lui 401 voi obține valoarea finală 3.

Dacă dorim să răspundem corect la problemă și să numărăm doar valorile 0 care sunt urmate de 1 atunci formula se complică puțin.

În F2 am introdus o funcție LET() ca să pot scrie mai puține caractere. Scopul în algoritmi este să ai un cod cât mai optim și poate mai puține caractere într-o formulă / cod.

=LET(bloc; MID(B2;SEQUENCE(LEN(B2));1); zu; IF(RIGHT(B2)=”0″;0;1); cate; MAX(SCAN(0; bloc;  LAMBDA(a;v; IF(v=”1″; 0; a+1)))); conc; CONCAT(zu;”-„;cate); IF(zu=0;0;cate))

În această funcție am introdus variabila zu pentru a determina dacă la finalul șirului este sau nu valoarea 1. Această abordare are în schimb o eroare de logică pentru că nu tratează excepțiile în care avem totuși valori 0 urmate de 1 în numărul binar și se finalizează cu valoarea 1. Exemplu este numărul 486 care se termină cu 0 dar are 2 de 0 în interior urmați de valoarea 1.

Variantele cu emulare de FOR sunt interesante și pot duce la soluții destul de frumoase. Doar că în rezolvarea problemelor mai complexe de Excel important este să ai formule cât mai reduse ca dimensiune pentru a asigura un optim de execuție dar și pentru a face mai ușor depanarea formulelor. În acest sens propun o metodă de rezolvare fără iterații explicite.

Formula din B46 este: =MAX(LEN(DROP(TEXTSPLIT(B46;1);;-1)))

În care:

TEXTSPLIT() – împarte numărul binar în funcție de fiecare apariție a valorii 1 rezultând vectori cu număr de coloane diferite:

În cazul în care numărul splitat nu conține nici un 0 vom vedea o listă de celule vide.
În cazul în care în ultima coloană a fiecărui vector avem valoarea 0 înseamnă că secvența de 0 nu este urmată de valoarea 1.

DROP() – este utilizat pentru a elimina excepția când la final-ul vectorului avem valoarea 0. Elimină ultima celulă din rezultat.

LEN() – numără pentru fiecare celulă rămasă în vector cât de lungă este secvența de 0 rezultatul intermediar fiind de forma:

MAX() extrage din lista de valori valoarea maximă a numărului de 0 consecutivi urmați de valoarea 1.

În Google Spreadsheets nu există funcția TEXTSPLIT() dar există o funcție SPLIT() care nu funcționează asemănător cu cea din Excel.

Cam asta ar fi. Sper să fie util cuiva.

Un model de automatizare raportare in Excel

Zilele acestea sunt foarte aglomerate pe la serviciu. O mulțime de cerințe și problemuțe care trebuie rezolvate cât mai pe loc posibil. Dar între zeci de emailuri a apărut unul care mi-a atras atenția.

Păi putem să refuzăm noi un ajutor în legătură cu Excelul? Păi nu. Așa că lăsăm totul deoparte și ne apucăm de treabă.

Descriere problemei

Se dorește ca în fiecare dimineață, datele agregate dintr-un fișier Excel să fie trimise prin email la o anumită oră către o anumită adresă de distribuție. Soluțiile existente pe internet sunt în mare parte cu VBA și o serie de operațiuni manuale, care implică deschiderea fișierelor. La altele nu m-am uitat, dar cred că se pot face și cu ceva automatizări din Power Automate. Dar cum nu toată lumea are subscripție de cloud, m-am gândit la Task Scheduler-ul de Windows. Și cum nu sunt nici fan VBA nici prea multe despre programare nu știu, m-am decis să abordez problema din PowerShell pe care-l știu de pe vremea când eram administrator de rețea și actualmente îl folosesc în administrarea de SharePoint.

Propunerea de soluție

Propun un exemplu de fișier Excel care conține o funcție de generare aleatorie de numere:

Funcția =RANDBETWEEN(1;49) generează numere aleatorii în cazul meu între 1 și 49 iar în C3 fac suma lor. Orice celulă se modifică se schimbă și numerele și suma lor.

Scriptul de refresh silent a fișierului.

Acest script presupune că aveți deja instalat pachetul Microsoft Excel pe calculator. Cu acest script se deschide fișierul din calea specificată în secțiunea 2. După care accesează prima foaie de calcul (3) și execută refresh la date (4), salvează (5), închide fișierul (6) și Excelul (7) după care eliberează memoria de obiecte (8).

# Actualizare silent date fisier Excel
# Author: Valy Greavu 
# Date: 13.02.2024
# Se recomandă utilizarea în Task Scheduler pentru refresh fișiere din când în când la noile date.

# 1. Se crează o instanță Excel
$excel = New-Object -ComObject Excel.Application

# 2. Se deschide fișierul Excel. Atentie la calea fisierului.
$workbook = $excel.Workbooks.Open("P:\Temp\FisierExcel.xlsx")

# 3. Se activează prima foaie de lucru din Excel în care sunt datele care se actualizează. 
# Item(2) inseamna a doua foaie de lucru in ordine.
$worksheet = $workbook.Sheets.Item(1)
$worksheet.Activate()

# 4. Se realizează refresh la date din diferite surse sau se recalculează formulele și funcțiile
$workbook.RefreshAll()

# 5. Se salvează modificările
$workbook.Save()

# 6. Se închide fișierul Excel
$workbook.Close()

# 7. Se închide aplicația Excel creată la pasul 1.
$excel.Quit()

# 8. Se eliberează obiectele COM
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($worksheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
Remove-Variable excel, workbook, worksheet

La fiecare rulare datele din Excel se reîmprospătează. O condiție importantă este ca acel fișier Excel să fie închis.

Scriptul de trimitere a datelor din Excel prin email

În acest script se preiau datele din fișierul Excel specificat la secțiunea 3. Înainte de preluare date ar trebui verificat dacă există sau nu instalat modulul ImportExcel în PowerShell. Dacă nu este se instalează cu linia comentată din secțiunea 1.

În acest script preiau valorile din celulele A2 și C3 (prima generată aleatoriu și suma tuturor valorilor). Specificul versiunii curente a modului Import-Excel este că trebuie să specificăm coloanele și liniile de start și de final în formatul R1C1.

În modelul meu numele foii de calcule este Sheet1.

# Preluare date din Excel și trimitere email
# Author: Valy Greavu 
# Date: 13.02.2024
# Se recomandă rulare script Refresh fisier Excel silent.ps1 înainte de trimitere email pentru actualizare date.
# Se recomandă utilizarea în Task Scheduler pentru trimitere email la o anumită dată.


# 1. Se instalează modulul Import-Excel dacă nu este deja instalat. 
# Operatiunea se realizeaza o singura data pe un calculator.
# În acest script linia de install este comentată

# Install-Module -Name ImportExcel -Scope CurrentUser -Force

# 2. Se importă modulul Import-Excel
Import-Module ImportExcel

# 3. Se specifică calea către fișierul Excel din care dorim preluarea datelor
$filePath = "P:\Temp\FisierExcel.xlsx"

# 4. Extrage valorile din anumite celule din foaia de calcul Sheet1 în acest exemplu celulele A2 și C3.
# Se pot extrage și blocuri de valori

$cellValue1 = (Import-Excel -Path $filePath -StartColumn 1 -StartRow 2 -NoHeader -EndRow 2 -EndColumn 1 -WorksheetName Sheet1).P1 # Celula A2
$cellValue2 = (Import-Excel -Path $filePath -StartColumn 3 -StartRow 3 -NoHeader -EndRow 3 -EndColumn 3 -WorksheetName Sheet1).P1 # Celula C3


# 5. Se construiește mesajul de email
# În acest exemplu trimit un email din Office 365 către Outlook.com

$subject = "Valori din Excel"
$body = "Buna ziua,`n`nValoarea din A2: $cellValue1`nSuma numerelor din C3: $cellValue2`n`nO zi buna!"
$to = "valy.greavu@outlook.com"
$emailAddress = "valy.greavu@feaa.uaic.ro"


# 6. Se specifică datele serverului SMTP al Outlook.com și portul
# Trebuie văzut la fiecare serviciu care sunt specificațiile de SMTP
# Trebuie văzut dacă serviciul utilizat suportă modern authentication (two factor authentication) pentru a compune credențialele de acces

$smtpServer = "smtp.office365.com"
$smtpPort = 587


# 7. În cazul în care nu automatizăm prin Task Scheduler putem folosi Prompt pentru parolă
$securePassword = Read-Host -Prompt "Introdu parola" -AsSecureString

# 7.1. Operatiune nerecomandata pentru ca salveaza parola in script in cazul in care dorim sa utilizam cu Task Scheduled dintr-un mediu securizat
# $password = "*************" # se trece parola în clear sau
# $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailAddress, (ConvertTo-SecureString -String $password -AsPlainText -Force)

# 7.2. Crearea obiectului de tip credențiale de autentificare pentru serviciul de email specificat.
# O altă verrsiune non-auto este să folosim comanda Get-Credential
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $emailAddress, $securePassword


# 8. Trimiterea emailului. 
# În cazul în care avem mai multe adrese la care trebuie să trimitem putem să le importăm dintr-un CSV și să folosim foreach
Send-MailMessage -To $to -From $emailAddress -Subject $subject -Body $body -SmtpServer $smtpServer -Port $smtpPort -UseSsl -Credential $credential

# Final script

Problemele mai mari care pot apărea sunt legate de serviciul de email utilizat pentru trimiterea datelor și tipul de autentificare suportat de SMTP. În exemplul meu folosesc un cont de Office 365 pentru a trimite datele. Sunt mai multe metode de a obține acel $credential corect, în funcție de furnizorul de email. Unele sisteme suportă inclusiv two factor authetication.

Personal nu recomand secțiunea 7.1. de salvare a parolei direct în script, dar este o opțiune pentru automatizare mai ales prin Task Scheduler.

Definirea task-urilor în Task scheduler

La final pentru automatizare se creează două task-uri noi în Excel cu programare dorită în cazul meu zilnic.

Și acțiunea de rulare a PowerShell și calea către fișier.

Cam asta ar fi. Sper să fie util cuiva.

Blog la WordPress.com.

SUS ↑