Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv #6

Enunț

Se citește de la tastatură un șir de maxim 70 caractere. Să se afișeze, unul sub altul, caracterele distincte din șir împreună cu frecvențele lor de apariție. Pe fiecare rând se va afișa un caracter urmat de frecvența sa în șir (prin frecvența de apariție a unui caracter se înțelege de câte ori apare caracterul respectiv în șir). Exemplu: Dacă se citește șirul "abracadabra", programul a afișa ceea ce apare în figură.

Rezolvare

#include <iostream>
#include <cstring>
using namespace std;


int main()
{
 char s[71];
 int l;


 cout << "Introduceti un sir: ";
 cin >> s;


 l = strlen(s);

 char caractere[30];
 int lCaractere = 0;


 int frecvente[30];

 for (int i = 0; i < l; ++i)
 {
  bool existaDeja = false;
  for (int j = 0; j < lCaractere; ++j)
  {
   if (caractere[j] == s[i])
   {
    existaDeja = true;


    ++frecvente[j];

    break;
   }
  }


  if (!existaDeja)
  {
   ++lCaractere;
   caractere[lCaractere - 1] = s[i];
   frecvente[lCaractere - 1] = 1;
  }
 }


 for (int i = 0; i < lCaractere; ++i)
 {
  cout << caractere[i] << " ->   " << frecvente[i] << endl;
 }


 return 0;
}

Vizual

Explicații

O implementare alternativă ar folosi clasa map din S.T.L.

Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv #5

Enunț

Se citește de la tastatură un număr întreg cu maxim 8 cifre. Să se elimine o cifră aleasă astfel încât numărul rămas să aibă cifrele în ordine crescătoare. Dacă sunt mai multe soluții se vor afișa toate, iar dacă problema nu are nici o soluție se va tipări un mesaj.

Exemplu: pentru m=2435, poate fi eliminată cifra 3 rămânând numărul 245, sau cifra 4 rămânând numărul 235.

Rezolvare

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
 char s[9];
 int l;

 cout << "Introduceti un numar: ";
 cin >> s;
 l = strlen(s);

 bool existaSolutie = false;
 for (int i = 0; i < l; ++i)
 {
  char s2[9];
  strcpy_s(s2, s);

  for (int j = i + 1; j < l; ++j)
  {
   s2[j - 1] = s2[j];
  }
  s2[l - 1] = '\0';

  bool sc = true;
  for (int j = 1; j < l - 1; ++j)
  {
   if ((s2[j - 1] - '0') > (s2[j] - '0'))
   {
    sc = false;
    break;
   }
  }

  if (sc)
  {
   cout << s2 << endl;
   existaSolutie = true;
  }
 }

 if (!existaSolutie)
 {
  cout << "Nu exista solutii." << endl;
 }

 return 0;
}

Vizual

Explicații

strcpy_s este varianta sigură a strcpy în Visual C++, folosită fiindcă strcpy dă eroare de compilare.

Exemple de încercat:

  • 123456
  • 5432

Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv #4

Enunț

Se citește de la tastatură un șir alcătuit din cel mult 100 litere mici ale alfabetului englez. Acest șir va fi supus unor procese de eliminare a tuturor secvențelor de caractere identice situate pe poziții alăturate, eliminările reluându-se până când șirul nu mai conține succesiuni de caractere alăturate egale. Realizați un program care afișează șirul rezultat după aplicarea algoritmului de mai sus.

Exemplu: pentru șirul "vacaantaaa" vom obține în ordine: "vacntaaa" și "vacnt".

Vizual

Exemple de încercat

  • teesst
  • abbc
  • cool
  • rezolvareaa

Rezolvare

#include <iostream>
#include <cstring>
using namespace std;


int main()
{
 char s[101];
 cout << "Sir: "; cin >> s;


 bool schimbare;
 do
 {
  schimbare = false;
  int inceput = -1;
  int sfarsit = -1;
  bool in = false;


  int l = strlen(s);

  for (int i = 0; i < l - 1; ++i)
  {
   if (s[i] == s[i + 1])
   {
    if (in)
    {
     sfarsit = i + 1;
    }
    else
    {
     in = true;
     inceput = i;
     sfarsit = i + 1;
     schimbare = true;
    }
   }
   else
   {
    if (in)
    {
     in = false;
     break;
    }
   }
  }
  if (schimbare)
  {
   for (int i = sfarsit + 1; i < l; ++i)
   {
    s[i - sfarsit + inceput - 1] = s[i];
   }
   l = l - (sfarsit - inceput + 1);
   s[l] = '\0';


   cout << s << endl;
  }
 } while (schimbare);


 return 0;
}

Tutorial „Interpolarea șirurilor în C#”


Acest tutorial vă arată cum să folosiți interpolarea șirurilor pentru a formata și a include rezultate de expresii într-un șir rezultat. Exemplele presupun că dvs. sunteți familiarizat cu conceptele de bază C# și formatarea tipurilor .NET. Dacă sunteți noi la interpolarea șirurilor sau formatarea tipurilor .NET, verificați tutorialul interactiv de interpolare a șirurilor mai întâi. Pentru mai multe informații despre formatarea tipurilor în .NET, vedeți subiectul Formatting Types in .NET.

