IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

2. diel - Filtrovanie a mapovanie polí vo Swift

V minulej lekcii, Úvod do kolekcií a genericita vo Swift , sme si vysvetlili genericitu vo Swift. Dostávame sa k ďalšie dôležité časti programovania. Ako už dúfam viete z lekcie o poli vo Swiftu, často sa hodí uložiť niekoľko prvkov rovnakého typu a potom s nimi ďalej pracovať. Práve na tento účel kolekcie existujú. Swift má ponuku kolekcií veľmi priamočiarou a ponúka tri primárne:

  • Array - pole, ktoré už poznáme
  • Dictionary - slovník
  • Set - množina

Každá z kolekcií sa hodí k niečomu inému, najčastejšie budete používať Array.

Array

Swift implementuje Array ako moderné polia a hojne sa v tomto jazyku využíva. Len pre zopakovanie nemusíme presne určovať jeho veľkosť, nijako sa o ňu starať, a máme k dispozícii metódy na pohodlné pridávanie položiek, ich odoberanie a tiež niekoľko ďalších. Tie základné už poznáme, takže je tu preskočíme. O pokročilých metódach polí bude práve dnešná lekcie.

Založme si novú konzolovú aplikáciu s názvom napr. Kolekce a v main.swift si pripravme jednoduché pole čísel.

var cisla = [2, 5, 9, 12, 34, 17, 28, 18]

Filter ()

Veľmi často potrebujeme pole "prefiltrovať" a získať iba prvky, ktoré vyhovujú nejaké podmienke. Túto podmienku zapíšeme do špeciálneho bloku nazvaného v angličtine closure. Teraz nám stačí vedieť, že sa jedná o blok kódu podobný funkciu, ktorý môžeme odovzdať nejaké metóde ako parameter. V podstate odovzdáme funkciu funkciu, možno to znie zmätene, ale keď sa nad tým zamyslíte, dáva to zmysel. Napr. zavoláme metódu filter() a tej povieme, aby nám polia profiltrovala len na hodnoty, ktoré zodpovedajú našej closure (funkciu). Práve metóde filter() teraz naozaj odovzdáme closure špecifikujúca ako prefiltrovať naše pole čísel. Ak začneme písať cisla.filter a zvyšok kódu si necháme "dopísať" od Xcode, tak dostaneme niečo takéto:

cisla.filter(isIncluded: (Int) throws -> Bool)

Closure sa zapisuje takto pomocou šípky, už by sme teda vedeli definovať metódu, ktorá ju berie ako argument. To my ale nechceme a preto znovu potvrdíme klávesou Enter. Xcode nám vygeneruje prázdnu closure. Výsledok vyzerá takto:

cisla.filter { (Int) -> Bool in
code
}

Výraz (Int) -> Bool označuje, že z čísla (typ Int) potrebujeme zistiť či má v poli zostať alebo nie (typ Bool). Int v zátvorke a tiež "code" v bloku máme zvýraznené. Do zátvorky, kde je teraz Int, zapíšeme názov premennej pre jednotlivé prvky v poli, aby sme s nimi mohli ďalej pracovať. Keďže v našom poli máme čísla, premenujeme premennú na cislo:

cisla.filter { (cislo) -> Bool in
}

Metóda filter() pre každé číslo v našom poli vykoná náš zatiaľ prázdny closure a podľa vrátenej hodnoty Bool určí, či bude číslo v novom filtrovaného poli. Dajme tomu, že chceme ponechať iba čísla väčšie ako 10, takže do bloku napíšeme:

return cislo > 10

Výsledný kód teda vyzerá nasledovne:

cisla.filter { (cislo) -> Bool in
    return cislo > 10
}

Keď výsledok vypíšeme, tak vidíme, že všetky čísla naozaj spĺňajú túto podmienku.

print(cisla)

Skrátenie zápisu

Možno si hovoríte, že pre filtrovanie potrebujeme nejako zbytočne veľa kódu, keď vlastne iba porovnávame číslo s hodnotou 10. Swift našťastie ponúka o poznanie skrátený zápis, ale musíme ho napísať celý sami.

cisla.filter { $0 > 10 }

To je oveľa lepšie, že? $0 zastupuje premennú, ktorá sa v našom prípade vymenovala cislo a return nie je potreba. Je totiž jasné, že sa tento výraz vracia a použije sa pre vyhodnotenie návratové hodnoty. Všimnite si, že z kódu zmizli okrúhle zátvorky. Tejto syntax sa hovorí tzv. Trailing closure. Ak by mala metóda aj nejaké štandardné parametre, uviedli by sme ich do okrúhle zátvorky, tú ukončili, a následne odovzdali closure. Mohlo by to vyzerať napr. Nasledovne:

instance.metoda("nějakýParametr") { $0 > 10 }

Keď teraz vieme poľa filtrovať, ukážeme si aj podobnú a nemenej užitočnú metódu, map().

Máp ()

Metóda slúži na tzv. Transformáciu dát na iný dátový typ alebo do inej štruktúry. V closure nám potom stačí definovať, ako chceme existujúce dáta na nové premeniť. Napríklad by sme chceli všetky čísla vynásobiť dvomi. Najskôr si opäť ukážeme, ako vyzerá vygenerovaný kód od Xcode. Z metódy filter() je vám určite povedomý.

cisla.map { (Int) -> T in
}

T tu nahrádza konkrétny dátový typ a jedná sa o genericitu, ktorú sme si vysvetlili minule. Teraz nám stačí T nahradiť za dátový typ, ktorý chceme vrátiť ako výsledok. Keďže iba násobíme, tak výstupné typ zostane Int. Kompletné volanie metódy map() pre vynásobení prvkov poľa dvoma by vyzeralo nasledovne:

cisla.map { (cislo) -> Int in
    return cislo * 2
}

Samozrejme ho môžeme opäť skrátiť na trailing colusure:

cisla.map { $0 * 2 }

Možno vás napadá, že toto všetko by ste mohli urobiť v nejakom cykle aj bez znalosti CLOSURES. Bolo by to ale pracnejšie, bolo by potrebné založiť nové polia a jednotlivé prvky doň vkladať. Navyše vďaka metódam filter() a map() je pri čítaní kódu hneď jasné, o čo sa snažíme.

Násobenie čísel je samozrejme veľmi základné prípad. map() by sme skôr využili napr. keby sme mali polia objektov Student a chceli získať pole ich emailových adries.

CompactMap ()

Táto metóda je veľmi podobná už vysvetlené map(), ale s dôležitým rozdielom. Nevráti nám nil hodnoty, ak by nejaké mali našej transformáciou vzniknúť. Napríklad môžeme mať pole typu String, z ktorého chceme získať čísla, ale nie všetky hodnoty je možné na čísla previesť.

Vytvoríme si pole reťazcov, z ktorých niektoré pôjde úspešne spracuje na Int, a použijeme compactMap():

let moznaCisla = ["21", "pět", "1", "osm", "98"]
let urciteCisla = moznaCisla.compactMap { Int($0) }

Ako výsledok získame pole: [21, 1, 98]

Keby sme použili obyčajnú map() metódu, tak dostaneme menej praktický výsledok: [Optional(21), nil, Optional(1), nil, Optional(98)].

Reduce ()

Na záver si ukážeme ešte jednu silnú a užitočnú metódu. Jej zmyslom je redukovať celé pole na jednu hodnotu. Takže môžeme napríklad všetky čísla sčítať, podobne ako by sme mali k dispozícii metódu sum() z iných jazykov.

Popíšeme si, ako reduce() vlastne funguje a ukážeme implementáciu súčtu všetkých prvkov. Pracovať budeme s nasledujúcim poľom:

let cisla = [21, 30, 57, 1, 23, 10]

V základe nám Xcode doplní takto obsiahlu metódu, vrátane placeholder textu. Ten potom nahradíme našimi názvy premenných:

cisla.reduce(initialResult: Result, nextPartialResult: (Result, Int) throws -> Result)

Metóda reduce() v každom kroku pracuje s predchádzajúcim výsledkom, do ktorého sa postupne premietajú všetky prvky poľa, v našom prípade v ňom bude na konci obsiahnutý ich súčet. Prvým parametrom určujeme počiatočnú hodnotu initialResult, čo bude v prípade sčítanie 0. Ako druhý parameter zadávame closure, ktorý určuje ako sa majú jednotlivé prvky vo výslednej hodnote prejaviť, my ich k nej pripočítame.

Prepíšeme si teda placeholder text na konkrétne dátové typy a využijeme trailing closure, ktorú poznáme z predchádzajúcich metód:

cisla.reduce(0) { (result, cislo) -> Int in
    return result + cislo
}

To už začína dávať väčší zmysel, že? Pretože chceme sčítať, tak začneme od 0 a v closure vždy vezmeme predchádzajúci výsledok a pripočítame k nemu aktuálne číslo. Týmto sčítame všetky čísla v poli.

Celý zápis môžeme dramaticky skrátiť, podobne ako u predchádzajúcich metód:

cisla.reduce(0, +)

Nechali sme 0 a celý zápis sčítanie nahradili + operátorom, pretože operátormi sú vlastne špeciálne typy metód. Ako som písal v úvode, reduce() je pomerne komplexný a ponúka hromadu možností. Cieľom v tomto tutoriálu je primárne to, aby ste o jej existencii vedeli a poznali základné použitie.

V budúcej lekcii, Slovníky (Dictionary) vo Swift , sa budeme venovať slovníkom, ktoré Swift implementuje ako triedu Dictionary.


 

Predchádzajúci článok
Úvod do kolekcií a genericita vo Swift
Všetky články v sekcii
Kolekcia vo Swift
Preskočiť článok
(neodporúčame)
Slovníky (Dictionary) vo Swift
Článok pre vás napísal Filip Němeček
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity