Lekce 9 - Textové řetězce ve Swift podruhé - Práce se znaky
V minulé lekci, Nejčastější chyby Swift nováčků - Umíš pojmenovat proměnné?, jsme si ukázali nejčastější chyby začátečníků ve Swift ohledně pojmenování proměnných.
String
je od Swift 4.0 formálně pole znaků, můžeme s ním
také jako s polem pracovat. To znamená využití for
cyklu či
hromady užitečných metod na poli jako je třeba map()
. Využít
lze také metodu reversed()
, která (jak už prozrazuje
název) vrátí otočený řetězec. Pole je podrobně popsáno v části o
kolekcích, takže se těmto specialitám nebudeme věnovat zde.
Nejprve si vyzkoušejme, že to všechno funguje. Rozcvičíme se na
jednoduchém vypsání znaku na dané pozici. Bohužel nestačí pouze napsat
index znaku jako číslo, ale musíme jej nejprve získat metodou
index()
. Ta vrací speciální typ String.Index
a
přes něj se již na znak dostaneme.
{SWIFT}
let s = "Swift"
print(s)
let index = s.index(s.startIndex, offsetBy: 2)
print(s[index])
{/SWIFT}
Výstup:
Swift i
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 ve Swift read-only, nemůžeme tedy napsat:
// Tento kód nebude fungovat var s = "Swift" let index = s.index(s.startIndex, offsetBy: 2) s[index] = "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 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 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í.
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 var s = "Mount Everest" print(s) s = s.lowercased() // inicializace počítadel var pocetSamohlasek = 0 var pocetSouhlasek = 0 // definice typů znaků let samohlasky = "aeiouyáéěíóúůý" let souhlasky = "bcčdďfghjklmnpqrřsštťvwxzž" // hlavní cyklus for znak in s { }
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
s
, přičemž v každé iteraci cyklu bude v proměnné
znak
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 znak in s { if samohlasky.contains(znak) { pocetSamohlasek += 1 } else if souhlasky.contains(znak) { pocetSouhlasek += 1 } }
Metodu contains()
na řetězci již známe, jako parametr ji lze
předat jak podřetězec, tak přímo znak. Daný znak znak
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:
{SWIFT}
var s = "Mount Everest"
print(s)
s = s.lowercased()
// inicializace počítadel
var pocetSamohlasek = 0
var pocetSouhlasek = 0
// definice typů znaků
let samohlasky = "aeiouyáéěíóúůý"
let souhlasky = "bcčdďfghjklmnpqrřsštťvwxzž"
// hlavní cyklus
for znak in s {
if samohlasky.contains(znak) {
pocetSamohlasek += 1
} else if souhlasky.contains(znak) {
pocetSouhlasek += 1
}
}
print("Samohlásek: \(pocetSamohlasek)")
print("Souhlásek: \(pocetSouhlasek)")
print("Nepísmenných znaků: \(s.count - (pocetSamohlasek + pocetSouhlasek))")
{/SWIFT}
Výsledek:
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 1 bajt, 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. Ve Swift 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. Kód je trochu komplikovanější, za okamžik si jej popíšeme:
{SWIFT}
var c : Character // znak
c = "a"
// převedeme znak na jeho ASCII hodnotu
let optionalASCIIhodnota = c.unicodeScalars.filter{$0.isASCII}.first?.value
// dostali jsme Optional, ale už víme, co dělat
if let ASCIIhodnota = optionalASCIIhodnota {
print("Znak \(c) jsme převedli na ASCII hodnotu \(ASCIIhodnota)")
}
// Převedeme ASCII hodnotu na znak
c = Character(UnicodeScalar(98))
print("ASCII hodnotu 98 jsme převedli na znak \(c)")
{/SWIFT}
Protože zvlášť převod znaku na jeho ASCII hodnotu vypadá celkem děsivě, tak si alespoň stručně popíšeme, co vlastně tento řádek kódu dělá:
let optionalASCIIhodnota = c.unicodeScalars.filter{$0.isASCII}.first?.value
Znaky a textové řetězce jsou ve Swift reprezentovány pomocí tzv. Unicode
Scalars, což je prostě 21bitová reprezentace jednoho znaku. To může být
naše "a"
, číslice, speciální znaky či také emoji. Z
vlastnosti unicodeScalars
získáme tyto hodnoty pro
Character
nebo všechny znaky daného řetězce.
filter
nám umožní vybrat jen ASCII znaky, jelikož Unicode
Scalars obsahují i další speciální znaky. Do filtru předáváme
$0
, což v tomto případě zastupuje jeden Unicode Scalar. Pomocí
vlastnosti isASCII
se jednoduše ptáme, jestli tento znak patří
do ASCII tabulky, která je omezenější než Unicode Scalars reprezentace.
Potom se již jen zeptáme na první nalezený prvek a získáme jeho hodnotu.
Ta je samozřejmě typu Optional
, protože nemusíme nalézt
nic.
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
readLine()
. Š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 let s = "gaiusjuliuscaesar" print("Původní zpráva: \(s)") var zprava : String = "" var posun = 1 // cyklus projíždějící jednotlivé znaky for c in s { } // výpis print("Zašifrovaná zpráva: \(zprava)")
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ě:
{SWIFT}
// inicializace proměnných
let s = "gaiusjuliuscaesar"
print("Původní zpráva: \(s)")
var zprava : String = ""
var posun : UInt32 = 1
// cyklus projíždějící jednotlivé znaky
for c in s {
let optionalASCIIhodnota = c.unicodeScalars.filter{$0.isASCII}.first?.value
if let ASCIIhodnota = optionalASCIIhodnota {
let novyZnak = Character(UnicodeScalar(ASCIIhodnota + posun)!)
zprava += [novyZnak]
}
}
// výpis
print("Zašifrovaná zpráva: \(zprava)")
{SWIFT}
Výsledek:
Původní zpráva: gaiusjuliuscaesar Zašifrovaná zpráva: hbjvtkvmjvtdbftbs
Můžete vidět, že jsme jednou Optional
rozbalili "silou", ale
víme, co děláme Proč jsou
okolo
novyZnak
hranaté závorky, když ho přidáváme do naší
zprávy? Swift nám totiž dovolí pomocí +=
operátoru přidat do
String
buď další String
nebo pole znaků, ne však
samostatný znak. Proto jsme vytvořili dočasné pole o jednom znaku.
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"
.
Nad cyklus vložíme novou proměnnou s ASCII hodnotou znaku
"z"
:
let zASCIIhodnota = "z".unicodeScalars.filter{$0.isASCII}.first!.value
A vnitřek cyklu upravíme do následující podoby:
let optionalASCIIhodnota = c.unicodeScalars.filter{$0.isASCII}.first?.value if let ASCIIhodnota = optionalASCIIhodnota { var posunutyZnak = ASCIIhodnota + posun if posunutyZnak > zASCIIhodnota { posunutyZnak -= 26 } let novyZnak = Character(UnicodeScalar(posunutyZnak)!) zprava += [novyZnak] }
Pokud posunutyZnak
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
posunutyZnak = posunutyZnak - 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 námi dříve získaná hodnota "z"
znaku
(stačí jednou, nemusíme ji získávat v každé iteraci cyklu), 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 9. lekci Swift, 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 26x (72.41 kB)
Aplikace je včetně zdrojových kódů v jazyce Swift