Funktiot
Esimerkki epäsuorasta rekursiosta
Lukuohje
Jos et ymmärrä esimerkkiä, ei kannata huolestua.
Epäsuorassa rekursiossa funktio A kutsuu funktiota B, joka puolestaan kutsuu A:ta. Tässä tarkastellaan lausekkeen jäsentämistä ja evaluointia eli sen arvon laskemista. Jäsentämisessä tarkistetaan, että lauseke noudattaa annettua kielioppia.
Lausekkeen kielioppi
Lausekkeiden kielioppi voidaan kuvata näin:
<Expr> ::= <Term> + <Expr> | <Term> - <Expr> | <Term> <Term> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ( <Expr> )
Kielioppi on kirjoitettu BNF-muodossa (Backus–Naur Form). Välisymbolit (nonterminal) kirjoitetaan kulmasulkeisiin, ja loppusymbolit (terminal) kirjoitetaan sellaisinaan.
Kielioppi sallii yksinumeroiset luvut 0–9, operaattorit + ja - sekä sulkeet. Esimerkkejä kieliopin mukaisista lausekkeista:
2+3 2+3-4 1-(2+3) (2+3) ((2+3)) 1+(6-7)-(2+4+5+(2+3))
Jos lauseke noudattaa kielioppia, se voidaan johtaa kieliopin säännöistä soveltamalla niitä toistuvasti. Esimerkiksi 1 - (2 + 3) voidaan johtaa näin:
<Expr> -> <Term> - <Expr> -> 1 - <Expr> -> 1 - <Term> -> 1 - ( <Expr> ) -> 1 - ( <Term> + <Expr> ) -> 1 - ( 2 + <Expr> ) -> 1 - ( 2 + <Term> ) -> 1 - ( 2 + 3 )
Ohjelmakoodi
Tarkasteltava ohjelma löytyy hakemistosta examples/02/expression/. Se jäsentää annetun lausekkeen ja laskee sen arvon, jos lauseke noudattaa kielioppia.
Pääohjelman lisäksi ohjelmassa on kaksi funktiota, yksi kumpaakin kieliopin sääntöä varten: parse_term ja parse_expr. Funktiot kutsuvat toisiaan, joten kyseessä on epäsuora rekursio. Tämän vuoksi ainakin toinen funktioista täytyy esitellä ennen sen ensimmäistä kutsukohtaa.
parse_expr()
└─ kutsuu parse_term()
└─ sulkeiden kohdalla kutsuu parse_expr()
└─ kutsuu taas parse_term()
Rekursio näkyy myös kieliopissa: <Expr>-säännössä esiintyy <Term>, ja <Term>-säännössä esiintyy <Expr>.
Ohjelman funktiot on toteutettu suoraviivaisesti kieliopin perusteella:
parse_termtarkistaa ensin, onko termi yksinumeroinen luku. Jos termi alkaa vasemmalla sulkeella, funktio ohittaa sen, kutsuuparse_expr-funktiota ja ohittaa lopuksi oikean sulkeen.parse_exprkutsuu ensinparse_term-funktiota, koska lauseke alkaa termillä. Sen jälkeen se tarkistaa, jatkuuko lauseke plus- tai miinusoperaattorilla, ja laskee arvon sen mukaisesti.
Rekursion hyödyllisyydestä
Tämänkaltaista jäsentäjää olisi vaikea toteuttaa yhtä suoraviivaisesti ilman rekursiota. Rekursio sopii ongelmiin, joiden rakenne vastaa luontevasti pienempiä, samanmuotoisia osaongelmia. Ohjelmaa on myös helppo laajentaa: esimerkiksi kertolaskulle ja jakolaskulle voi lisätä kielioppisäännöt ja niitä vastaavan käsittelyn.