Notă. Exemplele C# din acest articol rulează in interpretorul în linie și locul de joacă Try.NET. Selectați butonul Run pentru a rula un exemplu în fereastra interactivă. Odată ce dvs. ați executat codul, puteți să îl modificați și să rulați codul modificat selectând Run din nou. Codul modificat fie rulează în fereastra interactivă sau, dacă compilarea eșuează, fereastra interactivă afișează toate mesajele de eroare ale compilatorului C#.

Introducere

Facilitatea de interpolare a șirurilor este construită peste facilitatea de formatare compusă și furnizează o sintaxă mai lizibilă și mai convenientă pentru a include rezultate de expresii formatate într-un șir rezultat.

Pentru a identifica un șir literal ca un șir interpolat, precedați-l cu simbolul $. Puteți incorpora oricare expresie C# validă care întoarce un rezultat într-un șir interpolat. In exemplul următor, de îndată ce o expresie este evaluată, rezultatul ei este convertit într-un șir și inclus într-un șir rezultat:

double a = 3;
double b = 4;
Console.WriteLine($"Aria triunghiului dreptunghic cu catetele {a} si {b} este {0.5 * a * b}");
Console.WriteLine($"Lungimea ipotenuzei triunghiului dreptunghic cu catetele {a} si {b} este {CalculateHypotenuse(a, b)}");


double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 * leg1 + leg2 * leg2);

// Iesire asteptata:
// Aria triunghiului dreptunghic cu catetele 3 si 4 este 6
// Lungimea ipotenuzei triunghiului dreptunghic cu catetele 3 si 4 este 5


După cum arată exemplul, dvs. includeți o expresie într-un șir interpolat închizându-l în acolade:

{<expresieInterpolata>}

In momentul compilării, un șir interpolat este de obicei transformat într-un apel de metodă String.Format. Aceasta face toate capabilitățile facilității de compunere formatată a șirurilor disponibile dvs. pentru a le folosi cu șirurile interpolate de asemenea.

Cum să specificați un șir de format pentru o expresie interpolată

Dvs. specificați un șir format care este suportat de tipul de expresie rezultat urmând expresia interpolată cu două puncte („:”) și șirul format:

{<expresieInterpolata>:<sirFormat>}

Exemplul următor arată cum să specificați șiruri standard și personalizate de format pentru expresii care produc rezultate dată și timp sau numerice:

var date = new DateTime(1731, 11, 25);
Console.WriteLine($"In {date:dddd, MMMM dd, yyyy} Leonhard Euler a introdus litera e sa insemne {Math.E:F5} intr-o scrisoare catre Christian Goldbach.");


// Iesire asteptata:
// In Sunday, November 25, 1731 Leonhard Euler a introdus litera e sa insemne 2.71828 intr-o scrisoare catre Christian Goldbach.

Pentru mai multe informații, vedeți secțiunea Format String Component a subiectului Composite Formatting. Acea secțiune furnizează link-uri către subiecte care descriu șiruri format standard și personalizate suportate de tipurile de bază .NET.

Cum să controlați lățimea câmpului și alinierea expresiei formatate interpolate

Dvs. specificați lățimea minimă a câmpului și alinierea rezultatului expresiei formatate urmând expresia interpolată cu o virgulă („,”) și expresia constantă:

{<expresieInterpolata>,<aliniere>}

Dacă valoarea aliniere este pozitivă, rezultatul formatat al expresiei este aliniat la dreapta; dacă e negativă, este aliniat la stânga.

Dacă trebuie să specificați și alinierea și un șir format, începeți cu componenta alinierii:

{<expresieInterpolata>,<aliniere>:<sirFormat>}

Următorul exemplu arată cum să specificați alinierea și folosește caractere bară verticală („|”) pentru a delimita câmpuri de text:

const int NameAlignment = -9;
const int ValueAlignment = 7;


