10. diel - Textové reťazce v C# druhýkrát - Práca s jednotlivými znakmi
V minulej lekcii, Najčastejšie chyby C# nováčikov - Vieš pomenovať premenné?, sme si ukázali najčastejšie chyby začiatočníkov v C# .NET ohľadom pomenovania premenných.
Pokiaľ patríte medzi tých, ktorí v C# videli nejakú podobnosť medzi
poľom a textovým reťazcom, tak ste uvažovali správne. Pre tých ostatných
môže byť prekvapením, že string
je v podstate pole
znakov (hodnôt typu char
) a môžeme s ním aj takto
pracovať.
Najprv si vyskúšajme, že to všetko funguje. Rozcvičíme sa na jednoduchom vypísaní znaku na danej pozícii:
{CSHARP_CONSOLE}
string s = "C# .NET";
Console.WriteLine(s);
Console.WriteLine(s[1]);
Console.ReadKey();
{/CSHARP_CONSOLE}
Výstup:
Konzolová aplikácia
C# .NET
#
Vidíme, že k znakom v reťazci môžeme pristupovať cez hranatú zátvorku, ako tomu je aj pri poli. Sklamaním môže byť, že znaky na danej pozícii sú v C# read-only, nemôžeme teda napísať:
// this code doesn't work string s = "C# .NET"; s[1] = '!'; Console.WriteLine(s); Console.ReadKey();
Samozrejme to možno urobiť inak, neskôr si to ukážeme, zatiaľ sa však budeme venovať iba čítaniu jednotlivých znakov.
Analýza výskytu znakov vo vete
Napíšme si jednoduchý program, ktorý nám analyzuje zadanú vetu. Bude
nás zaujímať počet samohlások, spoluhlások a počet nepísmenných znakov
(napr. medzera alebo !
).
Daný textový reťazec si najskôr v programe zadáme napevno, aby sme ho
nemuseli pri každom spustení písať. Keď bude program hotový, nahradíme ho
Console.ReadLine()
. Reťazec budeme prechádzať cyklom po jednom
znaku. Je nutné podotknúť, že neapelujeme na rýchlosť programu a budeme
voliť názorné a jednoduché riešenia.
Najprv si pripravme kód, definujme si samohlásky a spoluhlásky. Počet ostatných znakov nemusíme počítať, pretože bude zodpovedať dĺžke reťazca mínus samohlásky a spoluhlásky. Aby sme nemuseli riešiť veľkosť písmen, celý reťazec na začiatku prevedieme na malé písmená. Pripravme si premenné, do ktorých budeme ukladať jednotlivé počty. Pretože sa jedná o zložitejší kód, nezabudneme ho opatriť komentármi:
// the string that we want to analyze string s = "A programmer gets stuck in the shower because the instructions on the shampoo were: Lather, Wash, and Repeat."; Console.WriteLine(s); s = s.ToLower(); // counters initialization int vowelsCount = 0; int consonantsCount = 0; // definition of character groups string vowels = "aeiouy"; string consonants = "bcdfghjklmnpqrstvwxz"; // the main loop foreach (char c in s) { } Console.ReadKey();
Spočiatku si pripravíme reťazec a prevedieme ho na malé písmená.
Počítadlá vynulujeme. Na definície znakov nám postačia obyčajné
reťazce. Hlavný cyklus nám prejde jednotlivé znaky v reťazci
s
, pričom v každej iterácii cyklu bude v premennej
c
aktuálny znak.
Poďme plniť počítadlá. Pre jednoduchosť už nebudeme opisovať zvyšok kódu a presunieme sa len k cyklu:
// the main loop foreach (char c in s) { if (vowels.Contains(c)) vowelsCount++; else if (consonants.Contains(c)) consonantsCount++; }
Metódu Contains()
na reťazci už poznáme. Ako parameter jej
možno odovzdať ako podreťazec, tak aj priamo znak. Daný znak c
našej vety teda najskôr skúsime vyhľadať v reťazci vowels
a
prípadne zvýšiť ich počítadlo. Pokiaľ znak v samohláskach nie je,
pozrieme sa do spoluhlások a prípadne opätovne zvýšime ich počítadlo.
Teraz nám už chýba len výpis na koniec:
{CSHARP_CONSOLE}
// the string that we want to analyze
string s = "A programmer gets stuck in the shower because the instructions on the shampoo were: Lather, Wash, and Repeat.";
Console.WriteLine(s);
s = s.ToLower();
// Counters initialization
int vowelsCount = 0;
int consonantsCount = 0;
// definition of character groups
string vowels = "aeiouy";
string consonants = "bcdfghjklmnpqrstvwxz";
// the main loop
foreach (char c in s)
{
if (vowels.Contains(c))
vowelsCount++;
else
if (consonants.Contains(c))
consonantsCount++;
}
Console.WriteLine("Vowels: {0}", vowelsCount);
Console.WriteLine("Consonants: {0}", consonantsCount);
Console.WriteLine("Non-alphanumeric characters: {0}", s.Length - (vowelsCount + consonantsCount));
Console.ReadKey();
{/CSHARP_CONSOLE}
Výstup:
Konzolová aplikácia
A programmer gets stuck in the shower because the instructions on the shampoo were: Lather, Wash, and Repeat.
Vowels: 33
Consonants: 55
Non-alphanumeric characters: 21
A je to!
ASCII hodnota
Možno ste už niekedy počuli o tabuľke ASCII. Najmä v ére operačného
systému MS-DOS prakticky neexistovala iná možnosť, ako zaznamenávať text.
Jednotlivé znaky boli uložené ako čísla typu byte
, teda s
rozsahom hodnôt od 0
do 255
. V systéme bola
uložená tzv. ASCII tabuľka, ktorá mala 256 znakov a každému ASCII kódu
(číselnému kódu) priraďovala jeden znak.
Asi je vám jasné, prečo tento spôsob nepretrval dodnes. Do tabuľky sa
jednoducho nezmestili všetky znaky všetkých národných abecied. Dnes sa
používa Unicode (UTF-8) kódovanie, v ktorom sú znaky reprezentované trochu
iným spôsobom. V C# máme možnosť pracovať s ASCII hodnotami jednotlivých
znakov. Hlavná výhoda spočíva v tom, že znaky sú uložené v tabuľke za
sebou podľa abecedy. Napr. na pozícii 97
teda nájdeme
'a'
, na 98
'b'
a tak ďalej. Podobne je
to s číslami, avšak diakritické znaky sú v ASCII bohužiaľ len nejako
rozhádzané.
Skúsme si teraz previesť znak do jeho ASCII hodnoty a naopak podľa ASCII hodnoty daný znak vytvoriť:
{CSHARP_CONSOLE}
char c; // character
int i; // ordinal (ASCII) value of the character
// conversion from text to ASCII value
c = 'a';
i = (int)c;
Console.WriteLine("The character {0} was converted to its ASCII value of {1}", c, i);
// conversion from an ASCII value to text
i = 98;
c = (char)i;
Console.WriteLine("The ASCII value of {1} was converted to its textual value of {0}", c, i);
Console.ReadKey();
{/CSHARP_CONSOLE}
Prevodom sa hovorí pretypovanie, ale o tom sa bližšie pobavíme až neskôr.
Caesarova šifra
Vytvoríme si jednoduchý program na šifrovanie textu. Ak ste niekedy počuli o Caesarovej šifre, bude to presne to, čo si tu naprogramujeme. Šifrovanie textu spočíva v posúvaní znaku v abecede o určitý, pevne stanovený počet znakov. Napríklad slovo "hello" sa s posunom textu o 1 zašifruje ako "ifmmp". Posun umožníme užívateľovi vybrať. Algoritmus tu máme samozrejme opäť vysvetlený, a to v článku Caesarova šifra. Program si dokonca môžete vyskúšať v praxi - Online Caesarova šifra.
Vráťme sa k programovaniu a pripravme si kód. Budeme potrebovať premenné
pre pôvodný text, zašifrovanú správu a pre posun. Ďalej cyklus
prechádzajúci jednotlivé znaky a výpis zašifrovanej správy. Správu si
necháme zapísanú napevno v kóde, aby sme ju nemuseli pri každom spustení
programu písať. Po dokončení nahradíme obsah premennej metódou
Console.ReadLine()
. Šifra nepočíta s diakritikou, medzerami a
interpunkčnými znamienkami. Diakritiku budeme bojkotovať a budeme
predpokladať, že ju používateľ nebude zadávať. Diakritiku, rovnako ako
čokoľvek okrem písmen, by sme potom mali v ideálnom prípade pred
šifrovaním odstrániť:
// variable initialization string s = "gaiusjuliuscaesar"; Console.WriteLine("Original message: {0}", s); string message = ""; int shift = 1; // loop iterating over characters foreach(char c in s) { } // printing Console.WriteLine("Encrypted message: {0}", message); Console.ReadKey();
Kódom string message = ""
ukladáme do premennej
message
prázdny string
.
Teraz sa presunieme dovnútra cyklu, prevedieme znak v c
na
ASCII hodnotu (čiže ordinálnu hodnotu), túto hodnotu zvýšime o
shift
a prevedieme späť na znak. Tento znak nakoniec pripojíme k
výslednej správe:
{CSHARP_CONSOLE}
// variable initialization
string s = "gaiusjuliuscaesar";
Console.WriteLine("Original message: {0}", s);
string message = "";
int shift = 1;
// loop iterating over characters
foreach(char c in s)
{
int i = (int)c;
i += shift;
char character = (char)i;
message += character;
}
// printing
Console.WriteLine("Encrypted message: {0}", message);
Console.ReadKey();
{/CSHARP_CONSOLE}
Operátor +=
vykoná to isté, ako keby sme
napísali i = i + shift
.
Výstup:
Konzolová aplikácia
Original message: gaiusjuliuscaesar
Encrypted message: hbjvtkvmjvtdbftbs
Program si vyskúšame. Výsledok vyzerá celkom dobre. Skúsme si však
zadať vyšší posun alebo napísať slovo „zebra“. Vidíme, že znaky
môžu po 'z'
pretiecť do ASCII hodnôt ďalších znakov, v texte
teda už nemáme len písmená, ale ďalšie škaredé znaky. Uzavrieme znaky do
kruhu tak, aby posun plynule po 'z'
prešiel opäť k
'a'
a ďalej. Postačí nám k tomu jednoduchá podmienka, ktorá
od novej ASCII hodnoty odpočíta celú abecedu tak, aby sme začínali opäť
na 'a'
:
int i = (int)c; i += shift; // overflow control if (i > (int)'z') i -= 26; char character = (char)i; message += character;
Pokiaľ i
presiahne ASCII hodnotu 'z'
, znížime ho
o 26
znakov (toľko znakov má anglická abeceda). Operátor
-=
vykoná to isté, ako keby sme napísali
i = i - 26
. Je to jednoduché riešenie a náš program je teraz
funkčný. Všimnime si, že nikde nepoužívame priame kódy znakov. V
podmienke je teda (int)'z'
, aj keď by sme tam mohli napísať
rovno 122
. Je to z dôvodu, aby bol náš program plne odtienený
od explicitných ASCII hodnôt a bolo viditeľnejšie, ako funguje. Cvične si
skúsme vytvoriť dešifrovanie.
V nasledujúcom cvičení, Riešené úlohy k 10. lekcii C# .NET, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 4x (96.63 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#