Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 12 - Kolekce Set, Queue a Map v Dartu

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.

Objektově orientované programování v Dartu

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ář! :)


 

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 4x (2.29 kB)
Aplikace je včetně zdrojových kódů v jazyce Dart

 

Předchozí článek
Datum a čas v Dartu
Všechny články v sekci
Objektově orientované programování v Dartu
Přeskočit článek
(nedoporučujeme)
Diář s databází v Dartu
Článek pro vás napsal Honza Bittner
Avatar
Uživatelské hodnocení:
2 hlasů
FIT ČVUT alumnus :-) Sleduj mě na https://twitter.com/tenhobi a ptej se na cokoli na https://github.com/tenhobi/ama.
Aktivity