Ruby po kapkách (6.) – iterátory a Enumerable
V minulém díle jsme se zabývali dvěma nejběžnějšími složitějšími datovými typy – polem a hashem. Co mají tyto dvě struktury společného? V obecné rovině se jedná o kolekce objektů, ke kterým můžeme přistupovat přímo pomocí indexu. V případě pole je indexem číslo, v případě hashe libovolný objekt, který reaguje na volání hash
navrácením unikátního klíče. Procházet obě kolekce můžeme ale i sekvenčně – prvek po prvku. S využitím stávajících znalostí Ruby bychom mohli takto iterovat přes pole pomocí postupného zvyšování indexu ve smyčce. V případě hashe bychom mohli použít stejný postup s tím, že bychom hash nejdříve převedli na pole hodnot pomocí volání values
.
Pozor! Následující příklad ukazuje výše popsaný postup, který může být blízký mnoha programátorům odkojeným klasickými procedurálními jazyky. V Ruby se to ale takhle rozhodně nedělá, i když to, jak je vidět, také funguje. (Rozhodně diskutabilní je v tomto případě opakované volání values
ve smyčce, což není zrovna efektivní algoritmus.)
irb(main):001:0> a = [‚a‘, ‚b‘, ‚c‘]
=> [„a“, „b“, „c“]
irb(main):002:0> 0.upto(2) { |i| puts a[i] }
a
b
c
=> 0
irb(main):003:0> h = { 1 => ‚jedna‘, 2 => ‚dve‘, 3 => ‚tri‘ }
=> {1=>“jedna“, 2=>“dve“, 3=>“tri“}
irb(main):004:0> 0.upto(2) { |i| puts h.values[i] }
jedna
dve
tri
Pro procházení vestavěných kolekcí nám Ruby nabízí takzvané iterátory. Jsou to vlastně obyčejné metody, které předávají do bloku postupně všechny prvky kolekce. Základním iterátorem je each
. V případě hashe předává each
do bloku jak klíč tak hodnotu. Proto má třída Hash
navíce definovány iterátory each_key
pro iteraci jen přes klíče a each_value
pro iteraci jen přes hodnoty.
irb(main):005:0> a.each { |e| puts e }
a
b
c
=> [„a“, „b“, „c“]
irb(main):006:0> h.each { |k, v| puts „#{k} / #{v}“ }
1 / jedna
2 / dve
3 / tri
=> {1=>“jedna“, 2=>“dve“, 3=>“tri“}
irb(main):007:0> h.each_key { |k| puts k }
1
2
3
=> {1=>“jedna“, 2=>“dve“, 3=>“tri“}
irb(main):008:0> h.each_value { |v| puts v }
jedna
dve
tri
=> {1=>“jedna“, 2=>“dve“, 3=>“tri“}
irb(main):009:0> h.values.each { |v| puts v }
jedna
dve
tri
=> [„jedna“, „dve“, „tri“]
Pro úplnost je v závěru uvedena i varianta s převodem hashe na pole hodnot. Tentokrát ovšem k přechodu dochází pouze jednou a iteruje se nad výsledným polem. Povšimněte si zřetězení volání metod. Návratovou hodnotou values
je objekt, který má definovanou metodu each
. Takto řetězit lze opakovaně, což přispívá k úspornému zápisu kódu. V návrhu jazyka se na to myslí a návratové hodnoty metod vestavěných tříd jsou přizpůsobeny možnému zřetězení. V příkladech z irb
jsou ostatně návratové hodnoty vidět. Umožňuje nám to například toto.
irb(main):010:0> h.each_key { |k| puts k }.each_value { |v| puts v }
1
2
3
jedna
dve
tri
=> {1=>“jedna“, 2=>“dve“, 3=>“tri“}
Pokud se vám práce s kolekcemi pomocí iterátorů líbí, tak tohle je teprve začátek. Každý objekt, který má definovánu metodu each
se totiž může snadno stát Enumerable
. Zatím nebudeme rozebírat, jak, ani co to přesně znamená. Řekněme si jen, že v okamžiku, kdy je objekt Enumerable
, automaticky získá několik velmi zajímavých metod. Ty umožňují s kolekcí provádět:
- vyhledávání nebo filtrování prvků,
- třídění,
- transformace,
- a agregace.
Předveďme si to postupně na příkladech.
irb(main):001:0> a = [ ‚ryba‘, ‚had‘, ‚pes‘, ‚vrabec‘, ‚osel‘ ]
=> [„ryba“, „had“, „pes“, „vrabec“, „osel“]
irb(main):002:0> a.find { |e| e.size == 3 }
=> „had“
irb(main):003:0> a.find_all { |e| e.size == 3 }
=> [„had“, „pes“]
irb(main):004:0> a.include?(‚mezek‘)
=> false
irb(main):005:0> a.reject { |e| e.size == 3 }
=> [„ryba“, „vrabec“, „osel“]
irb(main):006:0> a.grep(/r/)
=> [„ryba“, „vrabec“]
irb(main):007:0> a.min
=> „had“
irb(main):008:0> a.max
=> „vrabec“
irb(main):009:0> a.sort
=> [„had“, „osel“, „pes“, „ryba“, „vrabec“]
irb(main):010:0> a.map { |e| e.capitalize }
=> [„Ryba“, „Had“, „Pes“, „Vrabec“, „Osel“]
Metoda find
vyhledá první prvek, pro který předaný blok vrátí true
. Rozvláčněji řečeno: Enumerable
má definovanou metodu find
. Ta postupně pro každý prvek kolekce volá daný blok a prvek mu předává jako parametr. Výraz, který je v bloku uvedený naposledy je zároveň návratovou hodnotou bloku. Protože v bloku máme jen jeden výraz – porovnání délky prvku a čísla 3, je vyhodnocení této podmínky zároveň i návratovou hodnotou bloku. Metoda find
vrací první prvek, pro který blok vrátil true
.
Návratovou hodnotou metody find_all
je pole všech prvků, pro které blok vrátil true
. Metoda include?
vrací true
nebo false
v závislosti na tom, jestli je parametr obsažený v kolekci nebo ne. Metoda reject
pracuje inverzně k metodě find_all
a vrací pole se všemi prvky, pro které blok vrací false
. S metodou grep
poněkud předbíháme, protože ta jako parametr zpracovává regulární výraz. V příkladu je použitý triviální regulární výraz sestávající z jediného písmena a vyhledává všechny prvky, které obsahují ‚r‘. Metody min
a max
vracejí minimální a maximální prvek kolekce. V našem případě jsou prvky řetězce a navrácenými hodnotami jsou tedy první a poslední slovo podle abecedy. Metoda sort
vrací pole seřazených prvků kolekce.
Velmi užitečná je metoda map
, která umožňuje iterovat přes prvky kolekce a zároveň s nimi provádět nějakou operaci. Výstupem je pole návratových hodnot z bloku pro každý prvek. Trošku složitější na pochopení je pak metoda inject
, kterou si ukážeme zvlášť. Tato metoda postupně předává do bloku jednotlivé prvky a také návratovou hodnotu předchozího volání bloku (výsledek zpracování předchozího prvku). Klasickým příkladem na vysvětlení fungování metody inject
je sečtení prvků pole.
irb(main):001:0> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> a.inject { |suma, e| suma + e }
=> 6
irb(main):003:0> a.inject(0) { |suma, e| suma + e }
=> 6
irb(main):004:0> a.inject(1) { |suma, e| suma + e }
=> 7
V prvním případě je metoda inject
volána bez parametru a postupně předává do bloku tyto dvojice parametrů: 1, 2; 3, 3. Vidíme jednak, že výsledek prvního volání bloku (součet 1 a 2) byl předán jako parametr suma do druhého volání. Také lze vypozorovat, že první volání bloku bylo speciální v tom, že žádný předchozí výsledek ještě nebyl k dispozici. Do parametru suma se tak předal první prvek a samotná iterace začala vlastně až druhým prvek. Pokud by nám toto chování nevyhovovalo, můžeme metodě předat volitelný parametr, který se použije v parametru suma v první iteraci.
Pojďme se teď podívat, jak dlouhý program potřebujeme v Ruby, abychom vytvořili generátor náhodných osmiznakových hesel. Než ho napíšeme, naučíme se ještě pár fíglů. Prvním je třída Range
, se kterou jsme se již setkali v indexování řetězců a polí. Zapisuje se literálem sestávajícím z prvního a posledního prvku, které jsou odděleny dvěma případně třemi tečkami. Dvojtečková varianta zahrnuje poslední prvek, trojtečková ne. Tato třída má definovanou metodu each
a je Enumerable
.
irb(main):001:0> (1..3).each { |n| puts n }
1
2
3
=> 1..3
irb(main):002:0> (‚a’…’c‘).each { |c| puts c }
a
b
=> „a“…“c“
Literál pro konstrukci Range
musí být uzávorkován, protože operátor .. má nižší prioritu než tečkový operátor pro volání metody. Objekty, které tvoří hraniční body rozsahu musí být navzájem porovnatelné a musí odpovídat na metodu succ
, která vrací následující objekt v pořadí.
Další věc, která se nám bude hodit, že existuje metoda sort_by
, která funguje podobně jako sort
, ale řadí prvky na základě výsledku předaného bloku. Předposlední věc je metoda rand
, která vrací náhodné číslo. A poslední je nepovinný číselný parametr pro metodu first
, který umožňuje získat prvních několik prvků pole.
Nyní se můžeme podívat na zmíněný generátor hesel. Pro mírné zjednodušení předpokládejme, že hesla budou složena pouze z malých písmen a až z.
irb(main):001:0> (‚a‘..’z‘).sort_by { rand }.first(8).join
=> „xsepvdwb“
irb(main):002:0> (‚a‘..’z‘).sort_by { rand }.first(8).join
=> „afeunwxh“
Jak je vidět celý program se díky řetězení silných metod podařilo vměstnat do 41 znaků. Jednoduše popsaná funkčnost je následující: rozsah písmen od a do z seřadíme podle náhodného čísla – čili v náhodném pořadí. Pak vezmeme prvních 8 znaků a pomocí metody join
z nich uděláme řetězec.
Příště se opět zaměříme na úkoly a vyzkoušíme si více podobných miniprogramů.
Starší komentáře ke článku
Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.
Mohlo by vás také zajímat
-
Doména .io v ohrožení: Co přinese předání Čagoských ostrovů?
10. října 2024 -
Netcat a Ncat
8. prosince 2022 -
Umělá inteligence v IT
27. září 2023
Nejnovější
-
Výkonný a kompaktní: ASOME Max Studio s výjimečným poměrem cena/výkon
11. listopadu 2024 -
Šokující data od Microsoftu: Kyberútoky rostou o stovky procent!
8. listopadu 2024 -
Chcete jedinečnou doménu? Objevte koncovky FOOD, MEME a MUSIC!
7. listopadu 2024 -
OpenAI představilo novou funkci ChatGPT Search
6. listopadu 2024