====== 7 - Funkce ======
Cílem tohoto cvičení je seznámit se se způsoby volání funkcí (metod), předávání argumentů a návratových hodnot v trochu složitějším programu. Konkrétně v programu, jehož účelem je zpracování textového záznamu z průběhu několika experimentů do podoby textové tabulky. Následně si vytvoříme graf průběhu sledovaných parametrů z experimentu.
Vedlejším cílem cvičení je pochopení již hotového kódu a schopnost jej rozšířit, což se ověří i na domácí úloze.
Zároveň si na začátek napíšeme krátký písemný test.
===== Výchozí program v Javě =====
Výchozí projekt {{:courses:a0b36pr1:labs:pr1-lab07.zip|}} se skládá z připravené třídy ''Lab07'', v které se používá rozšířená třída ''TextIO'' pro zpracování souboru se záznamy. Projekt také obsahuje třídu ''Demo'', která je určena pro testování předávání argumentů a návratových hodnot funkcí. Pro volání metody ''start'' z třídy ''Demo'' je nutné modifikovat funkci ''main'' ve třídě ''Start''. Hlavní část programu je implementována v metodě ''processFile'', ve které je opakovaně načítán vstupní soubor a průměrná hodnota je vypočtena metodou ''getAvg''. Již používaná třída ''TextIO'' v předchozích cvičení je v tomto projektu rozšířena o načítání textového souboru se záznamy. Práce se soubory není náplní tohoto cvičení, proto do této třídy pouze nahlédněte, případně se pokuste porozumět jakým stylem je vstupní soubor zpracován.
Součastí archívu projektu jsou také vstupní datové soubory v adresáři ''dat'' a sada skriptů (programů) pro vykreslení grafu získaných hodnot z datových souborů se záznamy experimentů. Tyto skripty jsou uloženy v adresáři ''scripts'' a jejich volání předpokládá dva argumenty, první je jméno souboru s tabulkou (vytvoří náš program) a druhý argument specifikuje výstupní pdf soubor, do kterého je vykreslen graf průběhu hodnot.
Výchozí projekt je implementován s využitím pokud možno již probíraných konstruktů a standardních knihovních funkcí Java. Zejména implementace metod ''readLine'' a ''getColumnValue'' ve třídě ''TextIO'' je možné realizovat jinak - elegantněji a obecněji. Náplní dalších cvičení bude profilování tohoto kódu pro velké vstupní soubory, zobecnění a optimalizace zpracování s využitím asociativního pole a spojových seznamů. Zde použitá implementace však poskytuje prostor pro pochopení a rozvinutí zpracování souboru dat a ošetření výjimečných situací.
===== Informace k procvičovanému tématu =====
==== Předávání vstupních parametrů funkcí ====
Hlavní mechanismus předávání vstupních parametrů funkcí jsou v programovacím jazyku Java argumenty, které se formálně zapisují (a pojmenovávají) v hlavičce funkce (metody).
Argumenty funkcí jsou předávány v podstatě pouze hodnotou, můžeme však rozlišit mezi předáváním základního typu nebo referencí (odkazem) na existující objekt (obdoba ukazatele).
V prvním případě volání hodnotou se ve funkci vytváří kopie proměnné a nastaví se její hodnota, proto změna této proměnné nemění hodnotu proměnné předchozí do funkce vstupující. Tento způsob předávání je využíván pro základní datové typy, jako jsou ''int'', ''float'', ''double'' atd. Příklad použití je například:
public void print(int n) {
System.out.println("Input number is: " + n); //tisk hodnoty lokální proměnné na obrazovku, vytiskne 10
n += 10;
System.out.println("Now, the number is: " + n); //tisk hodnoty lokální proměnné na obrazovku, vytiskne 20
}
public void start() {
int number = 10; //lokální proměnná s rozsahem platnosti v rámci metody start
print(number); //předání proměnné (její hodnoty) funkci print
System.out.println("Number is: " + number); //vytiskne 10, protože byla předána hodnota
}
Druhým mechanismem je předávání odkazem (referencí), který se používá pro složitější datové typy (instance tříd). Při tomto předání není kopírován obsah celého objektu, ale pouze odkaz na vyhrazené místo v paměti, kde se jednotlivá data objektu nacházejí. Je dobré si uvědomit, že vlastně dochází pouze k předání (a kopírování) paměťové adresy, kde se objekt nachází, lokálně tak ve funkci vzniká nová proměnná typu reference, která však ukazuje na identické místo v paměti. Můžeme tak měnit obsah objektů, který takto do funkce vstupuje, ale také můžeme do této lokální proměnné (reference) přiřadit adresu jiného objektu, aniž bychom pak ovlivňovali stav předchozího objektu. To je demonstrováno na následujícím příkladu:
public void print1(StringBuilder str) {
System.out.println("Print1: Input string is " + str);
str.append(", CTU in Prague");
System.out.println("Print1: Now, the str is " + str);
}
public void print2(StringBuilder str) {
System.out.println("Print2: Input string is " + str);
str = new StringBuilder("CVUT"); //here, we create a new string and add the reference to the local variable str
System.out.println("Print2: Now, the str is " + str);
}
public void printStrings(String[] args) {
StringBuilder str = new StringBuilder("FEE");
print1(str);
System.out.println("Main: str is " + str);
print2(str);
System.out.println("Main: after calling print2 str is still " + str);
}
Očekávaný výstup programu je:
Print1: Input string is FEE
Print1: Now, the str is FEE, CTU in Prague
Main: str is FEE, CTU in Prague
Print2: Input string is FEE, CTU in Prague
Print2: Now, the str is CVUT
Main: after calling print2 str is still FEE, CTU in Pragu
Obecně se při psaní programů snažíme vyvarovat přímému zápisu konkrétních "magických" hodnot ve zdrojovém kódu a raději je volíme jako parametry funkcí či atributy tříd/objektů. Jedním z důvodu je čitelnost kódu, ale také jeho znovupožitelnost, neboť parametry můžeme nastavit vně funkce (metody) a můžeme tak metodu přímo použít, aniž bychom byli nuceni zasahovat přímo do jejího zdrojového kódu.
==== Předávání výstupních hodnot z funkcí ====
Pro výstupní hodnoty funkce slouží primárně návratový typ funkce, který se specifikuje v definici funkce jménem typu před jménem funkce. Například pro funkci ''isNumber'' s návratovou hodnotu typu ''boolean'' lze použít následující deklaraci hlavičky funkce.
public boolean isNumber(String str) {
...
return true;
}
Pro návratovou hodnotou je však nutné uvést v těle (definici) funkce klíčové slovo ''return'' s uvedením hodnoty nebo proměnné (reference). I zde dochází ke kopírování obsahu proměnné, tj. předání hodnoty základního typu nebo předání hodnoty reference pro proměnné typu objekt (dochází ke kopírování adresy objektu).
Jedním z dobrých zvyků při psaní programu je uvádět ve funkci pouze jediný ''return'' a to pokud možno na konci. To nejen zvyšuje čitelnost kódu, ale také umožňuje například vložení dodatečného testu na návratovou hodnotou. Zejména v bezpečnostních a kritických ("//safety//") aplikacích je tento požadavek zpravidla součástí kódovací konvence.
Je-li dovoleno používat ve funkci více příkazů ''return'', využíváme jej zpravidla pro předčasný návrat z volání funkce, pokud nejsou splněny vstupní předpoklady, např. hodnoty argumentů nejsou v požadovaných mezích
void print(String[] strs) {
if (strs == null || strs.length == 0) {
return;
}
...
return;
}
Požadujeme-li aby funkce vracela více hodnot nebo předávala více proměnných je nutné použít jako návratový typ složitější objekt, např. kontejner typu ''ArrayList''. Lze také využít předávání argumentů odkazem, které jsou následně ve funkci vyplněny, ale tento způsob je běžný spíše v jazycích C/C++.
===== Průběh cvičení =====
Náplní cvičení je vyzkoušet si předávání vstupních parametrů a výstupních hodnot funkcí, které kromě kratičkých demonstračních kódů je dále použito v řešení načítání a zpracování dat. Hlavní náplní cvičení je tak seznámit se s poskytnutým projektem a realizací jednoduchého programu pro zpracování textového záznamu z průběhu několika experimentů, ve kterém je každý experiment dokumentován jedním řádkem, ve kterém je informace o metodě, hodnotě parametru ''MNE'' a dvou hodnotách studovaných proměnných ''DISTANCE'' a ''CPUTIME''. Jedná se o konrétní data z průběhu experimentů robotického průzkumu, ve kterém studujeme vliv parametru ''MNE'' na rychlost průzkumu a požadovaný výpočetní čas. Parameter ''MNE'' udává jak často je prováděno plánování, které se skládá z generování cílů a jejich přidělení mobilním robotům provádějícím průzkum. Vliv tohoto parametru analyzujeme pro čtyři metody přidělování cílů robotům, které jsou označeny ''GA'', ''IA'', ''HA'' a ''MA''. Ukázku experimentálních mobilních robotů při exploraci a také porovnání exploračních cest pro plánovací metody HA a MA jsou zobrazeny na přiložených obrázcích.
{{courses:A0B36PR1:tutorials:07:exploration.jpg|}} {{courses:A0B36PR1:tutorials:07:mre.jpg|}}
Bližší informace problému, použitích metodách a vyhodnocení exploračních strategii lze nalézt na:
* [[http://robotics.fel.cvut.cz/jf/pr1/demo/mre.avi]]
* [[http://robotics.fel.cvut.cz/jf/pr1/demo/jh-ha_ma-r3-l3.avi]]
* V článku: "Jan Faigl, Miroslav Kulich, Libor Preucil: //Goal assignment using distance cost in multi-robot exploration//. IROS 2012: 3741-374", který je přístupný z domény ČVUT na [[http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6385660]]
Průběh experimentu je ovlivněn mnoha faktory jako je schopnost lokalizace robotu, a proto lze považovat měrené hodnoty délky nejdelší explorační cesty robotu a potřebný procesorový čas za náhodné veličiny a měření je opakováno pro několik běhu se stejnými parametry. Výsledky jednotlivých běhů jsou uloženy jako zaznámy v souboru, kde na každém řádku je uloženo jedno měření pro konkrétní plánovací metodu a parametr ''MNE''. Soubor tak má strukturu:
METHOD:ia;MNE:10;DISTANCE:61.1212;CPUTIME:116.25
METHOD:ha;MNE:10;DISTANCE:62.1952;CPUTIME:119.09
METHOD:ia;MNE:11;DISTANCE:58.7025;CPUTIME:132.73
Ukázkový vstupní soubor lze najít v ''dat/results-jh-l3r5.log''. Pro vyhodnocení a porovnání jednotlivých metod nás zajímají průměrné hodnoty DISTANCE a CPUTIME v závislosti na parametru MNE v rozsahu 5 až 20 pro každou plánovací metodu. Proto chceme tento soubor zpracovat do podoby přehledové tabulky průměrných hodnot:
MNE ga ia ha
5 77,6 63,9 58,1
6 71,3 60,3 55,8
7 69,4 55,6 56,1
8 70,8 60,8 56,7
9 75,2 59,4 58,5
10 71,2 62,8 60,1
11 70,1 64,0 59,4
12 71,8 61,1 58,6
13 69,2 62,2 60,3
14 73,2 62,9 60,7
15 70,0 62,3 59,9
16 73,3 63,2 62,5
17 75,8 65,3 61,0
18 72,6 62,4 62,3
19 74,4 69,0 64,2
20 73,2 67,4 65,8
Tabulka obsahuje čtyři sloupce oddělené tabulátorem (znak '\t') a na prvním řádku jsou uvedeny jmena sloupců. Tuto tabulku získáme spuštěním výchozího projektu, který ji při načtení vstupního souboru výpíše na standardní výstup. Program můžeme spustit z příkazové řádky voláním:
java -jar dist/lab07.jar
které vytvoří datový soubor ''jh-l3r5-avg.dat'' v aktuálním pracovním adresáři. Ten můžeme dále použít pro vykreslení grafu hodnot použitím dodaného skriptu voláním:
./scripts/graph_3methods_distance_avg_only.sh jh-l3r5-avg.dat jh-l3r5-avg.pdf
který vygeneruje PDF soubor zobrazující průběh vzdálenosti pro jednolivé plánovací metody v závislosti na parametru ''MNE'', který je v grafu označen jak s_max.
{{courses:A0B36PR1:tutorials:07:graph_distance.png|}}
Graf je vytvořen programem R [[http://www.r-project.org/]] a dodaný skript vytváří předpis (program) pro vytvoření grafu, který je přesměrován ze standardního výstupu skriptu do programu R.
V závislosti na lokálním nastavení může vytvářený datový souboru obsahovat znak '.' nebo ',' jako oddělovač desetinné části. Skript pro vytvoření grafu obsahuje jednoduchou detekci nastavení národního prostředí a předává oddělovač program R. Proto například v případě souboru s desetinnou tečkou a spuštěním skriptu v českém prostředí skončí volání skriptu chybou.
===== Úkoly na cvičení =====
- Procvičte si předávání parametrů a návratových hodnot diskutované výše. K implementaci využijte třídu Demo.java
- Seznamte se s poskytnutým programem, způsoby inicializace, voláním metod a předávání návratových hodnot.
- Vytvořte datový soubor ''jh-l3r5-avg.dat'' s tabulkou přesměrováním standarního výstupu programu.
- Vyzkoušejte si vygenerovat pdf soubor.
- Nahraďte výpis na standardní výstup použitím metod ''printToFile'' a ''appendToFile'' třídy ''TextIO'' a seznamte se s jejich implementací.
- Rozšiřte program o zpracování průměrných hodnot také pro metodu 'ma' a vytvořte tabulku o pěti sloupcích.
- Vykreslete graf pro tuto rozšířenou tabulku skriptem ''graph_4methods_distance_avg_only.sh'' a dále již uvažujte pouze zpracování hodnot všech čtyř metod.
- Modifikujte program pro vytvoření tabulky s průměrnou hodnotou ''CPUTIME'' a vykreslení grafu skriptem ''graph_4methods_cpu_avg_only.sh''
- Modifikujte program tak, aby činnost programu bylo možné určit argumenty programu a zároveň byly zachovány všechny funkcionality (inspirujte se příklady z předchozích cvičení).
Až budete mít hotové všechny úkoly ze cvičení (zpracování argumentů programu) i domácí úkol, můžete vyzkoušet zbylé skripty, které zpracovávají i tabulku směrodatných odchylek:
--- Příklad volání programu pro vytvoření souborů s tabulkami
ant jar
java -jar dist/lab07.jar dat/results-jh-l3r5.log DISTANCE jh-l3r5-avg.dat jh-l3r5-sd.dat
java -jar dist/lab07.jar dat/results-jh-l3r5.log CPUTIME jh-l3r5-ctime-avg.dat jh-l3r5-ctime-sd.dat
--- Příklad vytvoření PDF souborů s grafy a jejich zobrazaní programem xpdf
./scripts/graph_4methods_distance.sh jh-l3r5-avg.dat jh-l3r5-sd.dat jh-l3r5-distance.pdf
./scripts/graph_4methods_cpu.sh jh-l3r5-ctime-avg.dat jh-l3r5-ctime-sd.dat jh-l3r5-ctime.pdf
xpdf jh-l3r5-distance.pdf &
xpdf jh-l3r5-ctime.pdf &
===== Domácí úkol =====
[[courses:a0b36pr1:hw:lab07|]]