2023-06-28 05:03 Doba čtení: 20 min

10 datových struktur Pythonu [Explained With Examples]

Chcete rozšířit možnosti svého programovacího nástroje o sofistikované datové struktury? Začněte ještě dnes a pronikněte do tajů datových struktur v Pythonu.

Při učení se novému programovacímu jazyku je klíčové porozumět základním datovým typům a vestavěným datovým strukturám, které tento jazyk nabízí. V tomto průvodci světem datových struktur v Pythonu se podíváme na:

  • výhody, které datové struktury přinášejí
  • základní vestavěné datové struktury Pythonu, jako jsou seznamy, n-tice, slovníky a množiny
  • implementaci abstraktních datových typů, například zásobníků a front.

Pojďme se do toho pustit!

Proč jsou datové struktury tak užitečné?

Než se ponoříme do detailů jednotlivých datových struktur, podívejme se, v čem spočívá jejich přínos:

  • Efektivní manipulace s daty: Správná volba datové struktury umožňuje efektivnější zpracování dat. Například, pokud potřebujete uložit kolekci prvků stejného datového typu s rychlým vyhledáváním a těsnou vazbou, pole je ideální volbou.
  • Optimalizace správy paměti: V rozsáhlejších projektech může být jedna datová struktura z hlediska paměťové náročnosti výhodnější než jiná. V Pythonu lze například seznamy i n-tice použít k uložení kolekcí dat stejného i různého typu. Pokud ale víte, že danou kolekci nebudete potřebovat upravovat, n-tice bude úspornější, protože zabírá méně paměti než seznam.
  • Zlepšení organizace kódu: Použití vhodné datové struktury pro daný účel zlepší organizaci vašeho kódu. Ostatní programátoři, kteří budou váš kód číst, očekávají, že budete používat specifické datové struktury v závislosti na požadované funkčnosti. Například, pokud potřebujete mapování párů klíč–hodnota s rychlým vyhledáváním a vkládáním, slovník je pro tento účel ideální.

Seznamy

Seznamy jsou v Pythonu základní datovou strukturou pro vytváření dynamických polí – ať už jde o kódovací úlohy nebo běžné programovací situace.

Seznamy v Pythonu jsou proměnlivé a dynamické kontejnery. Umožňují přidávat a odebírat prvky přímo v seznamu bez nutnosti vytvářet jeho kopii.

Při používání seznamů v Pythonu:

  • Indexování do seznamu a přístup k prvku na daném indexu probíhá v konstantním čase.
  • Přidání prvku na konec seznamu je také operace s konstantním časem.
  • Vložení prvku na konkrétní index je operace s lineární časovou složitostí.

Python nabízí sadu metod pro efektivní provádění běžných úloh s seznamy. Následující ukázka kódu ilustruje, jak tyto operace provádět:

>>> nums = [5,4,3,2]

>>> nums.append(7)
>>> nums
[5, 4, 3, 2, 7]

>>> nums.pop()
7
>>> nums
[5, 4, 3, 2]

>>> nums.insert(0,9)
>>> nums
[9, 5, 4, 3, 2]

Seznamy v Pythonu podporují také dělení a testování přítomnosti prvků pomocí operátoru `in`:

>>> nums[1:4]
[5, 4, 3]

>>> 3 in nums
True

Datová struktura seznamu je flexibilní, jednoduchá a umožňuje ukládat prvky různých datových typů. Python také nabízí specializovanou datovou strukturu pole pro efektivní ukládání prvků stejného datového typu, o které se dozvíme dále v tomto průvodci.

N-tice

N-tice jsou další oblíbenou vestavěnou datovou strukturou v Pythonu. Jsou podobné seznamům v tom, že k prvkům můžete přistupovat pomocí indexů v konstantním čase a můžete je dělit. N-tice jsou však neměnné, takže je nelze přímo upravovat. Následující ukázka kódu demonstruje tyto vlastnosti:

>>> nums = (5,4,3,2)

>>> nums[0]
5

>>> nums[0:2]
(5, 4)

>>> 5 in nums
True

>>> nums[0] = 7 # Neplatná operace!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Pokud potřebujete vytvořit neměnnou kolekci, se kterou chcete efektivně pracovat, zvažte použití n-tice. Pokud potřebujete kolekci, kterou lze měnit, je vhodnější použít seznam.

📋 Prozkoumejte více o podobnostech a rozdílech mezi seznamy a n-ticemi v Pythonu.

Pole