double a = 3;
double b = 4;
Console.WriteLine($"Trei medii clasice pitagoreice ale lui {a} si {b}:");
Console.WriteLine($"|{"Aritmetica",NameAlignment}|{0.5 * (a + b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Geometrica",NameAlignment}|{Math.Sqrt(a * b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Armonica",NameAlignment}|{2 / (1 / a + 1 / b),ValueAlignment:F3}|");


// Iesire asteptata:
// Trei medii clasice pitagoreice ale lui 3 si 4:
// |Aritmetica|  3.500|
// |Geometrica|  3.464|
// |Armonica |  3.429|


După cum arată ieșirea exemplului, dacă lungimea rezultatului expresiei formatat întrece lățimea specificată a câmpului, valoarea aliniere este ignorată.

Pentru mai multe informații, vedeți secțiunea Alignment Component a subiectului Composite Formatting.

Cum să folosiți șiruri de evacuare într-un șir interpolat

Sirurile interpolate suportă toate șirurile de evacuare care pot fi folosite în șiruri literale obișnuite. Pentru mai multe informații, vedeți String escape sequences.

Pentru a interpreta șirurile de evacuare literal, folosiți un șir literal textual (en. verbatim). Un șir interpolat textual începe cu caracterul $ urmat de caracterul @.

Pentru a include o acoladă, „{” sau „}”, în șirul rezultat, folosiți două acolade, „{{” sau „}}”. Pentru mai multe informații vedeți secțiunea Escaping Braces a subiectului Composite Formatting.

Următorul exemplu arată cum să includeți acolade în șirul rezultat și să construiți un șir interpolat textual:

var xs = new int[] { 1, 2, 7, 9 };
var ys = new int[] { 7, 9, 12 };
Console.WriteLine($"Gasiti intersectia multimilor {{{string.Join(", ",xs)}}} si {{{string.Join(", ",ys)}}}.");


var userName = "Jane";
var stringWithEscapes = $"C:\\Users\\{userName}\\Documents";
var verbatimInterpolated = $@"C:\Users\{userName}\Documents";
Console.WriteLine(stringWithEscapes);
Console.WriteLine(verbatimInterpolated);


// Iesire asteptata:
// Gasiti intersectia multimilor {1, 2, 7, 9} si {7, 9, 12}.
// C:\Users\Jane\Documents
// C:\Users\Jane\Documents

Cum să folosiți operatorul condițional ternar ?: într-o expresie interpolată

După cum două puncte (":") are un înțeles special într-un element cu o expresie interpolată, pentru a folosi un operator condițional într-o expresie, închideți-o în paranteze, după cum arată exemplul următor:

var rand = new Random();
for (int i = 0; i < 7; i++)
{
    Console.WriteLine($"Aruncare cu banul: {(rand.NextDouble() < 0.5 ? "stema" : "ban")}");
}

Cum să creați șir rezultat specific unei culturi cu interpolarea șirurilor

Implicit, un șir interpolat folosește cultura curentă definită de proprietatea CultureInfo.CurrentCulture pentru toate operațiile de formatare. Folosiți conversia implicită a unui șir interpolat la o instanță System.FormattableString și apelați-i metoda ToString(IFormatProvider) pentru a crea un șir rezultat specific unei culturi. Următorul exemplu arată cum să faceți aceasta:

var cultures = new System.Globalization.CultureInfo[]
{
    System.Globalization.CultureInfo.GetCultureInfo("en-US"),
    System.Globalization.CultureInfo.GetCultureInfo("en-GB"),
    System.Globalization.CultureInfo.GetCultureInfo("nl-NL"),
    System.Globalization.CultureInfo.InvariantCulture
};


var date = DateTime.Now;
var number = 31_415_926.536;
FormattableString message = $"{date,20}{number,20:N3}";
foreach (var culture in cultures)
{
    var cultureSpecificMessage = message.ToString(culture);
    Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}");
}


// Iesire asteptata este ca:
// en-US       5/17/18 3:44:55 PM      31,415,926.536
// en-GB      17/05/2018 15:44:55      31,415,926.536
// nl-NL        17-05-18 15:44:55      31.415.926,536
//            05/17/2018 15:44:55      31,415,926.536


După cum arată exemplul, puteți folosi o singură instanță FormattableString pentru a genera multiple șiruri rezultat pentru culturi variate.

Cum să creați un șir rezultat folosind cultura invariantă

Impreună cu metoda FormattableString.ToString(IFormatProvider), dvs. puteți folosi metoda statică FormattableString.Invariant pentru a rezolva un șir interpolat la un șir rezultat pentru InvariantCulture. Următorul exemplu arată cum să faceți aceasta:

string messageInInvariantCulture = FormattableString.Invariant($"Data si timpul in cultura invarianta: {DateTime.Now}");
Console.WriteLine(messageInInvariantCulture);


// Iesirea asteptata este ca:
// Data si timpul in cultura invarianta: 05/17/2018 15:46:24

Concluzie

Acest tutorial descrie scenarii obișnuite de utilizare a interpolării șirurilor. Pentru mai multe informații despre interpolarea șirurilor, vedeți subiectul String interpolation. Pentru mai multe informații despre formatarea tipurilor în .NET, vedeți subiectele Formatting Types in .NET și Composite formatting.

Vedeți de asemenea

Tradus din această pagină oficială de documentație Microsoft.

Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv #2

Enunț

Se citește de la tastatură un cuvânt și apoi un text de maxim 70 de caractere (scris pe un singur rând). Să se afișeze umărul de apariții ale cuvântului citit în cadrul textului.

Exemplu: dacă se citește textul „E bine e foarte bine” și cuvântul „bine”, programul va afișa valoarea 2.

Vizual


Rezolvare

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
 char cuvant[71] = "bine", rand[501];

 cout << "Randul: "; cin.getline(rand, 500);

 int rezultat = 0;
 char *i = rand;
 while (i = strstr(i, cuvant))
 {
  ++rezultat;
  ++i;
 }

 cout << "Cuvantul \"" << cuvant << "\" apare de " <<
  rezultat << " ori in rand." << endl;

 return 0;
}

Explicații

In trecut, în loc de <cstring> se folosea <string.h>.

