Rekurze

Rekurze (angl. recursion) je programátorská technika, kdy funkce volá sebe sama.

Taková rekurze skončí nekonečným voláním. Když zadáš tento program:

def rekurzivni_funkce():
    vysledek = ...
    rekurzivni_funkce()
    return vysledek

rekurzivni_funkce()

Jak to funguje?

  • Python si nadefinuje funkci rekurzivni_funkce
  • Zavolá funkci rekurzivni_funkce:
    • Vypočítá výsledek
    • Zavolá funkci rekurzivni_funkce:
      • Vypočítá výsledek
      • Zavolá funkci rekurzivni_funkce:
        • Vypočítá výsledek
        • Zavolá funkci rekurzivni_funkce:
          • Vypočítá výsledek
          • Zavolá funkci rekurzivni_funkce:
            • ...
              • ... po stovkách opakování si Python všimne, že tohle asi nikam nevede, a skončí s chybou.

Tomu odpovídá chybová hláška:

Traceback (most recent call last):
  File "/tmp/ukazka.py", line 4, in <module>
    rekurzivni_funkce()
  File "/tmp/ukazka.py", line 2, in rekurzivni_funkce
    return rekurzivni_funkce()
  File "/tmp/ukazka.py", line 2, in rekurzivni_funkce
    return rekurzivni_funkce()
  File "/tmp/ukazka.py", line 2, in rekurzivni_funkce
    return rekurzivni_funkce()
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

Hláška je zkrácená – dva řádky by se správně měly opakovat 999×, ale novější verze Pythonu je vypíšou jen třikrát.

Kontrolované zanoření

Jak rekurzi využít v praxi? Jeden způsob je si počítat, kolikrát se ještě „zanořit“.

Představ si potapěče, který prozkoumává mořské hlubiny následujícím způsobem:

  • Jak „prozkoumat moře“ v určité hloubce:
    • Porozhlédnu se kolem
    • Jsem-li už teď moc hluboko, kašlu na to; nebudu prozkoumávat dál.
    • Jinak:
      • Zanořím se o 10 m níž
      • Prozkoumám moře v nové hloubce
      • Zase se o 10 m vynořím

Neboli v Pythonu:

def pruzkum(hloubka):
    print(f'Zkoumám v hloubce {hloubka} m')

    if hloubka >= 30:
        print('Už toho bylo dost!')
    else:
        print(f'Zanořuju se (z {hloubka} m)')

        pruzkum(hloubka + 10)

        print(f'Vynořuju se (na {hloubka} m)')

pruzkum(0)
  • Python si nadefinuje funkci pruzkum
  • Zavolá funkci pruzkum s hloubkou 0:
    • Vypíše Rozhlížím se v hloubce 0 m
    • Zkontroluje, že 0 ≥ 30 (což neplatí)
    • Vypíše Zanořuju se (z 0 m)
    • Zavolá funkci pruzkum s hloubkou 10 m:
      • Vypíše Rozhlížím se v hloubce 10 m
      • Zkontroluje, že 10 ≥ 30 (což neplatí)
      • Vypíše Zanořuju se (na 10 m)
      • Zavolá funkci pruzkum s hloubkou 20 m:
        • Zkontroluje, že 20 ≥ 30 (což neplatí)
        • Vypíše Zanořuju se (na 20 m)
          • Zavolá funkci pruzkum s hloubkou 30 m:
            • Zkontroluje, že 30 ≥ 30 (což platí! konečně!)
              • Vypíše Už toho bylo dost! a skončí
        • Vypíše Vynořuju se (na 20 m)
      • Vypíše Vynořuju se (na 10 m)
    • Vypíše Vynořuju se (na 0 m)

Lokální proměnné

Na předchozím příkladu je vidět zajímavé chování lokálních proměnných. Když je potapěč „na dně“, jakou hodnotu má proměnná hloubka?

Tahle otázka je chyták. Která proměnná hloubka? Když je program „na dně“, existují čtyři různé lokální proměnné hloubka: každé zanoření, každé zavolání funkce pruzkum, má vlastní proměnnou.

Podobně jako když máš globální a lokální proměnnou stejného jména, každá funkce „vidí“ jen tu svoji proměnnou. Ale když se potapěč vynoří a volání funkce se ukončí, tato proměnná přestane existovat. A „volající“ funkce vidí svoji proměnnou, ve které je stále původní hodnota.

Pro matematiky

Nemáš-li ráda matematiku, tuhle sekci přeskoč!

Rekurzivní algoritmy mají původ v matematice. Faktoriál x, neboli součin všech čísel od 1 do x, zapsaný jako x!, matematici definují takto:

  • 0! = 1
  • Pro kladná x je x! = x · (x - 1)!

