Ruby po kapkách (3.) – jednoduchý program typu filtr

12. února 2009

Protože nepřetržité probírání reprezentace různých typů dat pomocí tříd v Ruby by bylo poněkud nudné, zvýšíme si dnes motivaci malým úkrokem stranou. Podíváme se, jak vytvořit a spustit jednoduchý program. Velmi zlehka přitom nakoukneme pod pokličku řídících struktur programu a vstupně-výstupních (IO) operací.

V prvním díle jsme se setkali s možností spouštět Ruby kód zadaný jako argument interpretu na příkazové řádce. Ve druhém díle jsme spouštěli kousky kódu pomocí irb. Nejobvyklejším způsobem, jak interpretu předložit kód programu, však zůstává umístění kódu do jednoho či více souborů. Zdrojový soubor s programem v Ruby má typicky příponu .rb a je uložen v 8mi bitovém kódování. To nám umožňuje používat v řetězcových konstantách diakritiku v kódování ISO-8859-2 nebo Windows-1250. Později si ukážeme, jak využívat vícebajtové UTF-8.

K vytvoření a modifikacím souboru se zdrojovým kódem potřebujeme vhodný editor. V prostředí unixových operačních systémů jich existuje více než malé množství a nebudu raději žádný zmiňovat jménem, protože to je spolehlivá cesta, jak vyvolat flamewar. Pokud používáte Windows a One-Click instalátor, získali jste jako vedlejší efekt open source editor SciTE (http://www.scintilla.org/SciTE.html). Najdete ho pod větví Ruby ve spouštěcím menu. Alternativně mohu doporučit freeware programátorský editor PSPad, na který byl uveden odkaz v prvním dílu. S vývojem v prostředí Mac OS X nemám přímou zkušenost, ale v Ruby komunitě bývá často pro tuto platformu doporučován editor TextMate (http://macromates.com/). Jedná se však o placený software.

Přípravu máme za sebou, pojďme tvořit. Založte soubor se zvoleným názvem a s koncovkou .rb a uložte do něj následující krátký kód.

puts „Jedna a jedna je #{1 + 1}.“

Nyní z příkazové řádky v adresáři, kam jste uložili soubor, spusťte příkaz ruby nazev.rb. Dosaďte zvolený název souboru. Pokud vše funguje, jak má, uvidíte něco podobného já.

C:\test>ruby prg1.rb
Jedna a jedna je 2.

V tomto triviálním programu jsme volali metodu puts a jako parametr jsme jí předali řetězec s vnořeným výrazem. Vnořený výraz se vyhodnotil, protože řetězec je obklopen dvojitými uvozovkami. Výsledek byl metodou puts vytisknut na standardní výstup a doplněn znakem konce řádku.

Již jsme zmiňovali silnou objektovou orientaci Ruby a nyní se bavíme o metodě puts. K jakému objektu či třídě ale tato metoda patří? Návrh jazyka Ruby byl významně ovlivněn i paradigmatem tzv. skriptovacích jazyků (zejména jazykem Perl), které se vyznačují extrémní vyjadřovací silou a minimálními „formálními“ požadavky na kód. Ruby pro nás proto automaticky vytváří instanci základní třídy Object (základní ve smyslu, že je to jakási pratřída – předek všech ostatních tříd) a na tuto instanci směruje všechna volání metod, která nemají explicitně určeného příjemce. Tuto skutečnost si můžeme ověřit tak, že výše uvedený řádek kódu nahradíme řádkem puts self.class. Metoda self vrací referenci na aktuální objekt a metoda class vrací třídu příjemce.

V Ruby je samozřejmě možné vytvářet i aplikace s grafickým uživatelským rozhraním (GUI). Pro jednoduché příklady je ale nejspíš vhodnější použít příkazovou řádku a koncept standardního vstupu a výstupu, který je již po desetiletí implementován ve všech hlavních operačních systémech. Každý proces má tak přidělen standardní vstup (obvykle označovaný jako STDIN), který je typicky spojený s klávesnicí. Dále má přidělen standardní výstup (STDOUT) a standardní chybový výstup (STDERR), které typicky využívají obrazovku. Pomocí prostředků příkazové řádky lze však standardní vstup i výstupy přesměrovat na soubory nebo dokonce propojit výstup jednoho procesu se vstupem jiného a vytvářet jejich kombinace umožňující rozmanité zpracování dat.

Jako další krok zkusme zpracovat nějaký vstup. Vytvořme jednoduchou sčítačku dvou čísel.

n1 = gets
n2 = gets
puts n1.to_f + n2.to_f

Kód opět uložíme a spustíme stejně jako v předchozím případě.

C:\test>ruby prg2.rb
123
321
444.0

Asi jste pochopili, že metoda gets pracuje opačně než puts. Načítá ze standardního vstupu až do okamžiku, kdy narazí na znak konce řádky (v případě klávesnice okamžik stisku Enter). Metoda gets vrací načtený řetězec nebo hondotu nil v případě, že při pokusu o čtení narazila na konec souboru (to se nám bude hodit později). Poprvé jsme také použili symbol přiřazení =, pomocí kterého uchováme odkazy na načtené řetězce pod názvy n1 a n2. n1 a n2 označujeme jako proměnné programu. Skrývá se za nimi totiž něco, co se v průběhu provádění může měnit. Pomocí volání puts nakonec vytiskneme součet hodnot, které získáme voláním metody to_f na oba řetězce. Metoda to_f se snaží převést příjmece na reálné číslo a to dosti intenzivně. V případě, že to opravdu není možné vrací nulu. Proveďte několik experimentů a zkuste do sčítačky zadat například „123a“ nebo „aaa“ či jiné řetězce podle vlastní fantazie. Zadávat samozřejmě lze i desetinná čísla, pouze je třeba bez ohledu na lokalizaci používat desetinnou tečku.

Pojďme zkusit něco ještě o malinko složitějšího. Řekněme, že se nechceme spokojit se sčítáním pevně daného počtu čísel, ale chceme sčítat libovolně dlouhou sekvenci. K tomu budeme potřebovat zavést do programu cyklus.

s = 0.0
while n = gets
s = s + n.to_f
end
puts s 

Řídící konstrukce cyklu se skládá z klíčového slova while, z podmínky za tímto slovem a z kódu ukončeného klíčovým slovem end. Cyklus while vyhodnotí vždy na začátku podmínku a vykonává se pokud a dokud platí, že podmínka je pravdivá. Naše podmínka vypadá ovšem poněku zvláštně, protože je v ní přiřazení hodnoty načtené metodou gets do proměnné. V Ruby ovšem platí, že i přiřazení je výraz, který má svou hodnotu a tato hodnota je rovna přiřazené hodnotě. V podmínce tedy ve skutečnosti testujeme návratovou hodnotu gets. Ta se může vyhodnotit jako nepravda pouze v případě, že narazíme na konec souboru a gets vrátí nil. Cyklus tedy načítá řádky až do konce souboru. Zbytek kódu mimo cyklus zajišťuje inicializaci proměnné, ve které uchováváme součet a vypsání výsledku.

Jak použijeme tuto vylepšenou sčítačku v praxi?

C:\test>ruby prg3.rb
1
2
3
^Z
6.0

Při vstupu z klávesnice nasimulujeme konec souboru ve Windows stisknutím Ctrl-Z a klávesy Enter, v unixových operačních systémech pak stisknutím kombinace Ctrl-D. Vstup však stejně dobře může představovat textový soubor, kde na každém řádku bude jedno číslo. Spuštění s přesměrováním souboru na standardní vstup programu pak může vypadat například takto:

C:\test>ruby prg3.rb < vstup.txt
6.0

Pro úplnost dodejme, že přesměrování výstupu do souboru bychom docílili přidáním > vystup.txt na konec příkazové řádky. V tuto chvíli máme veškeré znalosti k tomu, abychom mohli v Ruby vytvářet jednoduché filtry. Filtry jsou obvykle jednoúčelové programy, které načítají standardní vstup, provádějí na každém řádku nějakou transformaci (případně agregaci) a výsledek vypisují na standardní výstup. Nejspíš vám nebude činit potíže pochopit funkci následujícího fragmentu kódu.

while t = gets
puts t.center(80)
end

Pro vyzkoušení vložte do souboru vstup.txt několik slov, na každý řádek jedno, a spusťe kód s přesměrovaným vstupem. Nejlepšího výsledku dosáhnete na terminálu se standardní šířkou 80 znaků.

Vytváření jednoduchých filtrů je ve skutečnosti tak častou aktivitou, že má interpret Ruby podporu pro psaní transformačního kódu přímo jako agrumentu na příkazovou řádku. Výše uvedený příklad by šel jednoduše spustit jako:

ruby -ne ‚puts $_.center(80)‘ < vstup.txt

Vypadá to poněkud krypticky, ale není to složité. Volba -e říká interpretu, že má další argument spustit jako kód Ruby. Volba -n pak říká, že zadaný kód má být automaticky obklopen načítacím cyklem while gets ... end. Obě volby lze kombinovat, proto -ne. V samotném kódu mezi apostrofy je pak použita proměnná $_, kterou interpret Ruby vždy nastavuje na hodnotu posledního volání gets. (Tyto skryté automatismy jsou opět dědictvím Perlu.)

Na závěr předkládám k zamyšlení několik námětů na filtry, které by mělo být možné vytvořit na základě informací, které jsme až dosud probrali.

  • Filtr, který spočítá řádky vstupu.
  • Filtr, který načítá na každém řádku dvě čísla oddělená mezerou a vypisuje jejich součet.
  • Filtr, který vypisuje poslední slovo z každého řádku vstupu (slova jsou oddělena mezerou a vždy jsou aspoň dvě).

Nápověda k poslednímu úkolu: cíle lze poněkud složitěji dosáhnout metodami třídy String zmíněnými v minulé díle nebo jednodušeji objevením metody rindex. Nepředpokládám nutnost použití jiných dosud nezmíněných metod.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

1 komentář

  1. mc

    Dub 25, 2010 v 10:38

    Tak nevím proč, ale nefunguje mi konstrukce program.rbvystup.txt ale funguje bez problemu. Ještě zkusím nainstalovat Linux, snad to tam pojede. Kdybyste někdo věděl čím by to mohlo být, předem díky za radu.

    Odpovědět

Napsat komentář: mc Zrušit odpověď na komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *