Lekce 12 - Kolekce Set, Queue a Map v Dartu

Dart Objektově-orientované programování Kolekce Set, Queue a Map v Dartu

Unicorn College ONEbit hosting Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Datum a čas v Dartu, jsme si řekli něco o datu a času v Dartu. V dnešním tutoriálu si ukážeme další kolekce, které máme dostupné a s jejichž položkami můžeme pracovat jinak než-li v nám již známém seznamu.

Pojem kolekce jsme tu již zmínili. Je to struktura, do které můžeme ukládat více objektů. Kolekcí je v Dartu několik, jsou uzpůsobeny pro různé účely a můžeme s nimi zacházet různými způsoby. Dosud známe pouze kolekci seznam (List). V průběhu kurzu však budeme potřebovat i další kolekce, které si dnes vysvětlíme.

Set

Set je kolekce reprezentující množinu, která si je velmi podobná s List, avšak zásadním rozdílem je, že každý objekt se v ní může vyskytovat pouze jednou, tj. neuchovává duplicity. Množinu lze snadno převést na seznam a seznam na množinu pomocí metody toSet() či toList(). Snadno tak lze z listu odstranit duplicity pomocí zřetězení těchto metod, např. mujList.toSet().toList().

Vyzkoušejme si ji:

Set<int> set = new Set();
set.add(1);
set.add(1);
set.add(2);
set.add(1);
print(set);

Výstup programu:

Konzolová aplikace
{1, 2}

Pro každý objekt zadaného typu lze určit, zda-li je nebo není v množině. Pokud začneme množinu procházet, budou prvky v takovém pořadí, v jakém byly přidány. Prvky se rozlišují podle operátoru == a je doporučeno, pokud je tento operátor přepsán, taktéž přepsat i metodu hashCode() pro zachování konzistence.

Množina má taktéž oproti seznamu několik specifických funkcí:

union()

Metoda množinového sjednocení, která vrací novou množinu s prvky z první i z druhé množiny. Druhou množinu předáme parametrem. Nejprve jsou přidány prvky z první množiny v původním pořadí a následně jsou přidány zbývající prvky, které jsou ve druhé množině navíc, v pořadí z druhé množiny.

Set<int> a = new Set();
a.addAll([1, 2, 1, 1, 2, 4]);
Set<int> b = new Set();
b.addAll([1, 2, 2, 2, 2, 3, 3, 5]);

print(a);
print(b);
print(a.union(b));
print(b.union(a));

Výstup programu:

Konzolová aplikace
{1, 2, 4}
{1, 2, 3, 5}
{1, 2, 4, 3, 5}
{1, 2, 3, 5, 4}

difference()

Další zajímavou metodou je množinový rozdíl. Metoda vrátí novou množinu s prvky z první množiny, které se nevyskytovaly v druhé množině. Druhou množinu opět předáme parametrem.

Set<int> a = new Set();
a.addAll([1, 2, 1, 1, 2, 4]);
Set<int> b = new Set();
b.addAll([1, 2, 2, 2, 2, 3, 3, 5]);

print(a);
print(b);
print(a.difference(b));
print(b.difference(a));

Výstup programu:

Konzolová aplikace
{1, 2, 4}
{1, 2, 3, 5}
{4}
{3, 5}

intersection()

Poslední z množinových metod je množinový průnik. Metoda vrátí novou množinu obsahující pouze prvky, které byly společné v obou množinách.

Set<int> a = new Set();
a.addAll([1, 2, 1, 1, 2, 4]);
Set<int> b = new Set();
b.addAll([1, 2, 2, 2, 2, 3, 3, 5]);

print(a);
print(b);
print(a.intersection(b));
print(b.intersection(a));

Výstup programu:

Konzolová aplikace
{1, 2, 4}
{1, 2, 3, 5}
{1, 2}
{1, 2}

Vlastní objekt

Jak již bylo řečeno, pokud chceme pracovat s množinou, objekt musí mít implementovaný operátor == a metodu hashCode(). Pro ukázku si vytvoříme objekt Auto, které bude mít rychlost a název. Následně vytvoříme 3 auta, přičemž 2 z nich budou mít stejné hodnoty:

class Auto {
        int rychlost;
        String nazev;

        @override
        String toString() {
                return '$nazev($rychlost)';
        }
}

void main() {
        Auto mazel = new Auto()
                ..nazev = 'Mazlík'
                ..rychlost = 150;
        Auto auto = new Auto()
                ..nazev = 'Mazlík'
                ..rychlost = 150;
        Auto popelnice = new Auto()
                ..nazev = 'Šunka'
                ..rychlost = 60;

        Set<Auto> auta = new Set();
        auta.add(mazel);
        auta.add(auto);
        auta.add(popelnice);
        print(auta);
}

Výstup programu:

Konzolová aplikace
{Mazlík(150), Mazlík(150), Šunka(60)}

Očekávané chování je samozřejmě takové, že když používáme množinu, měly by v ní být ve výsledku jen 2 auta, protože uchovává pouze unikátní objekty. Avšak hle – auto, které identifikujeme podle názvu a rychlosti, se nám objevilo dvakrát. Zapomněli jsme implementovat operátor == a hashCode(). Nemusíme vymýšlet relativně složitý kód sami, IDE nám jej vygeneruje za nás. Stačí stisknout klávesy ALT + INSERT (Generate...) a zvolit ==() and hashCode.

My budeme chtít identifikovat naše auto podle názvu a rychlosti, proto zvolíme obě vlastnosti.

@override
bool operator ==(Object other) =>
        identical(this, other) ||
        other is Auto && runtimeType == other.runtimeType && rychlost == other.rychlost && nazev == other.nazev;