Se consideră că un cuvânt are maxim 70 de caractere și un rând de text are maxim 500 caractere.

rezultat este variabilă de tip întreg care reține ca un contor numărul de apariții ale cuvântului cuvant în rândul de text rand.

i este un pointer (indicator) către începutul rândului de text din rand și la prima parcurgere a condiției de continuare a while-ului se schimbă în prima apariție a cuvântului în rând, sau în NULL dacă nu există asemenea apariție. La următoarea parcurgere, va fi ori NULL, ori poziția celei de-a doua apariții a cuvântului în rând etc.

strstr(i, cuvant) din condiția de continuare a ciclului while se atribuie lui i. acest apel întoarce poziția următoarei apariții a cuvântului în cuvântul care începe la adresa de memorie i (rand[x] este echivalent cu rand + x fiind vorba de pointeri C).

Problemă rezolvată șir de caractere clasa a XI-a mate-info neintensiv

Enunț

Un cuvânt este palindrom dacă citind literele de la dreapta la stânga obținem același cuvânt (de exemplu, cuvintele "cojoc" și "sas" sunt palindroame). Scrieți un program care verifică dacă un cuvânt citit de la tastatură este palindrom sau nu, afișând un mesaj.

Rezolvare

#include <iostream>
#include <cstring>
using namespace std;


int main()
{
 char s[201];
 int l;


 cout << "Cuvantul: "; cin >> s;

 l = strlen(s);

 bool palindrom = true;

 for (int i = 0; i < l / 2; ++i)
 {
  if (s[i] != s[l - i - 1])
  {
   palindrom = false;
   break;
  }
 }


 cout << (palindrom ? "ESTE PALINDROM." : "NU ESTE PALINDROM.") << endl;

 return 0;
}

Explicații

<iostream> este antetul pentru scrierea pe ecran și citirea de la tastatură.
<cstring> (în trecut <string.h>) este antetul pentru folosirea funcției strlen.

Presupunem că un cuvânt are maxim 200 de caractere . Al 201-lea este caracterul nul (codul ASCII 0 sau caracterul '\0').

Cu palindrom = true presupunem de la început că cuvântul dat este palindrom și apoi încercăm să demonstrăm contrariul.


Trecem prin fiecare caracter al cuvântului de la stânga la dreapta până la jumătatea lui. Dacă al i-lea caracter de la începutul cuvântului este diferit de al i-lea caracter de la sfârșitul cuvântului, presupunerea e falsă și oprim parcurgerea cu o instrucțiune break.

La sfârșit, înainte de a ieși din main cu return 0, folosim operatorul ?: pentru a afișa un șir de caractere literal în funcție de valoarea variabilei palindrom.

Exemple

Palindromul TOT (3 caractere) : i trece de la 0 la 3 / 2 = 1. Se compară T cu T și O cu O.

Palindromul ABCCBA (6 caractere): i trece de la 0 la 6 / 2 = 3.
Se compară caracterele de pe pozițiile 0 cu 5, 1 cu 4, 2 cu 3. Mai departe nu, deoarece condiția for are semnul strict mai mic, nu mai mic sau egal cu.

