13. diel - Textové reťazce v Jave - Práca s jednotlivými znakmi
V minulej lekcii, Najčastejšie chyby Java nováčikov - Vieš pomenovať premenné?, sme si ukázali najčastejšie chyby začiatočníkov v Jave týkajúce sa pomenovania premenných.
V dnešnom Java tutoriále sa budeme zaoberať prístupom k jednotlivým znakom textového reťazca.
Textový reťazec
Ak ste vycítili nejakú podobnosť medzi poľom a textovým reťazcom, tak
ste vycítili správne. Pre ostatných môže byť prekvapením, že
String
je v podstate pole znakov
(char
) a môžeme s ním aj takto pracovať. Pre prístup
k jednotlivým znakom slúži metóda charAt(index)
, kde
index
udáva index znaku v reťazci (počnúc 0
).
Opačne na zistenie indexu zadaného znaku slúži metóda
indexOf(character)
, kde character
je hľadaný znak.
Táto metóda vracia index prvého výskytu daného znaku a ak ho v reťazci
nenájde, vráti hodnotu -1
.
Najprv si vyskúšajme, že to všetko funguje. Rozcvičíme sa pri jednoduchom vypísaní znaku na danej pozícii:
{JAVA_CONSOLE}
String language= "Java";
System.out.println(language);
System.out.println(language.charAt(2));
{/JAVA_CONSOLE}
Výstup:
Konzolová aplikácia
Java
v
A teraz sa pozrieme na zistenie indexu zadaného znaku:
{JAVA_CONSOLE}
String language = "Java";
System.out.println(language);
System.out.println(language.indexOf('v'));
{/JAVA_CONSOLE}
Výstup:
Konzolová aplikácia
Java
2
Znaky na danej pozícii sú v Jave read-only, nemôžeme ich teda jednoducho zmeniť.
Samozrejme to ide urobiť inak, neskôr si to ukážeme, zatiaľ sa 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
metódou scanner.nextLine()
. Reťazec budeme prechádzať cyklom po
jednom znaku. Rovno tu zdôrazním, ž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ť, bude to dĺžka 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:
// the string that we want to analyze String mountain = "Mount Everest"; System.out.println(mountain); mountain = mountain.toLowerCase(); // Counters initialization int vowelsCount = 0; int consonantsCount = 0; // definition of character groups String vowels = "aeiouy"; String consonants = "bcdfghjklmnpqrstvwxz"; // the main loop for (char character : mountain.toCharArray()) { }
Pretože sa jedná o zložitejší kód, nebudeme zabúdať na komentáre.
Spočiatku si pripravíme reťazec a prevedieme ho na malé písmená.
Počítadlá vynulujeme. Na definície znakov nám postačí obyčajný typ
String
. Hlavný cyklus nám prejde jednotlivé znaky v reťazci
mountain
. Aby sme mohli znaky iterovať (prechádzať cyklom),
musíme si typ String
previesť na pole znakov. V úvode som
hovoril, že typ String
vlastne pole znakov je, ale nie
plnohodnotné. Obsahuje niečo navyše a niečo mu chýba, napr. možnosť prvky
iterovať cyklom. V cykle teda na premennú mountain
zavoláme
metódu toCharArray()
, ktorá vráti plnohodnotné pole znakov z
reťazca mountain
. V každej iterácii cyklu bude v premennej
character
aktuálny znak reťazca mountain
.
Poďme plniť počítadlá, pre jednoduchosť už nebudem opisovať zvyšok kódu a presuniem sa len k cyklu:
// the main loop for (char character : mountain.toCharArray()) { if (vowels.contains(String.valueOf(character))) { vowelsCount++; } else if (consonants.contains(String.valueOf(character))) { consonantsCount++; } }
Metódu contains()
na reťazci už poznáme, ako parameter je
možné jej odovzdať podreťazec. Bohužiaľ nemôžeme odovzdať znak
char
, musíme teda znak previesť na String
. Na to
slúži vyššie uvedená metóda valueOf()
. Daný znak našej vety
teda najskôr skúsime vyhľadať v reťazci vowels
a prípadne
zvýšiť ich počítadlo. Ak v samohláskach (vowels) nie je, pozrieme sa do
spoluhlások (consonants) a prípadne opätovne zvýšime ich počítadlo. Teraz
nám chýba už iba výpis na koniec. V texte použijeme špeciálnu sekvenciu
znakov %n
(alebo \n
), tá spôsobí odriadkovanie.
Použitím sekvencie %n
(namiesto \n
) zaistíme
cross-platform kompatibilitu. Java teda odriadkuje správne na MacOS a aj
napríklad na Windows:
{JAVA_CONSOLE}
// the string that we want to analyze
String mountain = "Mount Everest";
System.out.println(mountain);
mountain = mountain.toLowerCase();
// Counters initialization
int vowelsCount = 0;
int consonantsCount = 0;
// definition of character groups
String vowels = "aeiouy";
String consonants = "bcdfghjklmnpqrstvwxz";
// the main loop
for (char character : mountain.toCharArray()) {
if (vowels.contains(String.valueOf(character))) {
vowelsCount++;
} else if (consonants.contains(String.valueOf(character))) {
consonantsCount++;
}
}
System.out.printf("Vowels: %d%n", vowelsCount);
System.out.printf("Consonants: %d%n", consonantsCount);
System.out.printf("Non-alphanumeric characters: %d%n", mountain.length() - (vowelsCount + consonantsCount));
{/JAVA_CONSOLE}
Výstup programu:
Konzolová aplikácia
Mount Everest
Vowels: 5
Consonants: 7
Non-alphanumeric characters: 1
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 nebola 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 tiež 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, teraz sa
používa Unicode (UTF-8) kódovanie, kde sú znaky reprezentované trochu iným
spôsobom. Avšak v Jave máme stále možnosť pracovať s ASCII hodnotami
jednotlivých znakov. Hlavná výhoda je v tom, že znaky sú uložené v
tabuľke za sebou podľa abecedy. Napr. na pozícii 97
nájdeme
znak a
, na pozícii 98
znak b
a podobne.
Podobne je to s číslami, diakritické znaky tam budú 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ť:
{JAVA_CONSOLE}
char character; // character
int asciiValue; // ordinal (ASCII) value of the character
// conversion from text to ASCII value
character = 'a';
asciiValue= (int)character;
System.out.printf("The character %c was converted to its ASCII value of %d%n", character, asciiValue);
// conversion from an ASCII value to text
asciiValue= 98;
character = (char)asciiValue;
System.out.printf("ASCII value of %d was converted to its textual value of %c", asciiValue, character);
{/JAVA_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
preloží ako "ifmmp"
. 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
scanner.nextLine()
. Šifra nepočíta s diakritikou, medzerami a
interpunkčnými znamienkami. Diakritiku budeme bojkotovať a budeme
predpokladať, že ju používateľ nebude zadávať. Ideálne by sme potom mali
diakritiku pred šifrovaním odstrániť, taktiež aj čokoľvek iné okrem
písmen:
// variable initialization String originalMessage = "blackholesarewheregoddividedbyzero"; System.out.printf("Original message: %s%n", originalMessage); String encryptedMessage = ""; int shift = 1; // loop iterating over characters for (char character : originalMessage.toCharArray()) { } // printing System.out.printf("Encrypted message: %s%n", encryptedMessage);
Teraz sa presunieme dovnútra cyklu, prevedieme premennú so znakom
character
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:
{JAVA_CONSOLE}
// variable initialization
String originalMessage= "blackholesarewheregoddividedbyzero";
System.out.printf("Original message: %s%n", originalMessage);
String encryptedMessage= "";
int shift = 1;
// loop iterating over characters
for (char character: originalMessage.toCharArray()) {
int ascii = (int)character;
ascii += shift;
character = (char)ascii;
encryptedMessage += character;
}
// printing
System.out.printf("Encrypted message: %s%n", encryptedMessage);
{/JAVA_CONSOLE}
Výstup programu:
Konzolová aplikácia
Original message: blackholesarewheregoddividedbyzero
Encrypted message: cmbdlipmftbsfxifsfhpeejwjefecz{fsp
Program si vyskúšame. Výsledok vyzerá celkom dobre. Vidíme však, že
znaky môžu pretiecť do ASCII hodnôt ďalších znakov, v texte teda už
nemáme len písmená, ale aj ďalšie škaredé znaky. Uzavrieme znaky do kruhu
tak, aby posun plynule po znaku z
prešiel opäť k znaku
a
a ďalej. Postačí nám na to jednoduchá podmienka, ktorá od
novej ASCII hodnoty odpočíta celú abecedu tak, aby sme začínali opäť na
a
:
int ascii = (int)character; ascii += shift; // overflow control if (ascii > (int)'z') { ascii -= 26; } character = (char)ascii; encryptedMessage += character;
Pokiaľ ascii
presiahne ASCII hodnotu 'z'
,
znížime ju o 26
znakov (toľko znakov má anglická abeceda).
Operátor -=
vykoná to isté, ako keby sme napísali
ascii = ascii - 26
. Je to jednoduché a náš program je teraz
funkčný. Všimnime si, že nikde nepoužívame priame kódy znakov, v
podmienke je (int)z
, aj keď by sme tam mohli napísať rovno
122
. Je to z dôvodu, aby bol náš program plne zbavený
explicitných ASCII hodnôt a aby bolo lepšie viditeľné, ako funguje. Cvične
si skúste urobiť dešifrovanie
V nasledujúcom cvičení, Riešené úlohy k 13. lekcii Javy, 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é 15x (5.54 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java