@override
int get hashCode => rychlost.hashCode ^ nazev.hashCode;

Všimněte si novinky, klíčového slova operator. Přesně tak lze přepsat téměř libovolný operátor pro daný objekt. K přetěžování operátorů se ještě vrátíme a vysvětlíme si vše více do hloubky. Znovu si spustíme náš program, tentokrát již vše funguje přesně tak, jak bychom očekávali:

Konzolová aplikace
{Mazlík(150), Šunka(60)}

Queue

Queue je kolekce, která opět velmi připomíná seznam, avšak reprezentuje frontu (FIFO) a zásobník (LIFO). To znamená, že je tato kolekce optimalizovaná na přístup na první či poslední prvek. Pro používání je nutné importovat knihovnu dart:collection. Stejně jako seznam či množina, i frontu lze procházet a bude fungovat i přístup na položku na daném indexu, avšak kvůli implementaci fronty nebude přístup tak rychlý jako v seznamech.

Vyzkoušíme si jednoduchou ukázku:

Queue<int> queue = new Queue();
queue.add(1);
queue.add(2);
queue.add(2);
queue.add(3);
print(queue);

Doposud je chování stejné se seznamem. Ukážeme si však další metody pro práci s frontou.

addLast()

Metoda pro vložení prvku na konec fronty. Lze nahradit metodou add().

addFirst()

Metoda pro vložení prvku na začátek fronty.

Queue<int> queue = new Queue();
queue.add(1);
queue.add(2);
queue.add(2);
queue.addFirst(3);
print(queue);

Výstup programu:

Konzolová aplikace
{3, 1, 2, 2}

removeFirst() a removeLast()

Metody odstraní první/poslední prvek kolekce a tento prvek vrátí.

Queue<int> queue = new Queue();
queue.add(1);
queue.add(2);
queue.add(2);
queue.addFirst(3);
print(queue);
print(queue.removeFirst());
print(queue);
print(queue.removeLast());
print(queue);

Výstup programu:

Konzolová aplikace
{3, 1, 2, 2}
3
{1, 2, 2}
2
{1, 2}

Map

Mapa, asociativní pole, slovník, ... Všechny tyto názvy můžete znát z různých programovacích jazyků. V Dartu budeme používat pojem mapa. Mapa je kolekce, která uchovává své prvky pod zadanými klíči. V takové kolekci můžeme k prvkům přistupovat po zadání klíče. Stejně jako u předchozích kolekcí, prvky můžeme procházet v pořadí podle přidání. Datový typ klíče zapisujeme do lomených závorek na první místo a na druhé píšeme datový typ prvku. Ukažme si ukázku:

Map<int, String> mapa = new Map();
mapa[0] = 'nula';
mapa[1] = 'uno';
mapa[2] = 'два';
print(mapa);

Výstup programu:

Konzolová aplikace
{0: nula, 1: uno, 2: два}

Jak jste si jistě všimli již z ukázky, mapa nemá metodu add(). Pokud chceme přidat prvek do mapy, zapíšeme klíč do hranatých závorek a pomocí operátoru rovná se vložíme prvek. Mapa, podobně jako seznam, lze zapsat pomocí literálu, pro který se používají složené závorky, ve kterých zapisujeme dvojice klíč: prvek oddělené čárkou. V ukázce si namapujme postavy ze seriálu Simpsonovi na jejich oblíbenou věc, abychom si ukázali, že mapa nemusí mít na rozdíl od seznamu číselné klíče, ale že můžeme indexovat i řetězci:

Map<int, String> mapa = {
        'Homer': 'kobliha',
        'Marge': 'trouba',
        'Bart': 'prak',
        'Liza': 'kniha',
        'Meggie': 'dudlík',
};

keys a values

Klíče a prvky mapy se dají získat z kolekcí keys a values.

putIfAbsent()

Metoda vhodná pro přidání prvku pouze v případě, že již takový klíč neexistuje:

Map<int, String> mapa = {
        0: 'nula',
        1: 'uno',
        2: 'два',
};

mapa.putIfAbsent(2, () => 'dva'); // nepřepíše
print(mapa);
mapa[2] = 'zwei'; // přepíše
print(mapa);

Výstup programu:

Konzolová aplikace
{0: nula, 1: uno, 2: два}
{0: nula, 1: uno, 2: zwei}

containsKey() a containsValue()

Metody sloužící pro určení, zdali se daný klíč nebo prvek nachází v kolekci:

Map<int, String> mapa = {
        0: 'nula',
        1: 'uno',
        2: 'два',
};

print(mapa.containsKey(2));
print(mapa.containsKey(3));

print(mapa.containsValue('uno'));
print(mapa.containsValue('jedna'));

Výstup programu:

Konzolová aplikace
true
false
true
false

V příští lekci, Diář s databází v Dartu, si naprogramujeme pomocí kolekcí databázi, bude to elektronický diář! :)


 

Stáhnout

Staženo 0x (2.29 kB)
Aplikace je včetně zdrojových kódů v jazyce Dart

 

 

Článek pro vás napsal Honza Bittner
Avatar
Jak se ti líbí článek?
1 hlasů
Milovník Dartu. Student FIT ČVUT. Sleduj mě na https://twitter.com/tenhobi a ptej se na cokoli na https://github.com/tenhobi/ama.
Miniatura
Předchozí článek
Datum a čas v Dartu
Miniatura
Následující článek
Diář s databází v Dartu
Aktivity (2)

 

 

Komentáře

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.

Zatím nikdo nevložil komentář - buď první!