Lekce 13 - Textové řetězce v Javě podruhé - Práce s jednotlivými znaky
V minulé lekci, Nejčastější chyby Java nováčků - Umíš pojmenovat proměnné?, jsme si ukázali nejčastější chyby začátečníků v Javě ohledně pojmenování proměnných.
V dnešním Java tutoriálu se budeme zabývat přístupem k jednotlivým znakům textového řetězce.
Textový řetězec
Pokud patříte mezi ty, kteří vycítili 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ů
(char
) a můžeme s ním takto i pracovat. Pro přístup k
jednotlivým znakům slouží metoda charAt(index)
, kde
index
udává index znaku v řetězci (počínaje 0
).
Opačně pro zjištění indexu zadaného znaku slouží metoda
indexOf(znak)
, kde znak
je hledaný znak. Tato metoda
vrací index prvního výskytu daného znaku, a pokud jej v řetězci nenajde,
vrátí hodnotu -1
.
Nejprve si vyzkoušejme, že to všechno funguje. Rozcvičíme se na jednoduchém vypsání znaku na dané pozici:
{JAVA_CONSOLE}
String jazyk = "Java";
System.out.println(jazyk);
System.out.println(jazyk.charAt(2));
{/JAVA_CONSOLE}
Výstup:
Konzolová aplikace
Java
v
A nyní se podíváme na zjištění indexu zadaného znaku:
{JAVA_CONSOLE}
String jazyk = "Java";
System.out.println(jazyk);
System.out.println(jazyk.indexOf('v'));
{/JAVA_CONSOLE}
Výstup:
Konzolová aplikace
Java
2
Znaky na dané pozici jsou v Javě read-only, nemůžeme je tedy jednoduše změnit.
Samozřejmě to lze udělat i 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ý, řetězec
nahradíme metodou scanner.nextLine()
. Ř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, bude to délka ř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:
// řetězec, který chceme analyzovat String hora = "Mount Everest"; System.out.println(hora); hora = hora.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 for (char znak : hora.toCharArray()) { }
Protože se jedná o složitější kód, nezapomeneme jej opatřit komentáři.
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ý typ
String
. Hlavní cyklus nám projede jednotlivé znaky v řetězci
hora
. Abychom mohli znaky iterovat (procházet cyklem), musíme si
typ String
převést na pole znaků. V úvodu jsme sice poukázali
na to, že typ String
je vlastně pole znaků, ale ne
plnohodnotné. Obsahuje něco navíc a něco mu chybí, např. možnost prvky
iterovat cyklem. V cyklu tedy na proměnnou hora
zavoláme metodu
toCharArray()
, která vrátí plnohodnotné pole znaků z řetězce
hora
. V každé iteraci cyklu bude v proměnné znak
aktuální znak řetězce hora
.
Pojďme plnit počítadla. Pro jednoduchost již nebudeme opisovat zbytek kódu a přesuneme se jen k cyklu:
// hlavní cyklus for (char znak : hora.toCharArray()) { if (samohlasky.contains(String.valueOf(znak))) { pocetSamohlasek++; } else if (souhlasky.contains(String.valueOf(znak))) { pocetSouhlasek++; } }
Metodu contains()
na řetězci již známe. Jako parametr jí
lze předat podřetězec. Bohužel nemůžeme předat znak char
,
musíme jej tedy převést na String
. K tomu slouží výše
uvedená metoda valueOf()
. Daný znak c
naší věty
tedy nejprve zkusíme vyhledat v řetězci samohlasky
a případně
zvýšíme 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
chybí již jen výpis na konec. V textu použijeme speciální sekvenci znaků
%n
(nebo \n
), která způsobí odřádkování.
Použitím sekvence %n
(namísto \n
) zajistíme
cross-platform kompatibilitu. Java tedy odřádkuje správně jak na MacOS, tak
třeba na Windows:
{JAVA_CONSOLE}
// řetězec, který chceme analyzovat
String hora = "Mount Everest";
System.out.println(hora);
hora = hora.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
for (char znak : hora.toCharArray()) {
if (samohlasky.contains(String.valueOf(znak))) {
pocetSamohlasek++;
} else if (souhlasky.contains(String.valueOf(znak))) {
pocetSouhlasek++;
}
}
System.out.printf("Samohlásek: %d%n", pocetSamohlasek);
System.out.printf("Souhlásek: %d%n", pocetSouhlasek);
System.out.printf("Nepísmenných znaků: %d%n", hora.length() - (pocetSamohlasek + pocetSouhlasek));
{/JAVA_CONSOLE}
Výstup programu:
Konzolová aplikace
Mount Everest
Samohlásek: 5
Souhlásek: 7
Nepísmenných znaků: 1
Zpětné lomítko \
bychom na české klávesnici
napsali stiskem Pravého Alt + Q:
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 také 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. Nicméně v Javě stále máme možnost pracovat s ASCII hodnotami
jednotlivých znaků. Hlavní výhodou je, že znaky jsou uloženy v tabulce za
sebou, podle abecedy. Např. na pozici 97
nalezneme znak
a
, na pozici 98
znak 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:
{JAVA_CONSOLE}
char znak; // znak
int hodnotaAscii; // ordinální (ASCII) hodnota znaku
// převedeme znak na jeho ASCII hodnotu
znak = 'a';
hodnotaAscii = (int)znak;
System.out.printf("Znak %c jsme převedli na ASCII hodnotu %d%n", znak, hodnotaAscii);
// Převedeme ASCII hodnotu na znak
hodnotaAscii = 98;
znak = (char)hodnotaAscii;
System.out.printf("ASCII hodnotu %d jsme převedli na znak %c", hodnotaAscii, znak);
{/JAVA_CONSOLE}
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
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 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
scanner.nextLine()
. Š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 puvodniZprava = "gaiusjuliuscaesar"; System.out.printf("Původní zpráva: %s%n", puvodniZprava); String zasifrovanaZprava = ""; int posun = 1; // cyklus projíždějící jednotlivé znaky for (char znak : puvodniZprava.toCharArray()) { } // výpis System.out.printf("Zašifrovaná zpráva: %s%n", zasifrovanaZprava);
Nyní se přesuneme dovnitř cyklu, převedeme proměnnou se znakem
znak
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ě:
{JAVA_CONSOLE}
// inicializace proměnných
String puvodniZprava = "gaiusjuliuscaesar";
System.out.printf("Původní zpráva: %s%n", puvodniZprava);
String zasifrovanaZprava = "";
int posun = 1;
// cyklus projíždějící jednotlivé znaky
for (char znak : puvodniZprava.toCharArray()) {
int ascii = (int)znak;
ascii += posun;
znak = (char)ascii;
zasifrovanaZprava += znak;
}
// výpis
System.out.printf("Zašifrovaná zpráva: %s%n", zasifrovanaZprava);
{/JAVA_CONSOLE}
Výstup programu:
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 také další ošklivé znaky. Uzavřeme proto
znaky do kruhu tak, aby posun plynule po znaku z
přešel opět ke
znaku 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 ascii = (int)znak; ascii += posun; // kontrola přetečení if (ascii > (int)'z') { ascii -= 26; } znak = (char)ascii; zprava += znak;
Pokud hodnota
přesáhne ASCII hodnotu 'z'
,
snížíme ji o 26
znaků (tolik znaků má anglická abeceda).
Operátor -=
vykoná totéž, jako kdybychom napsali
hodnota = hodnota - 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
, přestože 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
zkuste vytvořit dešifrování
V následujícím cvičení, Řešené úlohy k 13. lekci Javy, 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 1306x (19.24 kB)
Aplikace je včetně zdrojových kódů v jazyce Java