fbpx

Rezolvarea unei cerinte de la Olimpiada Nationala de C#

0

Salutare dragi prieteni si bine v-am gasit. Cu cateva zile in urma am primit intrebari legate de C#, si m-am gandit ca ar fi bine sa va mai prezint cateva chestii noi la C#. Am ochit o cerinta de la Olimpiada Nationala de C# de anul acesta si m-am gandit ca ar fi foarte bine sa o rezolvam in mai multe moduri pentru a trece prin LINQ Expressions si prin Lambda Expressions. Nu o sa vorbesc momentan despre ele, toate la timpul potrivit. Cerinta este:

Realizează o criptare a şirului de caractere preluat din câmpul Parola, numită CriptareParola, pentru a obţine parola criptată, astfel:

– fiecare caracter literă mică va fi înlocuit cu litera următoare din alfabet, litera z va fi înlocuită cu litera a;

– fiecare caracter literă mare va fi înlocuit cu litera precedentă din alfabet, litera A va fi înlocuită cu litera Z;

– fiecare caracter cifră c va fi înlocuit cu cifra rezultată din operația aritmetică 9-c.

– caracterele care nu sunt litere sau cifre rămân nemodificate (nu se admit diacritice).

Exemplu:

Parola introdusă în timpul înregistrării: MA1995$#riz73

Parola criptată salvată în baza de date: LZ8004$#sja26

Cand am vazut cerinta aceasta m-am gandit ca nu este asa grea, nici nu este, insa cand am incercat sa o rezolv, mi-a dat putin batai de cap. In primul rand, trebuie spus ca aceasta nu este o criptare si nu este recomandat sa folositi astfel de „criptari” in programe reale, este o cerinta si trebuie doar rezolvata nu luata ca pe o modalitate de criptare. 

Ideea de rezolvare

Pentru a acumula cat mai multe informatii dupa citirea si parcurgerea acestui articol va pot spune ca aceasta criptare are la baza Cifrul lui Caesar.
Prima idee pe care o aveti in momentul in care vedeti aceasta cerinta este evident parcurgerea string-ului. Problema cea mai mare apare abia dupa ce am parcurs sirul. Trebuie ca fiecare litera/cifra sa fie inlocuita cu ceva anume. Am facut si o schema ca sa vedeti ce in ce se va schimba:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Z A B C D E F G H I J K L M N O P Q R S T U V W X Y

a b c d e f g h i j k l m n o p q r s t u v w x y z
b c d e f g h i j k l m n o p q r s t u v w x y z a

0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0

Deja vom putea creea primele conditii:

  1. Daca avem litere mici, vor fi schimbate cu literele urmatoare, adica +1.
  2. Daca avem litere mari, vor fi schimbate cu literele precedente, adica -1.
  3.  In cazul in care avem A, va fi schimbat in Z.
  4. In cazul in care aven z, va fi schimbat in a.

Recapituland, algoritmul pe care il vom creea va trebui sa parcurga string-ul si va trebui sa respecte conditiile prezentate mai sus, inlocuind cu literele/cifrele corespunzatoare. Cum vom putea rezolva aceasta cerinta ?? Ei bine, vom rezolva in mai multe moduri pentru a vedea cat de mult poate fi optimizat un algoritm in C#.

Metoda 1

Prima metoda este metoda ineficienta. Vom pargurce string-ul ca un sir de caractere si la fiecare litera intalnita folosind instructiunea if vom creea un nou string din caracterele inlocuite.

char[] chars = new char[inputString.Length]; //creem un vector in care adaugam caracterele