[C#] Tutorial „Lucrul cu LINQ”

Tradus din această pagină oficială de documentație Microsoft.

Introducere

Acest tutorial vă învață câteva facilități din .NET Core și limbajul C#. Dvs. veți învăța:
  • Cum să generați secvențe cu LINQ
  • Cum să scrieți metode care pot fi ușor folosite în interogări LINQ.
  • Cum să deosebiți între evaluarea imediată și evaluarea leneșă.
Veți învăța aceste tehnici construind o aplicație care demonstrează una dintre abilitățile de bază ale oricărui magician: faro shuffle. Pe scurt, un faro shuffle este o tehnică în care dvs. împărțiți un pachet de cărți exact în jumătate, apoi amestecul îmbină fiecare singură carte din fiecare jumătate să reconstruiască pachetul inițial.

Magicienii folosesc această tehnică deoarece fiecare carte este într-un loc cunoscut după fiecare amestec, și ordinea se repetă.

Pentru scopurile dvs., ea este o privire superficială la manipularea secvențelor de date. Aplicația pe care o veți construi va construi un pachet de cărți, și apoi va face o serie de amestecuri, afișând secvența de fiecare dată. Dvs. de asemenea veți compara ordinea actualizată cu ordinea originală.

Acest tutorial are pași multipli. După fiecare pas, dvs. puteți rula aplicația și vedea progresul. Dvs. puteți de asemenea vedea exemplul completat în depozitul GitHub dotnet/samples. Pentru instrucțiuni de descărcare, vedeți Samples and Tutorials.

Cerințe preliminare

Dvs. va trebui să vă configurați mașina să ruleze .NET core. Puteți găsi instrucțiunile de instalare pe pagina .NET Core. Puteți rula această aplicație pe Windows, Ubuntu Linux, OS X sau într-un container Docker. Dvs. va trebui să instalați editorul de cod favorit al dvs. Descrierile de mai jos folossc Visual Studio Code care este un editor cu sursă deschisă, cross-platform. Totuși, dvs. puteți folosi oricare unelte cu care sunteți confortabili.

Creați aplicația

Primul pas este să creați o nouă aplicație. Deschideți o linie de comandă și creați un director nou pentru aplicația dvs. Faceți-l directorul curent. Tastați comanda dotnet new console la linia de comandă. Aceasta creează fișierele de începere pentru o aplicație de bază „Hello World”.

Dacă nu ați folosit niciodată C# înainte, acest tutorial explică structura unui program C#. Dvs. puteți învăța acela și apoi să vă întoarceți aici să învățați mai multe despre LINQ.

Crearea setului de date

Inainte de a începe, asigurați-vă că următoarele linii sunt la începutul fișierului Program.cs generat de dotnet new console:

// Program.cs
using System;
using System.Collections.Generic;
using System.Linq;


Dacă aceste trei linii (instrucțiuni using) nu sunt la începutul fișierului, programul nostru nu va compila.

Acum că aveți toate referințele de care aveți nevoie, considerați ce constituie un pachet de cărți. De obicei, un pachet de cărți de joc au patru seturi, și fiecare set are treisprezece valori. In mod normal, dvs. ați considera crearea unei clase Card (Carte) imediat și popularea unei colecții de obiecte Card manual. Cu LINQ, dvs. puteți fi mai concis decât modul obișnuit de a crea un pachet de cărți. In schimbul creării unei clase Card, dvs. puteți crea două secvențe care să reprezinte seturile și respectiv rangurile. Dvs. veți crea o pereche foarte simplă de metode iterator care vor genera rangurile și seturile ca IEnumerable<T>-uri de șiruri:

// Program.cs
// The Main() method


static IEnumerable<string> Suits()
{
    yield return "clubs";
    yield return "diamonds";
    yield return "hearts";
    yield return "spades";
}


static IEnumerable<string> Ranks()
{
    yield return "two";
    yield return "three";
    yield return "four";
    yield return "five";
    yield return "six";
    yield return "seven";
    yield return "eight";
    yield return "nine";
    yield return "ten";
    yield return "jack";
    yield return "queen";
    yield return "king";
    yield return "ace";
}


Plasați acestea în interiorul metodei Main în fișierul dvs. Program.cs. Ambele aceste metode utilizează sintaxa yield return pentru a produce secvențe pe măsură ce rulează. Compilatorul construiește un obiect care implementează IEnumerable<T> și generează secvențele de șiruri pe măsură ce sunt cerute.

Acum, utilizați aceste metode iterator pentru a crea pachetul de cărți de joc. Veți plasa interogarea LINQ în metoda noastră Main. Iată o privire la ea:

// Program.cs
static void Main(string[] args)
{
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };


    // Display each card that we've generated and placed in startingDeck in the console
    foreach (var card in startingDeck)
    {
        Console.WriteLine(card);
    }
}


Clauzele multiple from produc un SelectMany, care creează o singură secvență din combinarea fiecărui element din prima secvență cu fiecare element din a doua secvență. Ordinea este importantă pentru scopurile noastre. Primul element din prima secvență sursă  (Suits) este combinat cu fiecare lement din secvența a doua (Ranks). Aceasta produce toate cele treisprezece cărți din primul set. Acest proces este repetat cu fiecare element din prima secvență (Suits). Rezultatul final este un pachet de cărți de joc ordonate după seturi, apoi după valori.

Este important să ținem minte că fie că dvs. alegeți să scrieți LINQ-ul dvs. în sintaxa de interogare folosită mai sus sau să folosiți sintaxa metodelor în schimb, este întotdeauna posibil să treceți de la o formă de sintaxă la alta. Interogarea de mai sus scrisă în sintaxă de interogare poate fi scrisă în sintaxa metodelor ca:

var startingDeck = Suits().SelectMany(suit => Ranks(rank => new { Suit = suit, Rank = rank }));

Compilatorul traduce instrucțiunile LINQ scrise cu sintaxă de interogare în sintaxa de apeluri de metode echivalentă. Ca urmare, indiferent de alegerea dvs. de sintaxă, cele două versiuni ale interogării produc același rezultat. Alegeți care sintaxă merge mai bine pentru situația dvs.: de exemplu, dacă dvs. lucrați într-o echipă în care unii din membri au dificultate cu sintaxa metodelor, încercați să preferați folosirea sintaxei de interogare.

Dați-i drumul și rulați exemplul pe care l-ați construit până la acest punct. El va afișa toate cele 52 de cărți din pachet. S-ar putea să găsiți foarte ajutător să rulați acest exemplu sub un depanator să observați cum metodele Suits() și Ranks() se execută. Dvs. puteți să vedeți clar că fiecare șir din fiecare secvență este generat doar când este necesar.


Manipularea ordinii

In continuare, concentrați-vă pe cum veți amesteca aceste cărți din pachet. Primul pas în oricare amestecare bună este să împărțiți pachetul în două. Metodele Take și Skip care sunt parte din API-urile LINQ furnizează această facilitate pentru dvs. Plasați-le sub ciclul foreach:

