Pouze tento týden sleva až 80 % na e-learning týkající se C# .NET. Zároveň využij akci až 30 % zdarma při nákupu e-learningu - Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
discount 30 + hiring

Lekce 10 - Textové řetězce v C# podruhé - Práce s jednotlivými znaky

V předešlém cvičení, Řešené úlohy k 8.-9. lekci C# .NET, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

Pokud jste vycítili v C# nějakou podobnost mezi polem a textovým řetězcem, tak jste vycítili správně. Pro 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:

string s = "C# .NET";
Console.WriteLine(s);
Console.WriteLine(s[1]);
Console.ReadKey();

Výstup:

C# .NET
#

Vidíme, že můžeme ke znakům v řetězci 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 jde udělat jinak, později si to ukážeme, zatím se 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. Rovnou zde říkám, že neapelujeme na rychlost programu a budeme volit názorná a jednoduchá řešení.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Nejprve si připravme kód, definujme si samohlásky a souhlásky. Počet ostatních znaků nemusíme počítat, bude to délka řetězce mínus 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, nebudeme zapomínat na komentáře.

// ř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ž nebudu opisovat zbytek kódu a přesunu 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 ji 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 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 chybí již jen výpis na konec:

Console.WriteLine("Samohlásek: {0}", pocetSamohlasek);
Console.WriteLine("Souhlásek: {0}", pocetSouhlasek);
Console.WriteLine("Nepísmenných znaků: {0}", s.Length - (pocetSamohlasek + pocetSouhlasek));

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 nebyla 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. Nyní se používá Unicode (UTF-8) kódování, kde 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 je v tom, že znaky jsou uloženy v tabulce za sebou, podle abecedy. Např. na pozici 97 nalezneme 'a', na 98 'b' a podobně. Podobně je to s čísly, diakritické znaky tam budou 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:

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();

Převodům se říká přetypování, ale o tom se blíže pobavíme až později.

Cézarova šifra

Vytvoříme si jednoduchý program pro šifrování textu. Pokud jste někdy slyšeli o Cézarově š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 přeloží jako "bipk". Posun umožníme uživateli vybrat. Algoritmus zde máme samozřejmě opět vysvětlený a to v článku Cézarova šifra. Program si dokonce můžete vyzkoušet v praxi - Online cézarova š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 bojkovat a budeme předpokládat, že ji uživatel nebude zadávat. Ideálně bychom poté měli diakritiku před šifrováním odstranit, stejně tak cokoli kromě písmen.

// 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();

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ě:

int i = (int)c;
i += posun;
char znak = (char)i;
zprava += znak;

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á to samé, jako bychom napsali i = i - 26. Je to jednoduché 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 (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 lépe viditelné, jak funguje. Cvičně si zkuste udělat dešifrování.

V následujícím cvičení, Řešené úlohy k 10. 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 1282x (48.75 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

Předchozí článek
Řešené úlohy k 8.-9. lekci C# .NET
Všechny články v sekci
Základní konstrukce jazyka C# .NET
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 10. lekci C# .NET
Článek pro vás napsal David Čápka
Avatar
Uživatelské hodnocení:
241 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 13 let. Má rád Nirvanu, sushi a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity

 

 

Komentáře
Zobrazit starší komentáře (87)

Avatar
Alesh
Překladatel
Avatar
Alesh:21.6.2020 17:24

Cézarova šifra je spíš taková legrácka než metoda, jak něco opravdu bezpečně zašifrovat, to je snad jasné. Možná ještě v době, kdy neexistovaly počítače, že to mohlo mít nějaké reálné využití, samozřejmě za předpokladu, že ten posun se udělal o nějaký "těžko odhadnutelný" posun. Dnes bys velmi jednoduše naprogramoval nástroj, který by zkoušel různé posuny a "na oči" bys posoudil, zda ten text po tom posunu je ten původní nezašifrovaný.
Pokud jde o tvůj dotaz, tak tam bys v podstatě asi mohl udělat 2 věci:

  1. naprogramovat "šifrovač" a "dešifrovač", v každém z nich by byl natvrdo nastavený posun o "n" znaků (šifrovač posunu tedy o "n" a "dešifrovač" o "-n").
  2. variantou je mít jeden nástroj, kde bude možná zadat posun uživatelem. Pak jinou cestou příjemci sdělíš posun, pak mu pošleš zprávu a on si i zas posune o -posun.

Ale jak jsem již psal, tuto úlohu ber jen jako cvičení s char proměnnými, reálné využití k šifrování to prostě nemá, čili tvoje úvahy jsou v tomto smyslu víceméně zbytečné.

 
Odpovědět
21.6.2020 17:24
Avatar
drevpe
Člen
Avatar
drevpe:2.7.2020 15:47

Ahoj jen taková poznámka.
dnes zkouším kód z článku a kompilátor mi hlásí error
Error 1 The best overloaded method match for 'string.Conta­ins(string)' has some invalid arguments
Error 2 Argument 1: cannot convert from 'char' to 'string'

když zkonvertuji tak je to ok.

foreach (char c in s)
{
    if (samohlasky.Contains(c))
        pocetSamohlasek++;
    else if (souhlasky.Contains(Convert.ToString(c)))
        pocetSouhlasek++;
}
 
Odpovědět
2.7.2020 15:47
Avatar
drevpe
Člen
Avatar
Odpovídá na drevpe
drevpe:2.7.2020 15:59

škoda že nemůžu editovat komentář...
už vím čím to je... .NET Framework verze a použití using System.Linq

 
Odpovědět
2.7.2020 15:59
Avatar
Rostislav Danko:5. srpna 16:11

Ahoj,
chci se zeptat, proč mi to jako výstup vrací : programátor se `asekne ve sprše, protože instrukce na šampónu byly: namydlit, omýt, opakovat.
všechny znaky mi to dešifruje zpátky správně až na "z" .
link ke kódu : https://www.itnetwork.cz/…lighter/1494
díky za radu

 
Odpovědět
5. srpna 16:11
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Rostislav Danko
DarkCoder:5. srpna 19:12

Máš chybu v posunu. Pokud znakem bude písmeno z (ASCII 122), pak při posunu o 26 zpět se dostaneš na hodnotu 96, což odpovídá znaku ' nikoli znaku a (ASCII 97).

Odpovědět
5. srpna 19:12
"Chceš-li předávat své znalosti, měj kvalitní podklady."
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Rostislav Danko
DarkCoder:5. srpna 19:45

Ještě jinak, přesněji. Když šifruješ písmeno z, dostaneš a. Při dešifrování písmene a posouváš o 1 zpět. Čímž se dostaneš na ASCII hodnotu 96 odpovídající znaku '. Následující podmínka je nesmyslná a nikdy nenastane. Musíš testovat na podsah nikoli přesah.

Odpovědět
5. srpna 19:45
"Chceš-li předávat své znalosti, měj kvalitní podklady."
Avatar
Odpovídá na DarkCoder
Rostislav Danko:5. srpna 19:56

Díky, už sem na to asi přišel. ve výše poslaném kódu jsem vyměnil znaménka u obou loopů "foreach" a šlape to jak má, viz https://www.itnetwork.cz/…lighter/1495

konkrétně :
i -= 26 -------> i += 26
a += 26 -------> a -= 26

 
Odpovědět
5. srpna 19:56
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Rostislav Danko
DarkCoder:5. srpna 20:10

Jsi si jist že šifrované písmeno z bude písmeno a?

Odpovědět
5. srpna 20:10
"Chceš-li předávat své znalosti, měj kvalitní podklady."
Avatar
Odpovídá na DarkCoder
Rostislav Danko:5. srpna 20:31

No, vypadá to takto...viz screen

 
Odpovědět
5. srpna 20:31
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Rostislav Danko
DarkCoder:5. srpna 21:03

Samotný pohled na zašifrovanou zprávu říká že něco je špatně. To co si vytvořil je nekonstantní posun. ASCII hodnoty malých písmen jsou v rozsahu 97-122. Znaky s diakritikou mají hodnoty vyšší než 122. Pokud řešíš cyklickou šifru, nemůžeš provádět posun vpřed nad mezní hodnotu. Jak už jsem naznačil a na výstupu je to krásně vidět, hodnoty znaků od znaků z výše nebudou přetočeny, z se nestane a. U zašifrování kontroluješ přesah a posouváš zpět, u dešifrování kontroluješ podsah a posouváš vpřed. Tedy dvě doporučení.. odstraň diakritiku, použij pouze písmena, ať už velká nebo malá a jako vstupní řetězec pro testování si zadej celou abecedu, od a do z. Na tom krásně uvidíš, jak se tvůj program chová.

Odpovědět
5. srpna 21:03
"Chceš-li předávat své znalosti, měj kvalitní podklady."
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 97. Zobrazit vše