Pole jsou v Pythonu méně známou datovou strukturou. Podobají se seznamům v tom, jaké operace podporují, jako je přístup k prvkům pomocí indexů v konstantním čase a vkládání prvku na konkrétní index v lineárním čase.

Hlavní rozdíl mezi seznamy a poli spočívá v tom, že pole ukládají prvky pouze jednoho datového typu. Díky tomu jsou pole paměťově efektivnější a mají těsnější vazbu.

Pro vytvoření pole můžeme použít konstruktor `array()` z vestavěného modulu `array`. Konstruktor `array()` přijímá řetězec, který specifikuje datový typ prvků, a samotné prvky. Následující kód vytvoří pole `nums_f` čísel s plovoucí desetinnou čárkou:

>>> from array import array
>>> nums_f = array('f',[1.5,4.5,7.5,2.5])
>>> nums_f
array('f', [1.5, 4.5, 7.5, 2.5])

K prvkům pole můžete přistupovat pomocí indexů (podobně jako u seznamů):

>>> nums_f[0]
1.5

Pole jsou měnitelné, takže je můžete upravovat:

>>> nums_f[0]=3.5
>>> nums_f
array('f', [3.5, 4.5, 7.5, 2.5])

Nelze však měnit datový typ prvku:

>>> nums_f[0]='zero'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be real number, not str

Řetězce

Řetězce v Pythonu jsou neměnné kolekce znaků Unicode. Na rozdíl od programovacích jazyků jako C, Python nemá vyhrazený datový typ pro znak. Znak je tedy v Pythonu vlastně řetězec o délce jedna.

Jak již bylo zmíněno, řetězce jsou neměnné:

>>> str_1 = 'python'
>>> str_1[0] = 'c'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

Řetězce v Pythonu podporují dělení a disponují sadou metod pro jejich formátování. Následuje několik příkladů:

>>> str_1[1:4]
'yth'
>>> str_1.title()
'Python'
>>> str_1.upper()
'PYTHON'
>>> str_1.swapcase()
'PYTHON'

⚠ Nezapomeňte, že všechny výše uvedené operace vracejí kopii řetězce a nemění původní řetězec. Pokud máte zájem, podívejte se na průvodce Python Programy pro operace s řetězci.

Množiny

Množiny v Pythonu jsou kolekce unikátních, hashovatelných položek. Můžete provádět běžné množinové operace jako sjednocení, průnik a rozdíl:

>>> set_1 = {3,4,5,7}
>>> set_2 = {4,6,7}

>>> set_1.union(set_2)
{3, 4, 5, 6, 7}

>>> set_1.intersection(set_2)
{4, 7}

>>> set_1.difference(set_2)
{3, 5}

Množiny jsou ve výchozím nastavení měnitelné, takže můžete přidávat a upravovat jejich prvky:

>>> set_1.add(10)
>>> set_1
{3, 4, 5, 7, 10}

📚 Přečtěte si o množinách v Pythonu: Kompletní průvodce s ukázkami kódu

FrozenSets

Pokud potřebujete neměnnou množinu, můžete použít `frozenset`. Můžete ji vytvořit z existující množiny nebo jiného iterovatelného objektu.

>>> frozenset_1 = frozenset(set_1)
>>> frozenset_1
frozenset({3, 4, 5, 7, 10})

Protože `frozenset_1` je neměnná množina, pokus o přidání prvku (nebo jiná modifikace) způsobí chybu:

>>> frozenset_1.add(15)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'

Slovníky

Slovník v Pythonu funguje podobně jako hash mapa. Slovníky slouží k ukládání párů klíč–hodnota. Klíče slovníku musí být hashovatelné, což znamená, že hodnota hash objektu se nesmí měnit.

K hodnotám můžete přistupovat pomocí klíčů, vkládat nové položky a odstraňovat stávající položky v konstantním čase. Python nabízí metody pro provádění těchto operací.

>>> favorites = {'book':'Orlando'}
>>> favorites
{'book': 'Orlando'}

>>> favorites['author']='Virginia Woolf'
>>> favorites
{'book': 'Orlando', 'author': 'Virginia Woolf'}

>>> favorites.pop('author')
'Virginia Woolf'
>>> favorites
{'book': 'Orlando'}

OrderedDict

Ačkoli slovník v Pythonu nabízí mapování klíč–hodnota, je ze své podstaty neuspořádanou datovou strukturou. Od Pythonu 3.7 je sice zachováno pořadí vkládání prvků, pro větší přehlednost však můžete použít `OrderedDict` z modulu `collections`.