// Program.cs
public static void Main(string[] args)
{
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    // 52 cards in a deck, so 52 / 2 = 26   
    var top = startingDeck.Take(26);
    var bottom = startingDeck.Skip(26);
}

Cu toate acestea, nu există nici o metodă de amestecare de care să profităm în biblioteca standard, deci dvs. va trebui să vă scrieți propria metodă. Metoda de amestecare pe care o veți fi creând ilustrează câteva tehnici pe care le veți folosi cu programele bazate pe LINQ, deci fiecare parte a acestui proces va fi explicat în pași.

Pentru a adăuga ceva funcționalitate la cum dvs. interacționați cu IEnumerable<T> veți face un pas înapoi de la interogări LINQ, dvs. va trebui să scrieți un fel special de metode numite metode extensii. Pe scurt, o metodă extensie este o metodă statică cu scop special care adaugă funcționalitate nouă la un tip deja existent fără a trebui să modificați tipul original  la care doriți să adăugați funcționalitate.

Dați metodelor extensii ale dvs. o casă nouă adăugând un fișier cu o nouă clasă statică programului dvs. numit Extensions.cs, și apoi începeți să construiți prima metodă extensie:

// Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqFaroShuffle
{
    public static class Extensions
    {
        public static IEnumerable<T> InterleaveSequenceWith<T>(this IEnumerable<T> first, IEnumerable<T> second)
        {
            // Your implementation will go here soon enough
        }
    }
}

Priviți pentru un moment semnătura metodei, mai exact parametrii:

public static IEnumerable<T> InterleaveSequenceWith<T> (this IEnumerable<T> first, IEnumerable<T> second)

Dvs. puteți vedea adăugarea modificatorului this pe primul argument către metodă. Aceasta înseamnă că dvs. apelați metoda ca și cum ar fi o metodă membru a tipului primului argument. Această declarație de metodă urmează de asemenea un idiom standard în care tipurile de intrare și ieșire sunt IEnumerable<T>. Această practică dă voie metodelor LINQ să fie înlănțuite împreună pentru a realiza interogări mai complexe.

In mod natural, fiindcă dvs. ați împărțit pachetul în două jumătăți, va trebui să uniți aceste două jumătăți împreună. In cod, aceasta înseamnă că dvs. veți fi enumerând ambele secvențe pe care le-ați obținut prin Take și Skip deodată, intercalând elementele (en. interleaving), și creând o singură secvență: pachetul dvs. acum amestecat de cărți. Scrierea uni metode LINQ care lucrează cu două secvențe necesită să înțelegeți cum funcționează IEnumerable<T>.

Interfața IEnumerable<T> are o singură metodă: GetEnumerator. Obiectul întors de GetEnumerator are o metodă să mute la următorul element, și o proprietate care întoarce elementul curent al secvenței. Dvs. veți folosi acești doi membri pentru a enumera colecția și a întoarce elementele. Această metodă de intercalare va fi o metodă iterator, deci în schimbul construirii unei colecții și întoarcerii colecției, dvs. veți folosi sintaxa yield return arătată mai sus.

Aici este implementarea acelei metode:

public static IEnumerable<T> InterleaveSequenceWith<T>
    (this IEnumerable<T> first, IEnumerable<T> second)
{
    var firstIter = first.GetEnumerator();
    var secondIter = second.GetEnumerator();


    while (firstIter.MoveNext() && secondIter.MoveNext())
    {
        yield return firstIter.Current;
        yield return secondIter.Current;
    }
}


Acum că ați scris această metodă, mergeți înapoi la metoda Main și amestecați pachetul o dată:

// Program.cs
public static void Main(string[] args)
{
    var startingDeck = from s in Suits()
                       from r in Ranks()
                       select new { Suit = s, Rank = r };


    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }

    var top = startingDeck.Take(26);
    var bottom = startingDeck.Skip(26);
    var shuffle = top.InterleaveSequenceWith(bottom);


    foreach (var c in shuffle)
    {
        Console.WriteLine(c);
    }
}

Comparări

Câte amestecuri sunt necesare pentru a pune pachetul în ordinea lui originală? Pentru a afla, dvs. va trebui să scrieți o metodă care determină dacă două secvențe sunt egale. După ce aveți această metodă, dvs. va trebui să plasați codul care amestecă pachetul într-un ciclu, și să verificați când pachetul este înapoi în ordine.

Scrierea unei metode să determinați dacă cele două secvențe sunt egale ar trebui să fie simplu. Este o structură similară cu metoda pe care ați scris-o să amestece pachetul. Dar de această dată, în schimbul yield return-ării fiecărui element, dvs. veți compara elementele potrivite din fiecare secvență. Când întreaga secvență a fost enumerată, dacă fiecare element se potrivește, secvențele sunt aceleași:

Extensions.cs

public static bool SequenceEquals<T>
    (this IEnumerable<T> first, IEnumerable<T> second)
{
    var firstIter = first.GetEnumerator();
    var secondIter = second.GetEnumerator();


    while (firstIter.MoveNext() && secondIter.MoveNext())
    {
        if (!firstIter.Current.Equals(secondIter.Current))
        {
            return false;
        }
    }


    return true;
}


