Obsah

Řazení

Řazení prvků patří mezi časté operace, které se v programátorské praxi vyskytují. Problematika řazení1) je velmi důkladně teoreticky zpracována a zkušený programátor by měl být obeznámen alespoň se základními vlastnostmi hlavních řadicích algoritmů a podle okolností správně volit odpovídající řešení.

Metoda sort

Naše časové možnosti nám nedovolují zabývat se tímto tématem hlouběji, přejdeme přímo k základním prostředkům pro řazení v jazyce Python. Budeme předpokládat, že úkolem je seřadit seznam prvků podle nějakého kritéria. To je možné pouze v případě, že libovolné dva prvky seznamu lze porovnat (tj. určit, který z nich je větší, případně zda se rovnají). Z předchozích hodin víme, že je možné porovnávat dvě čísla (dokonce i int a float navzájem), ale také dva řetězce či dva seznamy podle lexikografického uspořádání (nikoli však např. číslo s řetězcem).

Objektový typ list (seznam) má pro tento účel metodu sort, která upraví (přeskupí) seznam tak, že jeho prvky utvoří uspořádanou posloupnost.

cisla = [15, 4, 11, 8, -3, 5, 2, 1, 10]
cisla.sort()
cisla          # => [-3, 1, 2, 4, 5, 8, 10, 11, 15]

V jednoduché podobě metoda sort nedostává žádné parametry, pak jsou prvky seznamu uspořádány vzestupně (platí, že následník prvku je vždy větší nebo roven svému předchůdci).

Obecně je pravdou, že neexistuje „nejlepší“ řadicí algoritmus, který je naprosto univerzálně vhodný na všechny oblasti použití. Na druhé straně lze říci, že ve většině běžných případů (např. všechny prvky se nám zároveň vejdou do operační paměti počítače) odvede představená metoda sort svou práci velmi dobře - efektivně a rychle2).

Několik dalších příkladů řazení seznamů.

# shodné prvky se mohou v seznamu opakovat
cisla = [5, 4, 2, 6, 5, 5, 8, 1]
cisla.sort()
cisla          # => [1, 2, 4, 5, 5, 5, 6, 8]
 
# 'int' a 'float' se mohou vyskytovat v seznamu zároveň
cisla = [2.5, -0.21, 1, 0.98, 3, 3.0, 0.21]
cisla.sort()
cisla          # => [-0.21, 0.21, 0.98, 1, 2.5, 3, 3.0]
 
# řazení seznamu řetězců
barvy = ['red', 'white', 'black', 'blue', 'yellow']
barvy.sort()
barvy          # => ['black', 'blue', 'red', 'white', 'yellow']

Ve své základní podobě probíhá řazení metodou sort vzestupně. Pomocí volitelného parametru reverse logického typu je možné pořadí obrátit - seznam je uspořádán v klesajícím (přesněji nerostoucím) pořadí.

cisla.sort(reverse = True)
cisla          # => [3, 3.0, 2.5, 1, 0.98, 0.21, -0.21]
 
barvy.sort(reverse = True)
barvy          # => ['yellow', 'white', 'red', 'blue', 'black']

Metoda sort nemá návratovou hodnotu (resp. vrací None), výsledkem její činnosti je přeuspořádání samotného objektu.

Kritérium řazení

Poměrně často potřebujeme seřadit prvky seznamu podle jiného pravidla než je prosté porovnání hodnoty čísel či lexikografické uspořádání řetězců. K tomuto účelu má metoda sort volitelný parametr key. Pomocí něj se definuje funkce či metoda, která pro každý prvek poskytuje nějakou návratovou hodnotu, a právě na základě výsledků se provádí výsledné řazení.

# seřadíme čísla podle jejich absolutní hodnoty
cisla = [2, -5, 1, -4, 3, -2, 0]
cisla.sort(key = abs)
cisla          # => [0, 1, 2, -2, 3, -4, -5]

V tomto případě je výsledný seznam uspořádán nikoliv podle vlastní hodnoty čísel, nýbrž podle jejich absolutní hodnoty, tj. podle návratové hodnoty funkce abs (-4 je až za 3, protože platí abs(3) < abs(-4)). Novým poznatkem je pro nás i skutečnost, že i funkce či metody lze pomocí jména předávat jako parametry. Další ukázky následují.

# seřazení řetězců podle délky, tj. návratové hodnoty funkce 'len'
barvy.sort(key = len)
barvy          # => ['red', 'blue', 'white', 'black', 'yellow']
 