Jak je vidět na příkladu, `OrderedDict` zachovává pořadí klíčů:

>>> from collections import OrderedDict
>>> od = OrderedDict()
>>> od['first']='one'
>>> od['second']='two'
>>> od['third']='three'
>>> od
OrderedDict([('first', 'one'), ('second', 'two'), ('third', 'three')])
>>> od.keys()
odict_keys(['first', 'second', 'third'])

Defaultdict

Chyby klíče jsou při práci se slovníky v Pythonu poměrně časté. Kdykoli se pokusíte přistoupit ke klíči, který nebyl do slovníku přidán, vyvolá se výjimka `KeyError`.

Modul `defaultdict` z modulu `collections` vám však umožňuje tento problém elegantně vyřešit. Když se pokusíte přistoupit ke klíči, který se ve slovníku nenachází, klíč se přidá a inicializuje s výchozí hodnotou určenou výchozí továrnou.

>>> from collections import defaultdict
>>> prices = defaultdict(int)
>>> prices['carrots']
0

Zásobníky

Zásobník je datová struktura typu LIFO (last-in-first-out), tedy „poslední dovnitř, první ven“. Se zásobníkem můžeme provádět následující operace:

  • Přidání prvku na vrchol zásobníku (operace push)
  • Odebrání prvku z vrcholu zásobníku (operace pop)

Následující příklad ilustruje, jak operace push a pop fungují:

Jak implementovat zásobník pomocí seznamu

V Pythonu můžeme implementovat datovou strukturu zásobníku pomocí seznamu.

Operace se zásobníkem Ekvivalentní operace se seznamem
Push na vrchol zásobníku Přidání na konec seznamu pomocí metody `append()`
Pop z vrcholu zásobníku Odebrání a vrácení posledního prvku pomocí metody `pop()`

Následující ukázka kódu ilustruje, jak můžeme napodobit chování zásobníku pomocí seznamu v Pythonu:

>>> l_stk = []
>>> l_stk.append(4)
>>> l_stk.append(3)
>>> l_stk.append(7)
>>> l_stk.append(2)
>>> l_stk.append(9)
>>> l_stk
[4, 3, 7, 2, 9]
>>> l_stk.pop()
9

Jak implementovat zásobník pomocí Deque

Dalším způsobem implementace zásobníku je použití `deque` z modulu `collections`. `Deque` znamená oboustrannou frontu a umožňuje přidávání a odebírání prvků z obou konců.

Pro emulaci zásobníku můžeme:

  • Přidávat prvky na konec `deque` pomocí `append()`.
  • Odebírat naposledy přidaný prvek pomocí `pop()`.
>>> from collections import deque
>>> stk = deque()
>>> stk.append(4)
>>> stk.append(3)
>>> stk.append(7)
>>> stk.append(2)
>>> stk.append(9)
>>> stk
deque([4, 3, 7, 2,9])
>>> stk.pop()
9

Fronty

Fronta je datová struktura typu FIFO (first-in-first-out), tedy „první dovnitř, první ven“. Prvky se přidávají na konec fronty a odebírají ze začátku fronty, jak je znázorněno:

Datovou strukturu fronty můžeme implementovat pomocí `deque`:

  • Přidáváme prvky na konec fronty pomocí metody `append()`.
  • Pro odstranění prvku ze začátku fronty používáme metodu `popleft()`.
>>> from collections import deque
>>> q = deque()
>>> q.append(4)
>>> q.append(3)
>>> q.append(7)
>>> q.append(2)
>>> q.append(9)
>>> q.popleft()
4

Halda (Heap)

V této sekci si probereme binární haldy, konkrétněji minimální haldy.

Minimální halda je kompletní binární strom. Pojďme si vysvětlit, co znamená „kompletní binární strom“:

  • Binární strom je stromová datová struktura, kde každý uzel má maximálně dva podřízené uzly, a každý uzel je menší než jeho potomci.
  • Termín „kompletní“ znamená, že strom je zcela zaplněn, s možnou výjimkou poslední úrovně. Pokud je poslední úroveň částečně zaplněna, je zaplněna zleva doprava.

Protože každý uzel má maximálně dva podřízené uzly a zároveň platí, že každý uzel je menší než jeho potomci, kořen je minimálním prvkem v minimální haldě.

Zde je příklad minimální haldy:

V Pythonu nám modul `heapq` umožňuje vytvářet haldy a provádět s nimi operace. Naimportujme si potřebné funkce z `heapq`:

>>> from heapq import heapify, heappush, heappop

Pokud máte seznam nebo jiný iterovatelný objekt, můžete z něj vytvořit haldu voláním `heapify()`:

>>> nums = [11,8,12,3,7,9,10]
>>> heapify(nums)

Můžete přistoupit k prvnímu prvku pomocí indexu a zkontrolovat, zda se jedná o minimální prvek:

>>> nums[0]
3

Když vložíte prvek do haldy, uzly se přeuspořádají tak, aby platila vlastnost minimální haldy:

>>> heappush(nums,1)

Protože jsme vložili 1 (1 < 3), vidíme, že `num[0]` vrátí 1, což je nyní minimální prvek (a kořenový uzel):

>>> nums[0]
1

Prvky z minimální haldy můžeme odebírat voláním funkce `heappop()`, jak je ukázáno níže:

>>> while nums:
...     print(heappop(nums))
...
# Output
1
3
7
8
9
10
11
12

Maximální haldy v Pythonu

Nyní, když znáte minimální haldy, dokážete odhadnout, jak můžeme implementovat maximální haldu?

Implementaci minimální haldy můžeme transformovat na maximální haldu vynásobením každého čísla -1. Negovaná čísla uspořádaná v minimální haldě jsou ekvivalentní původním číslům uspořádaným v maximální haldě.

V implementaci v Pythonu můžeme prvky vynásobit -1 při vkládání do haldy pomocí `heappush()`:

>>> maxHeap = []
>>> heappush(maxHeap,-2)
>>> heappush(maxHeap,-5)
>>> heappush(maxHeap,-7)

Kořenový uzel – vynásobený -1 – bude maximálním prvkem.

>>> -1*maxHeap[0]
7

Při odstraňování prvků z haldy použijte `heappop()` a vynásobte výsledek -1, abyste získali původní hodnotu:

>>> while maxHeap:
...     print(-1*heappop(maxHeap))
...
# Output
7
5
2

Prioritní fronty

Na závěr si probereme datovou strukturu prioritní fronty v Pythonu.

Jak víme, ve frontě se prvky odebírají ve stejném pořadí, v jakém do fronty přišly. Prioritní fronta však obsluhuje prvky podle jejich priority, což je užitečné například při plánování. V každém okamžiku je tedy vrácen prvek s nejvyšší prioritou.

Pro definování priority můžeme použít klíče. Zde použijeme číselné váhy pro klíče.

Jak implementovat prioritní fronty pomocí Heapq

Zde je implementace prioritní fronty pomocí `heapq` a seznamu v Pythonu:

>>> from heapq import heappush,heappop
>>> pq = []
>>> heappush(pq,(2,'write'))
>>> heappush(pq,(1,'read'))
>>> heappush(pq,(3,'code'))
>>> while pq:
...     print(heappop(pq))
...

Při odebírání prvků fronta obslouží nejprve prvek s nejvyšší prioritou (1, 'read'), následovaný (2, 'write') a poté (3, 'code').

# Output
(1, 'read')
(2, 'write')
(3, 'code')

Jak implementovat prioritní fronty pomocí PriorityQueue

K implementaci prioritní fronty můžeme také použít třídu `PriorityQueue` z modulu `queue`. Tato třída interně také používá haldu.

Zde je ekvivalentní implementace prioritní fronty pomocí `PriorityQueue`:

>>> from queue import PriorityQueue
>>> pq = PriorityQueue()
>>> pq.put((2,'write'))
>>> pq.put((1,'read'))
>>> pq.put((3,'code'))
>>> pq
<queue.PriorityQueue object at 0x00BDE730>
>>> while not pq.empty():
...     print(pq.get())
...
# Output
(1, 'read')
(2, 'write')
(3, 'code')

Shrnutí

V tomto tutoriálu jste se seznámili s různými vestavěnými datovými strukturami v Pythonu. Také jsme si prošli různé operace a vestavěné metody, které tyto datové struktury podporují.

Dále jsme se podívali na další datové struktury jako zásobníky, fronty a prioritní fronty a jejich implementace v Pythonu pomocí funkcí z modulu `collections`.

Nyní se můžete podívat na seznam projektů v Pythonu pro začátečníky.

Jan Novák
Autor
Czechia

Redaktor zaměřený na Windows, produktivitu a cloudové nástroje.

Předchozí článek
10 aplikací pro psaní poznámek na Apple Watch, které usnadňují organizaci
Další článek
Řešení all-in-one pro růst podnikání pro prodejce na Amazonu