8. diel - Textové reťazce v Swift druhýkrát - Práca so znakmi
V predchádzajúcom cvičení, Riešené úlohy k 7. lekcii Swift, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
String
je od Swift 4.0 formálne pole znakov, môžeme s ním aj
ako s poľom pracovať. To znamená využitie for
cyklu či hromady
užitočných metód na poli ako treba map()
. Využiť možno tiež
metódu reversed()
, ktorá (ako už prezrádza názov)
vráti otočený reťazec. Pole je podrobne popísané v časti o kolekciách,
takže sa týmto špecialitám nebudeme venovať tu.
Najprv si vyskúšajme, že to všetko funguje. Rozcvičíme sa na
jednoduchom vypísanie znaku na danej pozícii. Bohužiaľ nestačí len
napísať index znaku ako číslo, ale musíme ho najprv získať metódou
index()
. Tá vracia špeciálny typ String.Index
a cez
neho sa už 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 ku znakom v reťazci pristupovať cez hranatú zátvorku, ako tomu je aj u pole. Sklamaním môže byť, že znaky na danej pozícii sú vo Swift read-only, nemôžeme teda napísať:
// Tento kód nebude fungovať var s = "Swift" let index = s.index(s.startIndex, offsetBy: 2) s[index] = "o" print(s)
Samozrejme to ide urobiť inak, neskôr si to ukážeme, zatiaľ sa budeme venovať iba čítanie 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 najprv v programe zadáme napevno, aby sme ho
nemuseli pri každom spustení písať. Keď bude program hotový, nahradíme ho
readLine()
. Reťazec budeme prechádzať cyklom po jednom znaku.
Rovno tu hovorím, že neapeluje 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. Pretože sa jedná o zložitejší kód, nebudeme zabúdať na komentáre.
// reťazec, ktorý chceme analyzovať var s = "Mount Everest" print(s) s = s.lowercased() // inicializácia počítadiel var pocetSamohlasek = 0 var pocetSouhlasek = 0 // definícia typov znakov let samohlasky = "aeiouyáéěíóúůý" let souhlasky = "bcčdďfghjklmnpqrřsštťvwxzž" // hlavný cyklus for znak in s { }
Spočiatku si pripravíme reťazec a prevedieme ho na malé písmená.
Počítadla vynulujeme. Na definícia znakov nám postačí obyčajný typ
String
. Hlavný cyklus nám prejde jednotlivé znaky v reťazci
s
, pričom v každej iterácii cyklu bude v premennej
znak
aktuálny znak.
Poďme plniť počítadla, pre jednoduchosť už nebudem opisovať zvyšok kódu a presunu sa len k cyklu:
// hlavný cyklus for znak in s { if samohlasky.contains(znak) { pocetSamohlasek += 1 } else if souhlasky.contains(znak) { pocetSouhlasek += 1 } }
Metódu contains()
na reťazci už poznáme, ako parameter ju
možno odovzdať ako podreťazec, tak priamo znak. Daný znak znak
našej vety teda najprv skúsime vyhľadať v reťazci samohlasky
a
prípadne zvýšiť ich počítadlo. Ak v samohláskach nie je, pozrieme sa do
spoluhlások a prípadne opätovne zvýšime ich počítadlo.
Teraz nám chýba už len výpis na koniec:
{SWIFT}
var s = "Mount Everest"
print(s)
s = s.lowercased()
// inicializácia počítadiel
var pocetSamohlasek = 0
var pocetSouhlasek = 0
// definícia typov znakov
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ások: \(pocetSamohlasek)")
print("Spoluhlások: \(pocetSouhlasek)")
print("Nepísmenných znakov: \(s.count - (pocetSamohlasek + pocetSouhlasek))")
{/SWIFT}
výsledok:
Mount Everest Samohlások: 5 Spoluhlások: 7 Nepísmenných znakov: 1
A je to!
Ascii hodnota
Možno ste už niekedy počuli o ASCII tabuľke. 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 1 bajt, teda s rozsahom
hodnôt od 0
do 255
. V systéme bola uložená tzv.
ASCII tabuľka, ktorá mala 256 znakov a každému ASCII kódu (číselnému
kódu) priradzovala jeden znak.
Asi je vám jasné, prečo tento spôsob nebol platný dodnes. Do tabuľky sa
jednoducho nevošli 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. Vo Swift máme 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 "a"
, na
98
"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ť. Kód je trochu komplikovanejšia, za okamih si ho popíšeme:
{SWIFT}
var c : Character // znak
c = "a"
// prevedieme znak na jeho ASCII hodnotu
let optionalASCIIhodnota = c.unicodeScalars.filter{$0.isASCII}.first?.value
// dostali sme Optional, ale už vieme, čo robiť
if let ASCIIhodnota = optionalASCIIhodnota {
print("Znak \(c) sme previedli na ASCII hodnotu \(ASCIIhodnota)")
}
// Prevedieme ASCII hodnotu na znak
c = Character(UnicodeScalar(98))
print("ASCII hodnotu 98 sme previedli na znak \(c)")
{/SWIFT}
Pretože zvlášť prevod znaku na jeho ASCII hodnotu vyzerá celkom desivo, tak si aspoň stručne popíšeme, čo vlastne tento riadok kódu robí:
let optionalASCIIhodnota = c.unicodeScalars.filter{$0.isASCII}.first?.value
Znaky a textové reťazce sú vo Swift reprezentované pomocou tzv. Unicode
Scalars, čo je jednoducho 21bitová reprezentácie jedného znaku. To môže
byť naše "a"
, číslice, špeciálne znaky či tiež emoji. Z
vlastnosti unicodeScalars
získame tieto hodnoty pre
Character
alebo všetky znaky daného reťazca.
filter
nám umožní vybrať len ASCII znakmi, pretože Unicode
Scalars obsahujú aj ďalšie špeciálne znaky. Do filtra odovzdávame
$0
, čo v tomto prípade zastupuje jeden Unicode Scalar. Pomocou
vlastnosti isASCII
sa jednoducho pýtame, či tento znak patrí do
ASCII tabuľky, ktorá je obmedzenejšia než Unicode Scalars reprezentácie.
Potom sa už len opýtame na prvý nájdený prvok a získame jeho hodnotu. Tá
je samozrejme typu Optional
, pretože nemusíme nájsť nič.
Cézarova šifra
Vytvoríme si jednoduchý program pre šifrovanie textu. Ak ste niekedy
počuli o Cézarově š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 ahoj
sa s posunom textu
o 1
preloží ako bipk
. Posun umožníme
užívateľovi vybrať. Algoritmus tu máme samozrejme opäť vysvetlený a to v
článku Cézarova
šifra. Program si dokonca môžete vyskúšať v praxi - Online cézarova
š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úce jednotlivé znaky a výpis zašifrované 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
readLine()
. Šifra nepočíta s diakritikou, medzier a
interpunkčných znamienok. Diakritiku budeme bojkotovať a budeme
predpokladať, že ju užívateľ nebude zadávať. Ideálne by sme potom mali
diakritiku pred šifrovaním odstrániť, rovnako tak hocičo okrem písmen.
// inicializácia premenných let s = "gaiusjuliuscaesar" print("Pôvodná správa: \(s)") var zprava : String = "" var posun = 1 // cyklus prechádzajúce jednotlivé znaky for c in s { } // výpis print("Zašifrované správa: \(zprava)")
Teraz sa presunieme dovnútra cyklu, prevedieme znak v c
na
ASCII hodnotu (čiže ordinálna hodnotu), túto hodnotu zvýšime o
posun
a prevedieme späť na znak. Tento znak nakoniec pripojíme k
výslednej správe:
{SWIFT}
// inicializácia premenných
let s = "gaiusjuliuscaesar"
print("Pôvodná správa: \(s)")
var zprava : String = ""
var posun : UInt32 = 1
// cyklus prechádzajúce 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é správa: \(zprava)")
{SWIFT}
výsledok:
Pôvodná správa: gaiusjuliuscaesar Zašifrované správa: hbjvtkvmjvtdbftbs
Môžete vidieť, že sme raz Optional
rozbalili "silou", ale
vieme, čo robíme:-) Prečo sú okolo novyZnak
hranaté zátvorky,
keď ho pridávame do našej správy? Swift nám totiž dovolí pomocou
+=
operátora pridať do String
buď ďalší
String
alebo pole znakov, nie však samostatný znak. Preto sme
vytvorili dočasné pole o jednom znaku.
Program si vyskúšame. Výsledok vyzerá celkom dobre. Skúsme si však
zadať vyššiu posun alebo napísať slovo zebra
. Vidíme, že
znaky môžu po "z"
pretiecť do ASCII hodnôt ďalších znakov, v
texte teda už nemáme len písmená, ale ďalšie škaredé znaky. Uzavrieme
znaky do kruhu tak, aby posun plynulo po "z"
prešiel opäť k
"a"
a ďalej. Postačí nám k tomu jednoduchá podmienka, ktorá
od novej ASCII hodnoty odpočíta celú abecedu tak, aby sme začínali opäť
na "a"
.
Nad cyklus vložíme novú premennú s ASCII hodnotou znaku
"z"
:
let zASCIIhodnota = "z".unicodeScalars.filter{$0.isASCII}.first!.value
A vnútro cyklu upravíme do nasledujúcej 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] }
Ak posunutyZnak
presiahne ASCII hodnotu "z"
,
znížime ho o 26
znakov (toľko znakov má anglická abeceda).
Operátor -=
vykoná to isté, ako keby sme napísali
posunutyZnak = posunutyZnak - 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 nami skôr získaná hodnota "z"
znaku (stačí raz,
nemusíme ju získavať v každej iterácii cyklu), aj keď by sme tam mohli
napísať rovno 122
. Je to z dôvodu, aby bol náš program plne
odtienený od explicitných ASCII hodnôt a bolo lepšie viditeľné, ako
funguje. Cvične si skúste urobiť dešifrovanie.
V nasledujúcom cvičení, Riešené úlohy k 8. lekcii Swift, 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é 26x (72.41 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Swift