# seřazení řetězců bez ohledu na velikost písmen
#   na každý prvek se aplikuje řetězcová metoda 'lower', výsledné
#   řetězce jsou pak řazeny normálně lexikograficky
seznam = ['Juk', 'haló', 'KUKU', 'Baf', 'BAF', 'ahoj', 'Nazdar']
seznam.sort(key = str.lower)   # metoda objektu 'str'
seznam         # => ['ahoj', 'Baf', 'BAF', 'haló', 'Juk', 'KUKU', 'Nazdar']
# pro porovnání normálně seřazený seznam
seznam.sort()
seznam         # => ['BAF', 'Baf', 'Juk', 'KUKU', 'Nazdar', 'ahoj', 'haló']
#   volitelné parametry 'key' a 'reverse' je možné použít najednou
seznam.sort(key = str.lower, reverse = True)
seznam         # => ['Nazdar', 'KUKU', 'Juk', 'haló', 'Baf', 'BAF', 'ahoj']

Při použití metody jako řadicího kritéria je nutno pomocí tečkové notace uvést i objektový typ, ke kterému metoda patří. Za povšimnutí stojí také skutečnost, že pořadí prvků při řazení se nezmění, pokud při porovnání návratových hodnot funkce nastane shoda3) (říká se, že řadicí algoritmus je stabilní).

Řazení řetězců v češtině

Již dříve jsme hovořili o problematice kódování znaků a řazení řetězců a upozorňovali na složitost implementace řazení podle národních zvyklostí. Nyní si ukážeme, jak správně seřadit seznam řetězců podle pravidel češtiny.

Využijeme k tomu standardní modul locale, který obhospodařuje veškeré národně a místně specifické činnosti (kromě námi použitého porovnávání řetězců také např. formátování číselných, finančních a datových údajů). Po importování modulu je nutné nastavit požadované národní prostředí pomocí setlocale, pro češtinu je předdefinován řetězec cs_CZ. Modul locale poskytuje mimo jiné i funkci strxfrm, která transformuje řetězec do podoby zaručující požadované řazení. Tuto funkci můžeme přímo použít jako parametr key pro řadicí metodu sort či funkci sorted4).

ceske_razeni.py
# modul pro nastavení národního prostředí
import locale
locale.setlocale(locale.LC_ALL, "cs_CZ") # čeština, Česko
 
seznam = ['candát', 'šakal', 'čmelák', 'liška', 'srna', 'lín', 'chroust']
# třídění bez nastavení národního prostředí
seznam.sort()
print(seznam)
# třídění podle pravidel češtiny
seznam.sort(key = locale.strxfrm)
print(seznam)

Program poskytl následující výstup, kromě správného pořadí písmen s diakritikou na druhém řádku si povšimněte i správného zařazení ch (z hlediska řazení se jedná o jedno písmeno!).

['candát', 'chroust', 'liška', 'lín', 'srna', 'čmelák', 'šakal']
['candát', 'čmelák', 'chroust', 'lín', 'liška', 'srna', 'šakal']

Vlastní funkce

V dosavadních příkladech jsme použili vestavěné funkce či metody jazyka, nic nám však nebrání použít i vlastní funkci. Taková funkce musí zpracovat prvek předaný pomocí pozičního parametru a výsledek poskytnout jako návratovou hodnotu.

Řekněme, že bychom chtěli seřadit seznam slov podle počtu výskytů písmene a. Napíšeme si funkci pocet_a s jedním řetězcovým parametrem a návratovou hodnotou typu int a s její pomocí již snadno seřadíme zadaný seznam.

razeni.py
def pocet_a(prvek):
    return prvek.count('a')
 
seznam = ['rada', 'klíč', 'klacek', 'abrakadabra', 'tralala']
seznam.sort(key = pocet_a)
print(seznam)

s výsledkem ['klíč', 'klacek', 'rada', 'tralala', 'abrakadabra'].

Funkce sorted

Pro situace, kdy potřebujeme seřazený seznam položek, ale chceme zachovat zdrojová data v původní podobě, nám Python nabízí funkci sorted. Té předáváme zdrojová data ve formě seznamu (obecněji dokonce data ve formě objektu, jehož položky můžeme postupně procházet - z nám známých datových typů také řetězec) jako poziční parametr, pojmenované parametry reverse a key je možno použít výše popsaným způsobem. Návratovou hodnotou funkce je nově vytvořený seznam, původní data zůstávají nezměněna.

# seřazení čísel při zachování původního seznamu
cisla = [2, -5, 1, -4, 3, -2, 0]
cisla2 = sorted(cisla)
cisla2        # => [-5, -4, -2, 0, 1, 2, 3]
#   vlastní kritérium řazení, sestupné pořadí
cisla3 = sorted(cisla, key = abs, reverse = True)
cisla3        # => [-5, -4, 3, 2, -2, 1, 0]
 
# vytvoření seřazeného seznamu znaků zdrojového řetězce
sorted("Python 3")    # => [' ', '3', 'P', 'h', 'n', 'o', 't', 'y']
1)
často se na základě anglického sorting uvádí i třídění
2)
Ve speciálních případech (např. téměř setříděné seznamy) by jiné algoritmy mohly být i rychlejší.
3)
pro řazení ve vzestupném pořadí, naopak prvky jsou vždy prohozeny při reverse = True
4)
viz níže