Lekce 9 - Textové řetězce v Dartu podruhé - Práce s jednotlivými znaky
V předešlém cvičení, Řešené úlohy k 7.-8. lekci Dartu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V milé lekci kurzu, Řešené úlohy k 7.-8. lekci Dartu, jsme se naučili pracovat se seznamy. Pokud
jste vycítili nějakou podobnost mezi seznamem 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ě seznam znaků a můžeme s
ním i takto pracovat. Dart však, oproti jiným programovacím jazykům, nemá
datový typ, který by uchovával samotný znak. V Dartu je pro nás jeden znak
to samé, jako String
o délce 1.
Nejprve si vyzkoušejme, že to všechno funguje. Rozcvičíme se na jednoduchém vypsání znaku na dané pozici:
String s = 'Ahoj ITnetwork'; print(s); print(s[2]);
Výstup programu:
Konzolová aplikace
Ahoj ITnetwork
o
Vidíme, že můžeme ke znakům v řetězci přistupovat přes hranatou závorku, jako tomu je i u seznamu. Zklamáním může být, že znaky na dané pozici jsou v Dartu read-only, nemůžeme tedy napsat:
String s = 'Ahoj ITnetwork'; s[1] = 'o'; print(s);
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 zanalyzuje 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 stdin.readLineSync()
. Řetězec budeme projíždět cyklem po
jednom znaku – toho docílíme tak, že si řetězec rozdělíme podle vzoru
'' (tedy prázdný vzor), pro který nám metoda split()
vrátí
seznam řetězců, kde každý z nich bude mít právě jeden znak. Rovnou zde
říkám, ž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 nepísmen 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 = 'Programátor se zasekne ve sprše, protože instrukce na šampónu byly: Namydlit, omýt, opakovat.'; print(s); s = s.toLowerCase(); // 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 s.split('').forEach((String c) { });
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é Stringy.
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 s.split('').forEach((String c) { 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 libovolný podřetězec. 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:
print('Samohlásek: $pocetSamohlasek'); print('Souhlásek: $pocetSouhlasek'); print('Nepísmenných znaků: ${s.length - (pocetSamohlasek + pocetSouhlasek)}');
Výpis programu:
Konzolová aplikace
Programátor se zasekne ve sprše, protože instrukce na šampónu byly: Namydlit, omýt, opakovat.
Samohlásek: 31
Souhlásek: 45
Nepísmenných znaků: 17
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 Dartu 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"
, 98
"b"
a podobně. Podobně je to s
čísly, diakritické znaky tam budou bohužel jen nějak rozházeny.
Pro převod budeme využívat ASCII, kterou nalezneme v knihovně
dart:convert
.
Zkusme si nyní převést znak do jeho ASCII hodnoty a naopak podle ASCII hodnoty daný znak vytvořit:
String c; // znak int i; // ordinální (ASCII) hodnota znaku // převedeme znak na jeho ASCII hodnotu c = 'a'; i = ASCII.encode(c).first; print('Znak $c jsme převedli na ASCII hodnotu $i'); // Převedeme ASCII hodnotu na znak i = 98; c = ASCII.decode([i]); print('ASCII hodnotu $i jsme převedli na znak $c');
Všimněte si, že po zakódovávání řetězce pracujeme pouze s první
hodnotou (first
), jelikož funkce pracuje obecně a vrací seznam
ASCII hodnot všech znaků v řetězci. Obdobně tak, že při zakódovávání
ASCII hodnoty do řetězce musíme vložit seznam hodnot.
Výstup programu:
Konzolová aplikace
Znak a jsme převedli na ASCII hodnotu 97
ASCII hodnotu 98 jsme převedli na znak b
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
stdin.readLineSync()
. Š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 = 'cernediryjsoutamkdebuhdelilnulou'; print('Původní zpráva: $s'); String zprava = ''; int posun = 1; // cyklus projíždějící jednotlivé znaky s.split('').forEach((String c) { }); // výpis print('Zašifrovaná zpráva: $zprava');
Nyní se přesuneme dovnitř cyklu, převedeme znak 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 = ASCII.encode(c).first; i += posun; String znak = ASCII.decode([i]); zprava += znak;
Výstup programu:
Konzolová aplikace
Původní zpráva: cernediryjsoutamkdebuhdelilnulou
Zašifrovaná zpráva: dfsofejszktpvubnlefcviefmjmovmpv
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 = ASCII.encode(c).first; i += posun; // kontrola přetečení if (i > ASCII.encode('z').first) i -= 26; String znak = ASCII.decode([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 ASCII.encode('z').first
, 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 příští lekci, Řešené úlohy k 9. lekci Dartu, si ukážeme, že String umí přeci jen ještě něco navíc. Prozradím, že budeme dekódovat Morzeovu abecedu.
V následujícím cvičení, Řešené úlohy k 9. lekci Dartu, 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 4x (2.96 kB)
Aplikace je včetně zdrojových kódů v jazyce Dart