Neboli v Pythonu:

def factorial(x):
    if x == 0:
        return 1
    elif x > 0:
        return x * factorial(x - 1)

print(factorial(5))
print(1 * 2 * 3 * 4 * 5)
{
  "data": {
    "sessionMaterial": {
      "id": "session-material:2019/brno-podzim-pondeli:file:4",
      "title": "Rekurze (bonus)",
      "html": "\n          \n    \n\n    <h1>Rekurze</h1>\n<p><em>Rekurze</em> (angl. <em>recursion</em>) je program&#xE1;torsk&#xE1; technika,\nkdy funkce vol&#xE1; sebe sama.</p>\n<p>Takov&#xE1; rekurze skon&#x10D;&#xED; nekone&#x10D;n&#xFD;m vol&#xE1;n&#xED;m.\nKdy&#x17E; zad&#xE1;&#x161; tento program:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">rekurzivni_funkce</span><span class=\"p\">():</span>\n    <span class=\"n\">vysledek</span> <span class=\"o\">=</span> <span class=\"o\">...</span>\n    <span class=\"n\">rekurzivni_funkce</span><span class=\"p\">()</span>\n    <span class=\"k\">return</span> <span class=\"n\">vysledek</span>\n\n<span class=\"n\">rekurzivni_funkce</span><span class=\"p\">()</span>\n</pre></div><p>Jak to funguje?</p>\n<ul>\n<li>Python si nadefinuje funkci <code>rekurzivni_funkce</code></li>\n<li>Zavol&#xE1; funkci <code>rekurzivni_funkce</code>:<ul>\n<li>Vypo&#x10D;&#xED;t&#xE1; v&#xFD;sledek</li>\n<li>Zavol&#xE1; funkci <code>rekurzivni_funkce</code>:<ul>\n<li>Vypo&#x10D;&#xED;t&#xE1; v&#xFD;sledek</li>\n<li>Zavol&#xE1; funkci <code>rekurzivni_funkce</code>:<ul>\n<li>Vypo&#x10D;&#xED;t&#xE1; v&#xFD;sledek</li>\n<li>Zavol&#xE1; funkci <code>rekurzivni_funkce</code>:<ul>\n<li>Vypo&#x10D;&#xED;t&#xE1; v&#xFD;sledek</li>\n<li>Zavol&#xE1; funkci <code>rekurzivni_funkce</code>:<ul>\n<li>...<ul>\n<li><em> ...\n   </em> po stovk&#xE1;ch opakov&#xE1;n&#xED; si Python v&#x161;imne, &#x17E;e tohle asi\n     nikam nevede, a skon&#x10D;&#xED; s chybou.</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p>Tomu odpov&#xED;d&#xE1; chybov&#xE1; hl&#xE1;&#x161;ka:</p>\n<div class=\"highlight\"><pre><code>Traceback (most recent call last):\n  File &quot;/tmp/ukazka.py&quot;, line 4, in &lt;module&gt;\n    rekurzivni_funkce()\n  File &quot;/tmp/ukazka.py&quot;, line 2, in rekurzivni_funkce\n    return rekurzivni_funkce()\n  File &quot;/tmp/ukazka.py&quot;, line 2, in rekurzivni_funkce\n    return rekurzivni_funkce()\n  File &quot;/tmp/ukazka.py&quot;, line 2, in rekurzivni_funkce\n    return rekurzivni_funkce()\n  [Previous line repeated 996 more times]\nRecursionError: maximum recursion depth exceeded</code></pre></div><p>Hl&#xE1;&#x161;ka je zkr&#xE1;cen&#xE1; &#x2013; dva &#x159;&#xE1;dky by se spr&#xE1;vn&#x11B; m&#x11B;ly opakovat 999&#xD7;, ale nov&#x11B;j&#x161;&#xED;\nverze Pythonu je vyp&#xED;&#x161;ou jen t&#x159;ikr&#xE1;t.</p>\n<h1>Kontrolovan&#xE9; zano&#x159;en&#xED;</h1>\n<p>Jak rekurzi vyu&#x17E;&#xED;t v&#xA0;praxi?\nJeden zp&#x16F;sob je si po&#x10D;&#xED;tat, kolikr&#xE1;t se je&#x161;t&#x11B; &#x201E;zano&#x159;it&#x201C;.</p>\n<p>P&#x159;edstav si potap&#x11B;&#x10D;e, kter&#xFD; prozkoum&#xE1;v&#xE1; mo&#x159;sk&#xE9; hlubiny n&#xE1;sleduj&#xED;c&#xED;m zp&#x16F;sobem:</p>\n<ul>\n<li>Jak <em>&#x201E;prozkoumat mo&#x159;e&#x201C;</em> v&#xA0;ur&#x10D;it&#xE9; hloubce:<ul>\n<li>Porozhl&#xE9;dnu se kolem</li>\n<li>Jsem-li u&#x17E; te&#x10F; moc hluboko, ka&#x161;lu na to; nebudu prozkoum&#xE1;vat d&#xE1;l.</li>\n<li>Jinak:<ul>\n<li>Zano&#x159;&#xED;m se o 10 m n&#xED;&#x17E;</li>\n<li><em>Prozkoum&#xE1;m mo&#x159;e</em> v&#xA0;nov&#xE9; hloubce</li>\n<li>Zase se o 10 m vyno&#x159;&#xED;m</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p>Neboli v&#xA0;Pythonu:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">pruzkum</span><span class=\"p\">(</span><span class=\"n\">hloubka</span><span class=\"p\">):</span>\n    <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s1\">&apos;Zkoum&#xE1;m v hloubce {hloubka} m&apos;</span><span class=\"p\">)</span>\n\n    <span class=\"k\">if</span> <span class=\"n\">hloubka</span> <span class=\"o\">&gt;=</span> <span class=\"mi\">30</span><span class=\"p\">:</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"s1\">&apos;U&#x17E; toho bylo dost!&apos;</span><span class=\"p\">)</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s1\">&apos;Zano&#x159;uju se (z {hloubka} m)&apos;</span><span class=\"p\">)</span>\n\n        <span class=\"n\">pruzkum</span><span class=\"p\">(</span><span class=\"n\">hloubka</span> <span class=\"o\">+</span> <span class=\"mi\">10</span><span class=\"p\">)</span>\n\n        <span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"s1\">&apos;Vyno&#x159;uju se (na {hloubka} m)&apos;</span><span class=\"p\">)</span>\n\n<span class=\"n\">pruzkum</span><span class=\"p\">(</span><span class=\"mi\">0</span><span class=\"p\">)</span>\n</pre></div><ul>\n<li>Python si nadefinuje funkci <code>pruzkum</code></li>\n<li>Zavol&#xE1; funkci <code>pruzkum</code> s&#xA0;hloubkou 0:<ul>\n<li>Vyp&#xED;&#x161;e <code>Rozhl&#xED;&#x17E;&#xED;m se v hloubce 0 m</code></li>\n<li>Zkontroluje, &#x17E;e <code>0 &#x2265; 30</code> (co&#x17E; neplat&#xED;)</li>\n<li>Vyp&#xED;&#x161;e <code>Zano&#x159;uju se (z 0 m)</code></li>\n<li>Zavol&#xE1; funkci <code>pruzkum</code> s&#xA0;hloubkou 10 m:<ul>\n<li>Vyp&#xED;&#x161;e <code>Rozhl&#xED;&#x17E;&#xED;m se v hloubce 10 m</code></li>\n<li>Zkontroluje, &#x17E;e <code>10 &#x2265; 30</code> (co&#x17E; neplat&#xED;)</li>\n<li>Vyp&#xED;&#x161;e <code>Zano&#x159;uju se (na 10 m)</code></li>\n<li>Zavol&#xE1; funkci <code>pruzkum</code> s&#xA0;hloubkou 20 m:<ul>\n<li>Zkontroluje, &#x17E;e <code>20 &#x2265; 30</code> (co&#x17E; neplat&#xED;)</li>\n<li>Vyp&#xED;&#x161;e <code>Zano&#x159;uju se (na 20 m)</code><ul>\n<li>Zavol&#xE1; funkci <code>pruzkum</code> s&#xA0;hloubkou 30 m:<ul>\n<li>Zkontroluje, &#x17E;e <code>30 &#x2265; 30</code> (co&#x17E; plat&#xED;! kone&#x10D;n&#x11B;!)<ul>\n<li><em> Vyp&#xED;&#x161;e <code>U&#x17E; toho bylo dost!</code></em> a skon&#x10D;&#xED;</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>Vyp&#xED;&#x161;e <code>Vyno&#x159;uju se (na 20 m)</code></li>\n</ul>\n</li>\n<li>Vyp&#xED;&#x161;e <code>Vyno&#x159;uju se (na 10 m)</code></li>\n</ul>\n</li>\n<li>Vyp&#xED;&#x161;e <code>Vyno&#x159;uju se (na 0 m)</code></li>\n</ul>\n</li>\n</ul>\n<h1>Lok&#xE1;ln&#xED; prom&#x11B;nn&#xE9;</h1>\n<p>Na p&#x159;edchoz&#xED;m p&#x159;&#xED;kladu je vid&#x11B;t zaj&#xED;mav&#xE9; chov&#xE1;n&#xED; lok&#xE1;ln&#xED;ch prom&#x11B;nn&#xFD;ch.\nKdy&#x17E; je potap&#x11B;&#x10D; &#x201E;na dn&#x11B;&#x201C;, jakou hodnotu m&#xE1; prom&#x11B;nn&#xE1; <em>hloubka</em>?</p>\n<p>Tahle ot&#xE1;zka je chyt&#xE1;k. <em>Kter&#xE1;</em> prom&#x11B;nn&#xE1; <code>hloubka</code>?\nKdy&#x17E; je program &#x201E;na dn&#x11B;&#x201C;, existuj&#xED; &#x10D;ty&#x159;i <em>r&#x16F;zn&#xE9;</em> lok&#xE1;ln&#xED; prom&#x11B;nn&#xE9; <code>hloubka</code>:\nka&#x17E;d&#xE9; zano&#x159;en&#xED;, ka&#x17E;d&#xE9; zavol&#xE1;n&#xED; funkce <code>pruzkum</code>, m&#xE1; vlastn&#xED; prom&#x11B;nnou.</p>\n<p>Podobn&#x11B; jako kdy&#x17E; m&#xE1;&#x161; glob&#xE1;ln&#xED; a lok&#xE1;ln&#xED; prom&#x11B;nnou stejn&#xE9;ho jm&#xE9;na,\nka&#x17E;d&#xE1; funkce &#x201E;vid&#xED;&#x201C; jen tu svoji prom&#x11B;nnou.\nAle kdy&#x17E; se potap&#x11B;&#x10D; vyno&#x159;&#xED; a vol&#xE1;n&#xED; funkce se ukon&#x10D;&#xED;, tato prom&#x11B;nn&#xE1;\np&#x159;estane existovat.\nA &#x201E;volaj&#xED;c&#xED;&#x201C; funkce vid&#xED; svoji prom&#x11B;nnou, ve kter&#xE9; je st&#xE1;le p&#x16F;vodn&#xED; hodnota.</p>\n<h1>Pro matematiky</h1>\n<div class=\"admonition note\"><p>Nem&#xE1;&#x161;-li r&#xE1;da matematiku, tuhle sekci p&#x159;esko&#x10D;!</p>\n</div><p>Rekurzivn&#xED; algoritmy maj&#xED; p&#x16F;vod v matematice. Faktori&#xE1;l <var>x</var>, neboli\nsou&#x10D;in v&#x161;ech  &#x10D;&#xED;sel od 1 do <var>x</var>, zapsan&#xFD; jako <var>x</var>!,\nmatematici definuj&#xED; takto:</p>\n<ul>\n<li>0! = 1</li>\n<li>Pro kladn&#xE1; <var>x</var> je <var>x</var>! = <var>x</var> &#xB7; (<var>x</var> - 1)!</li>\n</ul>\n<p>Neboli v Pythonu:</p>\n<div class=\"highlight\"><pre><span></span><span class=\"k\">def</span> <span class=\"nf\">factorial</span><span class=\"p\">(</span><span class=\"n\">x</span><span class=\"p\">):</span>\n    <span class=\"k\">if</span> <span class=\"n\">x</span> <span class=\"o\">==</span> <span class=\"mi\">0</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"mi\">1</span>\n    <span class=\"k\">elif</span> <span class=\"n\">x</span> <span class=\"o\">&gt;</span> <span class=\"mi\">0</span><span class=\"p\">:</span>\n        <span class=\"k\">return</span> <span class=\"n\">x</span> <span class=\"o\">*</span> <span class=\"n\">factorial</span><span class=\"p\">(</span><span class=\"n\">x</span> <span class=\"o\">-</span> <span class=\"mi\">1</span><span class=\"p\">)</span>\n\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"n\">factorial</span><span class=\"p\">(</span><span class=\"mi\">5</span><span class=\"p\">))</span>\n<span class=\"k\">print</span><span class=\"p\">(</span><span class=\"mi\">1</span> <span class=\"o\">*</span> <span class=\"mi\">2</span> <span class=\"o\">*</span> <span class=\"mi\">3</span> <span class=\"o\">*</span> <span class=\"mi\">4</span> <span class=\"o\">*</span> <span class=\"mi\">5</span><span class=\"p\">)</span>\n</pre></div>\n\n\n        "
    }
  }
}