Pouze tento týden sleva až 80 % na e-learning týkající se C# .NET
Nauč se s námi víc. Využij 50% zdarma na e-learningové kurzy.
C# week

Lekce 8 - Textové řetězce v Javě podruhé - práce s jednotlivými znaky

V předešlém cvičení, Řešené úlohy k 7. lekci Javy, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

Pokud jste vycítili 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ů (char) a můžeme s ním i takto pracovat. Pro přístup k jednotlivým znakům slouží metoda charAt(x), kde x udává index znaku v řetězci (počínaje 0). Opačně pro zjištění indexu zadaného znaku slouží metoda indexOf(c), kde c je hledaný znak. Tato metoda vrací index prvního výskytu daného znaku a pokud jej v řetězci nenajde vrátí -1.

Nejprve si vyzkoušejme, že to všechno funguje. Rozcvičíme se na jednoduchém vypsání znaku na dané pozici:

String s = "Java";
System.out.println(s);
System.out.println(s.charAt(2));

Výstup:

Konzolová aplikace
Java
v

A nyní se podíváme na zjištění indexu zadaného znaku:

String s = "Java";
System.out.println(s);
System.out.println(s.indexOf('v'));

Výstup:

Konzolová aplikace
Java
2

Zklamáním může být, že znaky na dané pozici jsou v Javě read-only, nemůžeme je tedy jednoduše změnit.

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 !).

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

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 sc.nextLine(). Ř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í.

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.

pozn.: Kdybyste věděli, jak se správně říká nepísmennému znaku, napište mi to prosím do komentáře pod článek :)

// řetězec, který chceme analyzovat
String s = "Mount Everest";
System.out.println(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
for (char c : s.toCharArray()) {

}

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. Abychom mohli znaky iterovat (procházet cyklem), musíme si String převést na pole znaků. V úvodu jsem říkal, že String vlastně pole znaků je, ale ne plnohodnotné. Obsahuje něco navíc a něco mu chybí, např. možnost prvky iterovat cyklem. V cyklu tedy na s zavoláme metodu toCharArray(), která vrátí plnohodnotné pole znaků z řetězce s. 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
for (char c : s.toCharArray()) {
    if (samohlasky.contains(String.valueOf(c))) {
        pocetSamohlasek++;
    }
    else if (souhlasky.contains(String.valueOf(c))) {
        pocetSouhlasek++;
    }
}

Metodu contains() na řetězci již známe, jako parametr ji lze předat podřetězec. Bohužel nemůžeme předat znak char, musíme tedy znak 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ězce 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. V textu použijeme speciální sekvenci znaků "\n", ta způsobí odřádkování.

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", 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 také 255 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 (UTF8) kódování, kde jsou znaky reprezentovány trochu jiným způsobem. Nicméně v Javě máme stále 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.

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;
System.out.printf("Znak %c jsme převedli na ASCII hodnotu %d\n", c, i);
// Převedeme ASCII hodnotu na znak
i = 98;
c = (char)i;
System.out.printf("ASCII hodnotu %d jsme převedli na znak %c", i, c);

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 na š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 sc.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. 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";
System.out.printf("Původní zpráva: %s\n", s);
String zprava = "";
int posun = 1;

// cyklus projíždějící jednotlivé znaky
for (char c : s.toCharArray()) {

}

// výpis
System.out.printf("Zašifrovaná zpráva: %s\n", 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 = (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 8. 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 893x (6.4 kB)
Aplikace je včetně zdrojových kódů v jazyce Java

 

Předchozí článek
Řešené úlohy k 7. lekci Javy
Všechny články v sekci
Základní konstrukce jazyka Java
Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
45 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 (72)

Avatar
Petra Krulová:10. září 12:29

Ještě mě napadlo, jaký máš nastavený font? Nemohl by tam někde být zakopaný pes? Jsem úplný začátečník, takže je tohle pro mě trochu španělská vesnice. A děkuju za pomoc :-)

 
Odpovědět
10. září 12:29
Avatar
Alesh
Překladatel
Avatar
Alesh:10. září 13:07

A co ti to dělá, pokud si dáš vypsat jen délku textu? Protože ty tam pak počítáš tohle:

int nepismeno = text.length() - (pocetSamohl) - (pocetSouhl);

Tak by mě zajímal jen samotný výpis té délky řetězce.
Jinak ten program lze i prokrokovat, nastavit si watches, kde se ti v každé fázi programu zobrazí, co je v které proměnné.
Že by to bylo fontem mi nepřijde reálný.

 
Odpovědět
10. září 13:07
Avatar
Odpovídá na Alesh
Petra Krulová:10. září 15:24

Výpis délky slova to v případě slova začínajícího na "č" hází 0. Vlastně cokoli, kde se snažím pracovat se slovem s č, to po č už nic nezpracuje. Velké Č už to ale vyhodnotí, i když nesprávně.

 
Odpovědět
10. září 15:24
Avatar
Odpovídá na Petra Krulová
Matúš Olejník:10. září 17:09

Dávalo by zmysel, že ak začneš slovo s diakritikou a vypíše to pri všetkom 0, že sa dané slovo ani nenačítalo. Buď to musíš debugovať alebo skús vypísať dané slovo hneď po načítaní nech vidíme či sa skutočne nenačítalo.

Ak by to bol nejaký problém netbeansu tak môžeš skúsiť iné IDE, napr. IntelliJ Idea podľa mňa najlepšie IDE pre Javu.

Alebo skús do txt súboru napísať do riadkov rôzne problémové slová (veľké, malé atď...) a vypísať ich pomocou kódu nižšie. Ak aj tu napr. pri slove český vypíše dĺžku 0 tak budeme skúmať ďalej, ale myslim že takto by malo vypísať správne. Prípadne napíš aj operačný systém a verziu Javy.

import java.io.BufferedReader;
import java.io.FileReader;

class Scratch {
    public static void main(String[] args) throws Exception {
        try (BufferedReader reader = new BufferedReader(new FileReader("cesta/k/suboru.txt"))){
            String line = reader.readLine();
            while (line != null) {
                System.out.println(line.length());
                System.out.println(line);

                line = reader.readLine();
            }
        }
    }
}
Editováno 10. září 17:09
Odpovědět
10. září 17:09
/* I am not sure why this works but it fixes the problem */
Avatar
Atrament
Super redaktor
Avatar
Atrament:10. září 19:15

Když to spouštíš ten program, tak v okně Output se ti zobrazuje na prvním řádku přesný příkaz který to provádí, je to ten co u tebe začíná 'cd c:\Users\Peťa...' Mohla bys ho prosím celý komplet zkopírovat a postnout sem?

Alesh a Matúš Olejník
Mám podezření že problém bude v mavenu v exec:exec, který je bohužel defaultní v Netbeans. Když totiž zkouším u sebe stejný kód s exec:java tak funguje bez problémů, kdežto s exec:exec to prostě nefunguje...

 
Odpovědět
10. září 19:15
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Robert Michalovič:11. září 5:36

2Petra Krulová : Štouráš se zbytečně do hloubky, zde na foru tak podrobně tato problematika není vysvětlena. Nejkvalitněji je problematika češtiny v českých zdrojím je objasněna v této knize ale je to histrorie z éry WinXP,Vista a i ta není úplně dokonalá.

Nejdříve vůbec doporučuji zkontrolovat jestli se ti to vůbec správně načetlo + navíc načtení v IDE vs přímo OS přes cmd není stejné.

String text = sc.nextLine().toLowerCase();
System.out.println("Test Vypisu Stringu : "+text);

Problémem v tomto případě je multiplatformnost Javy. Podpora různých OS, přerušení OS, low level věci v OS a znakových sad neumožnují vývojářům Javy vytvořit universální řešení. Navíc v pořátcích Javy se nepředpokládalo využívání konzole, tak se o to ani nepokoušely. Prostě v té době chyběl někdo kdo by těmto low level věcem na úrovni všech OS, které Java podporuje rozuměl a dal tomu "chaosu" jednoduchý a smysluplný řád. Na úrovni GUI už tento problém není, problémem je konzola.

 
Odpovědět
11. září 5:36
Avatar
Odpovídá na Atrament
Petra Krulová:15. září 13:00

Atrament , celý příkaz tady:
cd C:\Users\Peťa\Do­cuments\NetBe­ansProjects\tex­tretez; "JAVA_HOME=C:\\Pro­gram Files\\Eclipse Foundation\\jdk-16.0.2.7-hotspot" cmd /c "\"C:\\Program Files\\NetBeans-12.4\\netbean­s\\java\\maven\\bin\\mv­n.cmd\" -Dexec.vmArgs= -Dexec.args=\"${e­xec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}\" -Dexec.appArgs= -Dexec.mainClas­s=cz.itnetwor­k.textretez.tex­tretez -Dexec.executa­ble=\"C:\\Pro­gram Files\\Eclipse Foundation\\jdk-16.0.2.7-hotspot\\bin\\j­ava.exe\" -Dmaven.ext.clas­s.path=\"C:\\Pro­gram Files\\NetBeans-12.4\\netbean­s\\java\\maven-nblib\\netbeans-eventspy.jar\" -Dfile.encoding=UTF-8 -q org.codehaus.mo­jo:exec-maven-plugin:3.0.0:exec"
Running NetBeans Compile On Save execution. Phase execution is skipped and output directories of dependency projects (with Compile on Save turned on) will be used instead of their jar artifacts.

 
Odpovědět
15. září 13:00
Avatar
Odpovídá na Robert Michalovič
Petra Krulová:15. září 13:10

Robert Michalovič,
bohužel ani načtení nefunguje.

Jsem opravdu úplný začátečník a snažím se jen najít nějaké řešení.
Pokud to není v nastavení a byl by problém ve verzi programu, vezmu to na vědomí a budu v budoucnu používat jiný.

 
Odpovědět
15. září 13:10
Avatar
Robert Michalovič:Včera 7:40

Můj příspěvek ti měl vysvětlit, že to není plně tvůj problém. Jedná se hlubší problém multiplatformní Javy. Někdy je výhodné některý problém i přeskočit a vrátit se k němu mnohem později až bude mít větší znalosti.

Pro tebe bude vhodné najít správnou znakovou sadu která v tém IDE a tvém OS funguje a té se držet. Vyzkoušej tyto možnosti jednu po druhé dokud ti to nebude fungovat. Jedna ze zde zveřejněných znakových sady by ti měla pomoci, za předpokladu že máš CZ nebo ENG Windows.

Scanner sc = new Scanner (System.in, "UTF-8");
Scanner sc = new Scanner (System.in, "UTF-16");
Scanner sc = new Scanner (System.in, "cp1250");
Scanner sc = new Scanner (System.in, "cp852");
Scanner sc = new Scanner (System.in, "UTF-32");
Scanner sc = new Scanner (System.in, "ISO8859_2");
Scanner sc = new Scanner (System.in, "ISO8859_1");
Scanner sc = new Scanner (System.in, "IBM437");

Pokud to nepomůže napiš co máš za OS (např. Windows10 Pro 64bit ENG, verzi IDE + jakou jazykovou verzi, jakou verzi Javy, )

Případně můžeš zkusit vypsat jakou znakovou sadu máš defaultně nastaveno.

InputStreamReader vstup = new InputStreamReader(System.in);
System.out.println("Znakova sada nastavena vstup : "+vstup.getEncoding());
OutputStreamWriter vypis = new OutputStreamWriter(System.out);
System.out.println("Znakova sada nastavena vystup: "+vypis.getEncoding());
 
Odpovědět
Včera 7:40
Avatar
Atrament
Super redaktor
Avatar
Odpovídá na Petra Krulová
Atrament:Včera 9:44

Není to nastavením, je to tím pluginem, který IDE používá ke spouštění tvého kódu, a ve kterém se zdá být momentálně nějaká chyba, že nepracuje správně se vstupem s těmi našimi krásnými znaky jako jsou č š ř a podobně.

Ověřit si to člověk může jednoduše tím, že si v Netbeans otevřeš Terminál (menu Window - IDE Tools - Terminal), přepneš se do adresáře ve kterém máš ten java soubor s programem, a prostě si ho pustíš ručně

java Soubor.java

Jednoduché prozatimní řešení je založit nový projekt, ale tentokrát nevybrat Java with Maven, ale Java with Ant. Ant funguje s českým vstupem správně.

 
Odpovědět
Včera 9:44
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 82. Zobrazit vše