# Fibonacci ```elixir Mix.install([ {:benchee, "~> 1.3"}, # {:benchee_html, "~> 1.0"}, # {:benchee_json, "~> 1.0"}, # {:kino_vega_lite, "~> 0.1.13"}, {:kino_explorer, "~> 0.1.25"}, {:arrays, "~> 2.1"} ]) ``` ## Fibonacci hét változatban A Fibonacci-számok jól ismert matematikai definíciója: $F_0 = 0$
$F_1 = 1$
$F_i = F_{i-2} + F_{i-1}$, ha $i > 1$ Naív rekurzív megoldásunk ezt követi. Az $i$-edik Fibonacci-szám meghatározása elágazó rekurzióval nagyon rossz hatékonyságú, mert a két elágazó ágat minden egyes rekurzív lépésben újra meg újra teljesen be kell járni, azaz az $i$-ediknél kisebb Fibonacci-számokat újra és újra ki kell számolni, ráadásul a részeredményeket az egyre mélyülő veremben kell tárolni. Fib.fib(5) kiszámításának folyamata: ![](files/fib5.png) ```elixir defmodule Fib do # Tree recursion # O(2^n) futási idő, O(2^n) tárhely @spec fib(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib(0), do: 0 def fib(1), do: 1 def fib(i), do: fib(i-1) + fib(i-2) end Fib.fib(23) #|> IO.inspect() ``` ``` 28657 ``` ```elixir defmodule FibM do # Memoization (top down) – dinamikus programozás # O(n) futási idő, O(n) tárhely @spec fib_mem(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_mem(i), do: fib_m(i, %{0 => 0, 1 => 1}) |> elem(0) @type mem() :: %{index :: integer() => value :: integer()} @spec fib_m(i :: integer(), mem :: mem()) :: {n :: integer(), uj_mem :: mem()} # n az i-edik Fibonacci-szám def fib_m(i, mem) do case mem[i] do # case nem váltható ki mintaillesztéssel nil -> {prev, memp} = fib_m(i-2, mem) {curr, memc} = fib_m(i-1, memp) val = prev + curr {val, Map.put(memc, i, prev+curr)} val -> {val, mem} end end end FibM.fib_mem(63) #|> IO.inspect() ``` ``` 6557470319842 ``` ```elixir defmodule FibT do # Tabulation (bottom-up) – dinamikus programozás # O(n) futási idő, O(n) tárhely @spec fib_tab(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_tab(i), do: fib_t(i, 2, %{0 => 0, 1 => 1}) @type tab() :: %{index :: integer() => value :: integer()} @spec fib_t(i :: integer(), j :: integer(), tab :: tab()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_t(i, j, tab) when i < j, do: tab[i] def fib_t(i, j, tab) do tab0 = Map.put(tab, j, tab[j-2] + tab[j-1]) fib_t(i, j+1, tab0) end end FibT.fib_tab(63) #|> IO.inspect() ``` ``` 6557470319842 ``` ```elixir defmodule FibAerl do # Tabulation (bottom-up) – dinamikus programozás # O(n) futási idő, O(n) tárhely # Erlang :array @spec fib_tab(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_tab(i), do: fib_t(i, 2, :array.set(1,1,(:array.set(0,0,:array.new())))) @type tab(integer) :: :array.arrayinteger(integer) @spec fib_t(i :: integer(), j :: integer(), tab :: tab(integer())) :: n :: integer() # n az i-edik Fibonacci-szám def fib_t(i, j, tab) when i < j, do: :array.get(i, tab) def fib_t(i, j, tab) do prev = :array.get(j-2, tab) curr = :array.get(j-1, tab) tab0 = :array.set(j, prev+curr, tab) fib_t(i, j+1, tab0) end end FibAerl.fib_tab(1023) #|> IO.inspect() ``` ``` 2785293550699592923938812412668093509353307352123703806913182668987369503203465183625616759613324452749958549669966882191117895425015208455469403731272652158240825628484818131485544230827304940519132195299466733282 ``` ```elixir defmodule X do @type array(int) :: :array.array(int) end ``` ``` {:module, X, <<70, 79, 82, 49, 0, 0, 5, ...>>, :ok} ``` ```elixir defmodule FibAex do # Tabulation (bottom-up) – dinamikus programozás # O(n) futási idő, O(n) tárhely # Elixir Array @spec fib_tab(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_tab(i), do: fib_t(i, 2, Arrays.new([0,1])) @type tab(integer) :: :array.arrayinteger(integer) @spec fib_t(i :: integer(), j :: integer(), tab :: tab(integer)) :: n :: integer() # n az i-edik Fibonacci-szám def fib_t(i, j, tab) when i < j, do: Arrays.get(tab, i) def fib_t(i, j, tab) do prev = Arrays.get(tab, j-2) curr = Arrays.get(tab, j-1) tab0 = Arrays.append(tab, prev+curr) fib_t(i, j+1, tab0) end end FibAex.fib_tab(1023) #|> IO.inspect() ``` ``` 2785293550699592923938812412668093509353307352123703806913182668987369503203465183625616759613324452749958549669966882191117895425015208455469403731272652158240825628484818131485544230827304940519132195299466733282 ``` ```elixir defmodule FibLtab do # Tabulation (bottom-up) – dinamikus programozás # O(n) futási idő, O(n) tárhely # Elixir List @spec fib_tab(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_tab(i), do: fib_t(i, 2, [1,0]) @type tab(integer) :: :array.arrayinteger(integer) @spec fib_t(i :: integer(), j :: integer(), tab :: tab(integer)) :: n :: integer() # n az i-edik Fibonacci-szám def fib_t(i, j, tab) when i < j, do: hd(tab) def fib_t(i, j, tab) do prev = hd(tl(tab)) curr = hd(tab) tab0 = [prev+curr | tab] fib_t(i, j+1, tab0) end end FibLtab.fib_tab(63) #|> IO.inspect() ``` ``` 6557470319842 ``` ```elixir defmodule FibI do # Space optimized (bottom up) # O(n) futási idő, O(1) tárhely @spec fib_iter(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_iter(i), do: fib_i(i, 1, 0) @spec fib_i(i :: integer(), curr :: integer(), prev :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám defp fib_i(0, _curr, prev), do: prev defp fib_i(1, curr, _prev), do: curr defp fib_i(i, curr, prev), do: fib_i(i-1, prev+curr, curr) end FibI.fib_iter(2203) #|> IO.inspect() ``` ``` 11227588022178051398070623745770537746981032161033283578641889149504371902547595733548949731279174036520553510211185291521165787504965947954328861725425894532117680897067684977042503589399040135697168277407633126059586479862184815462709569351240070274187436057121550393922337505846249722123756568019538289963931388811270535294468233234206275243288823876307712381776769983580371337794399152833220102956602421639379175057893229860412359902362848104779389231572677 ``` ```elixir Benchee.run( %{ "fib tree recursive" => fn -> Fib.fib(33) end, "fib memoization" => fn -> FibM.fib_mem(33) end, "fib tabulation" => fn -> FibT.fib_tab(33) end, "fib tabula_array_erl" => fn -> FibAerl.fib_tab(33) end, "fib tabula_array_ex" => fn -> FibAex.fib_tab(33) end, "fib tabula_list" => fn -> FibLtab.fib_tab(33) end, "fib iterative" => fn -> FibI.fib_iter(33) end }, # formatters: [ # Benchee.Formatters.Console, # {Benchee.Formatters.HTML, file: "fibonacci_bench.html"} # grafikonok # {Benchee.Formatters.JSON, file: "fibonacci_bench.json"} # opcionális # ], profile_after: false ) :ok ``` ``` Warning: the benchmark fib iterative is using an evaluated function. Evaluated functions perform slower than compiled functions. You can move the Benchee caller to a function in a module and invoke `Mod.fun()` instead. Alternatively, you can move the benchmark into a benchmark.exs file and run mix run benchmark.exs Warning: the benchmark fib memoization is using an evaluated function. Evaluated functions perform slower than compiled functions. You can move the Benchee caller to a function in a module and invoke `Mod.fun()` instead. Alternatively, you can move the benchmark into a benchmark.exs file and run mix run benchmark.exs Warning: the benchmark fib tabula_array_erl is using an evaluated function. Evaluated functions perform slower than compiled functions. You can move the Benchee caller to a function in a module and invoke `Mod.fun()` instead. Alternatively, you can move the benchmark into a benchmark.exs file and run mix run benchmark.exs Warning: the benchmark fib tabula_array_ex is using an evaluated function. Evaluated functions perform slower than compiled functions. You can move the Benchee caller to a function in a module and invoke `Mod.fun()` instead. Alternatively, you can move the benchmark into a benchmark.exs file and run mix run benchmark.exs Warning: the benchmark fib tabula_list is using an evaluated function. Evaluated functions perform slower than compiled functions. You can move the Benchee caller to a function in a module and invoke `Mod.fun()` instead. Alternatively, you can move the benchmark into a benchmark.exs file and run mix run benchmark.exs Warning: the benchmark fib tabulation is using an evaluated function. Evaluated functions perform slower than compiled functions. You can move the Benchee caller to a function in a module and invoke `Mod.fun()` instead. Alternatively, you can move the benchmark into a benchmark.exs file and run mix run benchmark.exs Warning: the benchmark fib tree recursive is using an evaluated function. Evaluated functions perform slower than compiled functions. You can move the Benchee caller to a function in a module and invoke `Mod.fun()` instead. Alternatively, you can move the benchmark into a benchmark.exs file and run mix run benchmark.exs Operating System: Linux CPU Information: Intel(R) Core(TM) i5-8365U CPU @ 1.60GHz Number of Available Cores: 8 Available memory: 38.81 GB Elixir 1.18.4 Erlang 28.0.1 JIT enabled: true Benchmark suite executing with the following configuration: warmup: 2 s time: 5 s memory time: 0 ns reduction time: 0 ns parallel: 1 inputs: none specified Estimated total run time: 49 s Benchmarking fib iterative ... Benchmarking fib memoization ... Benchmarking fib tabula_array_erl ... Benchmarking fib tabula_array_ex ... Benchmarking fib tabula_list ... Benchmarking fib tabulation ... Benchmarking fib tree recursive ... Calculating statistics... Formatting results... Name ips average deviation median 99th % fib iterative 1677.45 K 0.60 μs ±7934.35% 0.44 μs 0.89 μs fib tabula_list 1143.84 K 0.87 μs ±4153.56% 0.69 μs 1.51 μs fib tabula_array_erl 180.83 K 5.53 μs ±264.61% 4.97 μs 10.26 μs fib tabulation 157.79 K 6.34 μs ±215.84% 5.66 μs 12.96 μs fib memoization 147.87 K 6.76 μs ±215.94% 6.00 μs 13.66 μs fib tabula_array_ex 79.00 K 12.66 μs ±96.51% 11.56 μs 23.89 μs fib tree recursive 0.0185 K 53956.38 μs ±2.59% 53938.80 μs 58288.08 μs Comparison: fib iterative 1677.45 K fib tabula_list 1143.84 K - 1.47x slower +0.28 μs fib tabula_array_erl 180.83 K - 9.28x slower +4.93 μs fib tabulation 157.79 K - 10.63x slower +5.74 μs fib memoization 147.87 K - 11.34x slower +6.17 μs fib tabula_array_ex 79.00 K - 21.23x slower +12.06 μs fib tree recursive 0.0185 K - 90509.23x slower +53955.78 μs ``` ``` :ok ``` ```elixir # Módosított változat a memoizálási lépések követésére defmodule FibMm do @spec fib_mem(i :: integer()) :: mem :: %{integer() => integer()} def fib_mem(i), do: FibM.fib_m(i, %{0 => 0, 1 => 1}) |> elem(1) end FibMm.fib_mem(5) ``` ``` %{0 => 0, 1 => 1, 2 => 1, 3 => 2, 4 => 3, 5 => 5} ``` ```elixir # Interaktív bemenet (n slider); önálló cellába kell rakni cell = Kino.Input.number("Fibonacci index", default: 10, min: 0, max: 35) ``` ```elixir # Bemenet beolvasása index = cell |> IO.inspect(label: "Kino input cell") |> Kino.Input.read() |> IO.inspect(label: "Kino input read") ``` ``` Kino input cell: %Kino.Input{ ref: "p476tnlhyud6wikh4h7dsiw4yzqqrhot", id: "131674189", destination: {Kino.SubscriptionManager, :"livebook_x5hje53k--xgga5nyt@127.0.0.1"}, attrs: %{ default: 10, label: "Fibonacci index", type: :number, debounce: :blur } } Kino input read: 10 ``` ``` 10 ``` ```elixir # Táblázat mem = FibMm.fib_mem(index) Explorer.DataFrame.new(Enum.map(mem, fn {k, v} -> %{index: k, value: inspect(%{k => v})} end)) ``` ```elixir defmodule FibTdbg do # Tabulation (bottom-up) – dinamikus programozás # O(n) futási idő, O(n) tárhely @spec fib_tab(i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_tab(i), do: fib_t(%{0 => 0, 1 => 1}, 2, i) @type fib() :: %{index :: integer() => value :: integer()} @spec fib_t(mem :: fib(), j :: integer(), i :: integer()) :: n :: integer() # n az i-edik Fibonacci-szám def fib_t(tab, j, i) when j > i, do: tab[i] def fib_t(tab, j, i) do tab |> Map.put(j, tab[j-1] + tab[j-2]) |> fib_t(j+1, i) |> dbg() end end FibTdbg.fib_tab(8) #|> IO.inspect() ``` ``` 21 ```