for (int i = 0; i < inputString.Length; i++) //parcurgem vectorul
{
    if (inputString[i] == 'a')//verificam litera i = pozitia
        chars[i] = 'b';//adaugam in vector si la final vom creea un string din elementele din vector si acela va fi rezultatul afisat
    else if (inputString[i] == 'b')
        chars[i] = 'c';
    else if (inputString[i] == 'c')
        chars[i] = 'd';
    .......
    else if (inputString[i] == 'z')
        chars[i] = 'a';
    else if (inputString[i] == 'A')
        chars[i] = 'Z';
    else if (inputString[i] == 'B')
        chars[i] = 'A';
    else if (inputString[i] == 'C')
        chars[i] = 'B';
    else if (inputString[i] == 'D')
        chars[i] = 'C';
    .......
    else if (inputString[i] == '0')
        chars[i] = '9';
    else if (inputString[i] == '1')
        chars[i] = '8';
    else if (inputString[i] == '2')
        chars[i] = '7';
    else if (inputString[i] == '3')
        chars[i] = '6';
    else if (inputString[i] == '4')
        chars[i] = '5';
    else if (inputString[i] == '5')
        chars[i] = '4';
    else if (inputString[i] == '6')
        chars[i] = '3';
    else if (inputString[i] == '7')
        chars[i] = '2';
    else if (inputString[i] == '8')
        chars[i] = '1';
    else if (inputString[i] == '9')
        chars[i] = '0';
    else
        chars[i] = inputString[i];
}
string outputString = new string(chars); //practic creem un string din vectorul de caractere

O metoda foarte lunga, care ne duce la rezultat, insa dupa ce am scris 125 linii doar din if-uri.

Metoda 2 – Folosind metoda transpunerii

Aceasta metoda este foarte interesanta, deoarece vom creea doua string-uri constante, in primul vom pune literele absolut normal, iar in cel de-al doilea vom pune literele schimbate, in paralel.

const string stringNormal = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; //literele la rand
const string stringModificat = "bcdefghijklmnopqrstuvwxyzaZABCDEFGHIJKLMNOPQRSTUVWXY9876543210"; //literele modificate (a din primul cu b din cel de-al doilea si tot asa)

char[] chars = new char[inputString.Length]; //creem un vector unde vom stoca caracterele
for (int i = 0; i < inputString.Length; i++) //parcurgem string-ul
{
    int index = stringNormal.IndexOf(inputString[i]); //cautam unde se afla in primul string litera la care am ajuns 
    chars[i] = index >= 0 ? stringModificat[index] : inputString[i]; //adaugam in vector litera din string-ul modificat, pozitiile corespunzand.
    //elementul de pe pozitia i din primul string se va schimba cu elementul de pe pozitia index din cel de-al doilea string
}
string outputString = new string(chars);// construim string-ul din caracterele vectorului

Din experienta, pot spune ca toata lumea se gandeste la prima metoda, insa daca doresc o rezolvare cat de cat mai usoara va trebui sa foloseasca metode de genul acesta. Este folositor sa tineti minte principiul cum am gandit acest algoritm, cine stie cand veti mai avea nevoie.

Metoda 3 – Folosind LINQ Expressions si Lambda Expressions

Aceasta este metoda mea preferata, si nu doar in acest caz, eu mereu folosesc cand este posibil LINQ Expressions si Lambda Expressions. O sa va povestesc pe scurt cam ce inseamna fiecare pentru a intelege cat de cat ultima metoda care are un nivel ridicat de dificultate.

Ce inseamna LINQ Expressions si la ce le vom folosi ?

LINQ Expressions sunt niste Query-uri aplicate pe vectori, pe functii generice(adica liste), si asa mai departe. Acestea pot fi folosite in doua moduri:

string[] angajati = {"Catalin", "Mihai", "Samy", "Silviu", "Mike", "Andrei", "Adel" };
var linq =  from nume in angajati          //selectam exact ca in SQL dar dintr-un vector toate numele care contin S. ATENTIE !! nu sunt aceleasi query-uri ca in SQL
                where nume.Contains('S') 
                    select nume;
       
foreach (var nume in linq)    //parcurgem linq(este un nou vector care contine doar elementele dorite de noi) si scriem numele
    Console.Write(nume + " "); // Output: Samy Silviu

Denumirea de LINQ vine de la Language-Integrated Query, adica fix ce am spus mai sus, este un query integrat in limbajul de programare C#.

Iar acum o sa va arat ce-l de-al doilea mod in care puteti folosi LINQ Expression, si anume sub forma unei metode (metodele le-am explicat intr-un alt articol pe site). Toate metodele o sa le prezentam pe noua platforma, cand le vom deschide deoarece sunt foarte multe si nu o sa intelegeti absolut nimic din articole, o sa fie cu codul langa si il veti putea testa in timp real.
Revenind, o sa va arat un exemplu:

string[] angajati = { "Catalin", "Mihai", "Samy", "Silviu", "Mike", "Andrei", "Adel" }; //definim vectorul cu angajati
var linq = angajati.Where(c => c.Contains('S')); //creem o variabila linq, si folosim metoda Where
            
foreach (var nume in linq) //parcurgem linq
    Console.WriteLine(nume); //afisam numele care contine S
 
//Output: Samy
          Silviu

Ce inseamna Lambda Expressions si la ce folosim?

Dupa cum puteti observa in metoda Where am folosit:

c => c.Contains('S')

cu Contains banuiesc ca ne-am lamurit, acesta verifica daca este continut caracterul pe care noi il dam. Insa ce reprezinta „=>” ?? Acesta este Lambda Expression, si anume cu acesta vom putea scurta mult codul, in sensul ca variabila c este direct definita folosind lambda si practic ne va selecta fix elementul care contine S. Bineinteles exista foarte multe metode si foarte multe posibilitati de a folosi. V-am aratat pe scurt cam cum folosim Lambda si LINQ Expressions.

 

LINQ Expressions Lambda Expression

Tot inainte de a va prezenta algoritmul va trebui sa va arat cate ceva despre operatorii ternari.

Ce sunt operatorii ternari ?

Operatorii Ternari sunt folositi in locul if-urilor. Spre exemplu avem secventa urmatoare:

if(contditie)
    se executa ceva...
else if(alta conditie)
    se executa altceva...
else 
    se executa altceva...

Pentru a folosi LINQ Expression nu vom putea folosi evident si if-uri, de aceea vom folosi operatori ternari adica: „? :”. Cum va arata deci o sintaxa simpla:

conditie ? se executa : se executa acel else
// Daca conditia este indeplinita executam ce este dupa ?
//Daca conditia nu este indeplinita executam ce este dupa :

Cum ar arata cu if??

if(conditie)
    se executa ceva
else 
    se executa altceva

Iar acum va voi arata cum veti putea transforma mai multe if-uri folosind operatori ternari:

a ? b : c ? d : e 

sau


a ? b : (c ? d : e)

Cum ar fi cu if??
if(contditie) 
    se executa ceva... 
else if(alta conditie) 
    se executa altceva... 
else 
    se executa altceva...

Acum ca am lamurit cam tot ce tinea de sintaxa vom trece si la metodele folosite in rezolvare, dar asta dupa ce o sa va prezint rezolvarea:

int numere = '9' + '0';
string outputString = string.Join("", normal.Select(ch => (ch >= 'a' && ch < 'z') ? (char)(ch + 1) : (ch > 'A' && ch <= 'Z') ? (char)(ch - 1) : (ch == 'z') ? (char)'a' : (ch == 'A') ? (char)'Z' : (Char.IsDigit(ch)) ? (char)(Char.IsDigit(ch) ? numere - ch : ch + 1) : ch));

Acum vom lua pe rand metodele si le vom explica. Prima metoda este string.Join, ce face acesta? Va voi arata pe un exemplu particular, avem array-ul cu angajati si vrem sa afisam elementele, separate ori prin virgula, ori neseparate, etc.

string[] angajati = { "Catalin", "Mihai", "Samy", "Silviu", "Mike", "Andrei", "Adel" }; //declaram vectorul
var nou = string.Join(", ",angajati); //creem o variabila, functia va afisa toate elementele din vector si le va separa prin virgula cu spatiu
Console.WriteLine(nou); // afisam variabila

//Output: Catalin, Mihai, Samy, Silviu, Mike, Andrei, Adel

//daca variabila nou arata astfel:
var nou = string.Join("",angajati); //se vor afisa elementele fara spatiu

//Output: CatalinMihaiSamySilviuMikeAndreiAdel


Prin urmare cu ajutorul functiei Join vom creea string-ul final. Vedem ca urmeaza metoda Select. Aceasta verifica caracterele si le inlocuieste.

Observatie !! IsDigit(char) verifica daca un caracter este cifra si returneaza variabila de tip boolean.

Link-uri utile:

https://tutoriale-pe.net/metode-si-clase-generice-folosite-in-c/
https://tutoriale-pe.net/tipul-struct-in-c-teorie-si-exemple/
https://www.youtube.com/watch?v=lsaYfyhf5xg
https://www.youtube.com/watch?v=AaE-1rd0SR8

Comentarii
Se incarca comentariile...

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More