Aceasta arată un al doilea idiom LINQ: metodele terminale. Ele primesc o secvență ca intrare (sau în acest caz, două secvențe), și întorc o singură valoare scalară. Când folosiți metode terminale, ele sunt întotdeauna metoda finală într-un lanț de metode pentru o interogare LINQ, de aici numele „terminal”.

Dvs. puteți vedea acesta în acțiune când dvs. îl folosiți să aflați dacă pachetul este înapoi în ordinea lui originală. Puneți codul de amestecare într-un ciclu, și opriți când secvența este înapoi în ordinea lui originală aplicând metoda SequenceEquals(). Dvs. puteți vedea că ea ar fi întotdeauna metoda finală în oricare interogare, deoarece întoarce o singură valoare în schimbul unei secvențe:

// Program.cs
static void Main(string[] args)
{
    // Query for building the deck


    // Shuffling using InterleaveSequenceWith<T>();

    var times = 0;
    // We can re-use the shuffle variable from earlier, or you can make a new one
    shuffle = startingDeck;
    do
    {
        shuffle = shuffle.Take(26).InterleaveSequenceWith(shuffle.Skip(26));


        foreach (var card in shuffle)
        {
            Console.WriteLine(card);
        }
        Console.WriteLine();
        times++;


    } while (!startingDeck.SequenceEquals(shuffle));

    Console.WriteLine(times);
}


Rulați codul pe care îl aveți până acum și observați cum pachetul se rearanjează la fiecare amestec. După 8 amestecuri (iterații ale ciclului do-while), pachetul se întoarce la configurația originală în care era când l-ați creat prima dată din interogarea LINQ de începere.

Optimizări

Exemplul pe care l-ați construit până acum realizează un out shuffle, în care cărțile de sus și de jos rămân aceleași la fiecare rulare. Haideți să facem o schimbare: vom folosi un in shuffle în schimb, în care toate cele 52 de cărți își schimbă poziția. Pentru un in shuffle, dvs. intercalați pachetul astfel încât prima carte a jumătății de jos devine prima carte din pachet. Aceasta înseamnă că ultima carte din prima jumătate devine cartea de jos. Aceasta este o simplă schimbare la o singură linie de cod. Actualizați interogarea curentă de amestecare inversând pozițiile lui Take și Skip. Aceasta va schimba ordinea jumătății de sus și a jumătății de jos ale pachetului:

shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));

Rulați programul din nou, și veți vedea că sunt necesare 52 de iterații pentru ca pachetul să se reordoneze. Dvs. de asemenea veți începe să observați câteva degradări serioase ale performanței pe măsură ce programul continuă să ruleze.

Există câteva motive pentru aceasta. Puteți aborda una dintre cauzele majore ale acestei căderi a performanței: folosirea ineficientă a evaluării leneșe.

Pe scurt, evaluarea leneșă face că evaluarea unei instrucțiuni să nu fie realizată până valoarea ei este necesară. Interogările LINQ sunt instrucțiuni care sunt evaluate leneș. Secvențele sunt generate doar pe măsură ce elementele sunt cerute. In mod obișnuit, acesta este un avantaj major al LINQ. Cu toate acestea, într-o utilizare cum este acest program, aceasta acesta cauzează creștere exponențială în timpul de execuție.

Țineți minte că noi am generat pachetul original folosind o interogare LINQ. Fiecare amestec este generat făcând trei interogări LINQ pe pachetul anterior. Toate acestea sunt realizate leneș. Aceasta înseamnă de asemenea că ele sunt realizate din nou de fiecare dată când secvența este cerută. In momentul în care ajungeți la a 52-a iterație, dvs. veți fi regenerând pachetul original de multe, multe ori. Haideți să scriem un jurnal (en. log) să demonstrăm acest comportament. Apoi, dvs. îl veți corecta.

Iată aici o metodă jurnal care poate fi pusă la sfârșitul oricărei interogări să marcheze că interogarea a fost executată.

public static IEnumerable<T> LogQuery<T>
    (this IEnumerable<T> sequence, string tag)
{
    using (var writer = File.AppendText("debug.log"))
    {
        writer.WriteLine($"Executing Query {tag}");
    }

    return sequence;
}

Acum, instrumentați definiția fiecărei interogări cu un mesaj de jurnal:

// Program.cs
public static void Main(string[] args)
{
    var startingDeck = (from s in Suits().LogQuery("Suit Generation")
                        from r in Ranks().LogQuery("Rank Generation")
                        select new { Suit = s, Rank = r }).LogQuery("Starting Deck");

    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }
       

    Console.WriteLine();
    var times = 0;
    var shuffle = startingDeck;

    do
    {
        // Out shuffle
        /*
        shuffle = shuffle.Take(26)
            .LogQuery("Top Half")
            .InterleaveSequenceWith(shuffle.Skip(26)
            .LogQuery("Bottom Half"))
            .LogQuery("Shuffle");
        */

        // In shuffle
        shuffle = shuffle.Skip(26)
            .LogQuery("Bottom Half")
            .InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
            .LogQuery("Shuffle");

        foreach (var c in shuffle)
        {
            Console.WriteLine(c);
        }

        times++;
        Console.WriteLine(times);
    } while (!startingDeck.SequenceEquals(shuffle));

    Console.WriteLine(times);
}

