Lekce 11 - Textové řetězce v C# podruhé - Práce s jednotlivými znaky
V minulé lekci, Nejčastější chyby C# začátečníků - Umíš pojmenovat proměnné?, jsme si ukázali nejčastější chyby začátečníků v C# .NET ohledně pojmenování proměnných.
Pokud patříte mezi ty, kteří v C# spatřovali nějakou podobnost mezi
polem a textovým řetězcem, tak jste uvažovali správně. Pro ty ostatní
může být překvapením, že string
je v podstatě pole
znaků (hodnot typu char
) a můžeme s ním i takto
pracovat.
Nejprve si vyzkoušejme, že to všechno funguje. Rozcvičíme se na jednoduchém vypsání znaku na dané pozici:
{CSHARP_CONSOLE} string s = "C# .NET"; Console.WriteLine(s); Console.WriteLine(s[1]); Console.ReadKey(); {/CSHARP_CONSOLE}
Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.
Výstup:
Konzolová aplikace
C# .NET
#
Vidíme, že ke znakům v řetězci můžeme přistupovat přes hranatou závorku, jako tomu je i u pole. Zklamáním může být, že znaky na dané pozici jsou v C# read-only, nemůžeme tedy napsat:
string s = "C# .NET"; s[1] = '!'; Console.WriteLine(s); Console.ReadKey();
Samozřejmě to lze udělat jinak, později si to ukážeme, zatím se však budeme věnovat pouze čtení jednotlivých znaků.
Analýza výskytu znaků ve větě
Napišme si jednoduchý program, který nám analyzuje zadanou větu. Bude
nás zajímat počet samohlásek, souhlásek a počet nepísmenných znaků
(např. mezera nebo !
).
Daný textový řetězec si nejprve v programu zadáme napevno, abychom ho
nemuseli při každém spuštění psát. Až bude program hotový, nahradíme
ho Console.ReadLine()
. Řetězec budeme projíždět cyklem po
jednom znaku. Je nutné podotknout, že neapelujeme na rychlost programu a
budeme volit názorná a jednoduchá řešení.
Nejprve si připravme kód, definujme si samohlásky a souhlásky. Počet ostatních znaků nemusíme počítat, jelikož bude odpovídat délce řetězce minus samohlásky a souhlásky. Abychom nemuseli řešit velikost písmen, celý řetězec na začátku převedeme na malá písmena. Připravme si proměnné, do kterých budeme ukládat jednotlivé počty. Protože se jedná o složitější kód, nezapomeneme jej opatřit komentáři.
// řetězec, který chceme analyzovat string s = "Mount Everest"; Console.WriteLine(s); s = s.ToLower(); // inicializace počítadel int pocetSamohlasek = 0; int pocetSouhlasek = 0; // definice typů znaků string samohlasky = "aeiouyáéěíóúůý"; string souhlasky = "bcčdďfghjklmnpqrřsštťvwxzž"; // hlavní cyklus foreach (char c in s) { } Console.ReadKey();
Zpočátku si připravíme řetězec a převedeme ho na malá písmena.
Počítadla vynulujeme. Na definice znaků nám postačí obyčejné řetězce.
Hlavní cyklus nám projede jednotlivé znaky v řetězci s
,
přičemž v každé iteraci cyklu bude v proměnné c
aktuální
znak.
Pojďme plnit počítadla. Pro jednoduchost již nebudeme opisovat zbytek kódu a přesuneme se jen k cyklu:
// hlavní cyklus foreach (char c in s) { if (samohlasky.Contains(c)) pocetSamohlasek++; else if (souhlasky.Contains(c)) pocetSouhlasek++; }
Metodu Contains()
na řetězci již známe. Jako parametr jí
lze předat jak podřetězec, tak přímo znak. Daný znak c
naší
věty tedy nejprve zkusíme vyhledat v řetězci samohlasky
a
případně zvýšit jejich počítadlo. Pokud znak v samohláskách není,
podíváme se do souhlásek a případně opětovně zvýšíme jejich
počítadlo.
Nyní nám již chybí jen výpis na konec:
{CSHARP_CONSOLE} // řetězec, který chceme analyzovat string s = "Mount Everest"; Console.WriteLine(s); s = s.ToLower(); // inicializace počítadel int pocetSamohlasek = 0; int pocetSouhlasek = 0; // definice typů znaků string samohlasky = "aeiouyáéěíóúůý"; string souhlasky = "bcčdďfghjklmnpqrřsštťvwxzž"; // hlavní cyklus foreach (char c in s) { if (samohlasky.Contains(c)) pocetSamohlasek++; else if (souhlasky.Contains(c)) pocetSouhlasek++; } Console.WriteLine("Samohlásek: {0}", pocetSamohlasek); Console.WriteLine("Souhlásek: {0}", pocetSouhlasek); Console.WriteLine("Nepísmenných znaků: {0}", s.Length - (pocetSamohlasek + pocetSouhlasek)); Console.ReadKey(); {/CSHARP_CONSOLE}
Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.
Konzolová aplikace
Mount Everest
Samohlásek: 5
Souhlásek: 7
Nepísmenných znaků: 1
A je to!
ASCII hodnota
Možná jste již někdy slyšeli o ASCII tabulce. Zejména v éře
operačního systému MS-DOS prakticky neexistovala jiná možnost, jak
zaznamenávat text. Jednotlivé znaky byly uloženy jako čísla typu
byte
, tedy s rozsahem hodnot od 0
do 255
.
V systému byla uložena tzv. ASCII tabulka, která měla 256 znaků a každému
ASCII kódu (číselnému kódu) přiřazovala jeden znak.
Asi je vám jasné, proč tento způsob nepřetrval dodnes. Do tabulky se
jednoduše nevešly všechny znaky všech národních abeced. Dnes se používá
Unicode (UTF-8) kódování, v němž jsou znaky reprezentovány trochu jiným
způsobem. V C# máme možnost pracovat s ASCII hodnotami jednotlivých znaků.
Hlavní výhoda spočívá v tom, že znaky jsou uloženy v tabulce za sebou
podle abecedy. Např. na pozici 97
tedy nalezneme 'a'
,
na 98
'b'
a tak dále. Podobně je tomu s čísly,
avšak diakritické znaky jsou v ASCII bohužel jen nějak rozházeny.
Zkusme si nyní převést znak do jeho ASCII hodnoty a naopak podle ASCII hodnoty daný znak vytvořit:
{CSHARP_CONSOLE} char c; // znak int i; // ordinální (ASCII) hodnota znaku // převedeme znak na jeho ASCII hodnotu c = 'a'; i = (int)c; Console.WriteLine("Znak {0} jsme převedli na ASCII hodnotu {1}", c, i); // Převedeme ASCII hodnotu na znak i = 98; c = (char)i; Console.WriteLine("ASCII hodnotu {1} jsme převedli na znak {0}", c, i); Console.ReadKey(); {/CSHARP_CONSOLE}
Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.
Převodům se říká přetypování, ale o tom se blíže pobavíme až později.
Caesarova šifra
Vytvoříme si jednoduchý program pro šifrování textu. Pokud jste někdy slyšeli o Caesarově šifře, bude to přesně to, co si zde naprogramujeme. Šifrování textu spočívá v posouvání znaku v abecedě o určitý, pevně stanovený počet znaků. Například slovo "ahoj" se s posunem textu o 1 zašifruje jako "bipk". Posun umožníme uživateli vybrat. Algoritmus zde máme samozřejmě opět vysvětlený, a to v článku Caesarova šifra. Program si dokonce můžete vyzkoušet v praxi – Online Caesarova šifra.
Vraťme se k programování a připravme si kód. Budeme potřebovat
proměnné pro původní text, zašifrovanou zprávu a pro posun. Dále cyklus
projíždějící jednotlivé znaky a výpis zašifrované zprávy. Zprávu si
necháme zapsanou napevno v kódu, abychom ji nemuseli při každém spuštění
programu psát. Po dokončení nahradíme obsah proměnné metodou
Console.ReadLine()
. Šifra nepočítá s diakritikou, mezerami a
interpunkčními znaménky. Diakritiku budeme bojkotovat a budeme
předpokládat, že ji uživatel nebude zadávat. Diakritiku, stejně jako
cokoli kromě písmen, bychom poté měli v ideálním případě před
šifrováním odstranit.
// inicializace proměnných string s = "gaiusjuliuscaesar"; Console.WriteLine("Původní zpráva: {0}", s); string zprava = ""; int posun = 1; // cyklus projíždějící jednotlivé znaky foreach(char c in s) { } // výpis Console.WriteLine("Zašifrovaná zpráva: {0}", zprava); Console.ReadKey();
Kódem string zprava = ""
ukládáme do proměnné
zprava
prázdný string
.
Nyní se přesuneme dovnitř cyklu, převedeme znak v c
na ASCII
hodnotu (neboli ordinální hodnotu), tuto hodnotu zvýšíme o
posun
a převedeme zpět na znak. Tento znak nakonec připojíme k
výsledné zprávě:
{CSHARP_CONSOLE} // inicializace proměnných string s = "gaiusjuliuscaesar"; Console.WriteLine("Původní zpráva: {0}", s); string zprava = ""; int posun = 1; // cyklus projíždějící jednotlivé znaky foreach(char c in s) { int i = (int)c; i += posun; char znak = (char)i; zprava += znak; } // výpis Console.WriteLine("Zašifrovaná zpráva: {0}", zprava); Console.ReadKey(); {/CSHARP_CONSOLE}
Zkontroluj, zda výstupy programu odpovídají předloze. S jinými texty testy neprojdou.
Operátor +=
vykoná totéž, jako kdybychom
napsali i = i + posun
.
Konzolová aplikace
Původní zpráva: gaiusjuliuscaesar
Zašifrovaná zpráva: hbjvtkvmjvtdbftbs
Program si vyzkoušíme. Výsledek vypadá docela dobře. Zkusme si však
zadat vyšší posun nebo napsat slovo "zebra". Vidíme, že znaky mohou po
'z'
přetéct do ASCII hodnot dalších znaků, v textu tedy již
nemáme jen písmena, ale další ošklivé znaky. Uzavřeme znaky do kruhu tak,
aby posun plynule po 'z'
přešel opět k 'a'
a dále.
Postačí nám k tomu jednoduchá podmínka, která od nové ASCII hodnoty
odečte celou abecedu tak, abychom začínali opět na 'a'
.
int i = (int)c; i += posun; // kontrola přetečení if (i > (int)'z') i -= 26; char znak = (char)i; zprava += znak;
Pokud i
přesáhne ASCII hodnotu 'z'
, snížíme ho
o 26
znaků (tolik znaků má anglická abeceda). Operátor
-=
vykoná totéž, jako kdybychom napsali i = i - 26
.
Je to jednoduché řešení a náš program je nyní funkční. Všimněme si,
že nikde nepoužíváme přímé kódy znaků. V podmínce je tedy
(int)'z'
, i když bychom tam mohli napsat rovnou 122
.
Je to z důvodu, aby byl náš program plně odstíněn od explicitních ASCII
hodnot a bylo viditelnější, jak funguje. Cvičně si zkusme vytvořit
dešifrování.
V následujícím cvičení, Řešené úlohy k 11. lekci C# .NET, si procvičíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 1480x (48.75 kB)
Aplikace je včetně zdrojových kódů v jazyce C#