Notați că dvs. nu înregistrați (en. log) de fiecare dată când accesați o interogare. Dvs. înregistrați doar când creați interogarea originală. Programul încă ia mult timp să ruleze, dar acum puteți vedea de ce. Dacă rămâneți fără răbdare rulând in shuffle-ul cu jurnalizarea pornită, treceți înapoi la out shuffle. Dvs. încă veți simți efectele evaluării leneșe. Intr-o rulare, ea execută 2592 de interogări, incluzând toate generările de valoare și set.

Puteți îmbunătăți performanța codului aici pentru a reduce numărul de execuții pe care le faceți. O simplă reparație pe care o puteți face este să cache-uiți rezultatele interogării LINQ originale care construiește pachetul de cărți. In prezent, dvs. executați interogările iar și iar de fiecare dată când ciclul do-while trece printr-o iterație, reconstruind pachetul de cărți și reamestecându-l de fiecare dată. Pentru a cache-ui pachetul de cărți, puteți profita de metodele LINQ ToArray și ToList; când le puneți la sfârșitul interogărilor, ele vor executa aceleași acțiuni pe care le-ați spus să le execute, dar acum ele vor stoca rezultatul într-un tablou (array) sau o listă, depinzând de care metodă alegeți să apelați. Adăugați metoda LINQ ToArray la sfârșitul ambelor interogări și rulați programul din nou:

public static void Main(string[] args)
{
    var startingDeck = (from s in Suits().LogQuery("Suit Generation")
                        from r in Ranks().LogQuery("Value Generation")
                        select new PlayingCard(s, r))
                        .LogQuery("Starting Deck")
                        .ToArray();


    foreach (var c in startingDeck)
    {
        Console.WriteLine(c);
    }


    Console.WriteLine();

    var times = 0;
    var shuffle = startingDeck;


    do
    {
        /*
        shuffle = shuffle.Take(26)
            .LogQuery("Top Half")
            .InterleaveSequenceWith(shuffle.Skip(26).LogQuery("Bottom Half"))
            .LogQuery("Shuffle")
            .ToArray();
        */


        shuffle = shuffle.Skip(26)
            .LogQuery("Bottom Half")
            .InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
            .LogQuery("Shuffle")
            .ToArray();


        foreach (var c in shuffle)
        {
            Console.WriteLine(c);
        }


        times++;
        Console.WriteLine(times);
    } while (!startingDeck.SequenceEquals(shuffle));


    Console.WriteLine(times);
}


Acum amestecul out shuffle este jos la 30 de interogări. Rulați din nou cu in shuffle-ul și veți vedea îmbunătățiri similare: el execută acum 162 de interogări.

Vă rugăm să notați că acest exemplu este proiectat să sublinieze cazurile de utilizare în care evaluarea leneșă poate cauza dificultăți de performanță. In timp ce este important să vedeți unde evaluarea leneșă poate lovi performanța codului, este egal de important să înțelegeți că nu toate interogările ar trebui să ruleze imediat. Lovitura de performanță pe care o întâmpinați fără să folosiți ToArray este deoarece fiecare nou aranjament al pachetului de cărți este construit din aranjamentul anterior. Folosirea evaluării leneșe înseamnă că fiecare nouă configurație a pachetului este construită din pachetul original, chiar executând codul care a construit startingDeck. Aceasta cauzează o mare cantitate de lucru în plus.

In practică, unii algoritmi rulează bine folosind evaluarea imediată. Pentru utilizarea zilnică, evaluarea leneșă este de obicei o alegere mai bună când sursa de date este un proces separat, ca un motor de bază de date. Pentru bazele de date, evaluarea leneșă permite interogări mai complexe să se execute doar cu un singur drum către procesul bazei de date și înapoi la restul codului dvs. LINQ este flexibil fie că alegeți să folosiți evaluare leneșă fie imediată, deci măsurați-vă procesele și alegeți oricare fel de evaluare vă dă cea mai bună performanță.

Concluzie

In acest proiect, dvs. ați acoperit:
  • folosirea interogărilor LINQ pentru a agrega date într-o secvență semnificativă
  • scrierea de metode extensii pentru a adăuga propria noastră funcționalitate personalizată la interogări LINQ
  • localizarea zonelor din codul nostru în care interogările noastre LINQ ar putea ajunge în probleme de performanță ca viteză degradată
  • evaluarea leneșă și imediată în legătură cu interogările LINQ și implicațiile pe care ele le pot avea asupra performanței interogărilor
Pe lângă LINQ, ați învățat puțin despre o tehnică pe care magicienii o folosesc pentru trucuri cu cărți de joc. Magicienii folosesc amestecul Faro (Faro shuffle) deoarece ei pot controla unde fiecare carte se mișcă în pachet. Acum că știți, nu o stricați pentru toți ceilalți!

Pentru mai multe informații despre LINQ, vedeți:

Tradus din această pagină oficială de documentație Microsoft.