# Run as: iex --dot-iex path/to/notebook.exs
# Title: DP25a - 5. FP gyakorlat, 2025-10-21
# ── Bináris fák feldolgozása elágazó rekurzióval, 2. rész ──
# A feladatokhoz az alábbi tree adattípust definiáljuk:
#
# ```elixir
# @type tree() :: :leaf | {any(), tree(), tree()}
# ```
#
# Tehát egy tree() típusú Elixir-kifejezés
# * vagy egy adatot (a továbbiakban *címkének* nevezzük) tartalmazó *csomópont*, amely további két tree() típusú értéket tartalmaz: az első a bal, a második a jobb részfa;
# * vagy egy címke nélküli :leaf (azaz *levél)* atom.
# A példákban felhasznált változók és értékük:
#
# ```elixir
# t1 = {4,
# {3,:leaf,:leaf},
# {6,
# {5,:leaf,:leaf},
# {7,:leaf,:leaf}
# }
# }
# t2 = {:a,
# {:b, {:v,:leaf,:leaf}, :leaf},
# {:c,
# :leaf,
# {:d,
# {:w, {:x,:leaf,:leaf}, :leaf},
# {:f, {:x,:leaf,:leaf}, {:y,:leaf,:leaf}}
# }
# }
# }
# ```
#
# A típust és a két változó helyett a két függvényt definiáljuk a `T` modulban, hogy más modulokból hivatkozhassunk rájuk:
#
# ```elixir
# defmodule T do
# @type tree() :: :leaf | {any(), tree(), tree()}
# @type inttree() :: :leaf | {integer(), inttree(), inttree()}
# def t1() do
# {4, {3, :leaf, :leaf}, {6, {5, :leaf, :leaf}, {7, :leaf, :leaf}}}
# end
# def t2() do
# {:a, {:b, {:v, :leaf, :leaf}, :leaf},
# {:c, :leaf,
# {:d, {:w, {:x, :leaf, :leaf}, :leaf}, {:f, {:x, :leaf, :leaf}, {:y, :leaf, :leaf}}}}}
# end
# end
# IO.puts("t1 = " <> inspect(T.t1()))
# IO.puts("t2 = " <> inspect(T.t2()))
# ```
#
defmodule T do
@type tree() :: :leaf | {any(), tree(), tree()}
# @type inttree() :: :leaf | {integer(), inttree(), inttree()}
def t1() do
{4, {3, :leaf, :leaf}, {6, {5, :leaf, :leaf}, {7, :leaf, :leaf}}}
end
def t2() do
{:a, {:b, {:v, :leaf, :leaf}, :leaf},
{:c, :leaf,
{:d, {:w, {:x, :leaf, :leaf}, :leaf}, {:f, {:x, :leaf, :leaf}, {:y, :leaf, :leaf}}}}}
end
end
IO.puts("t1 = " <> inspect(T.t1()))
IO.puts("t2 = " <> inspect(T.t2()))
# #### T8. Bal és jobb szélső címkék visszaadása
# írjon egy-egy függvényt egy bináris fa bal, ill. jobb szélső címkéjének visszaadására!
#
# ```elixir
# defmodule MostLeftRight do
# @spec fa_balerteke(f::T.tree()) :: {:ok, c::any()} | :error
# # Egy nemüres f fa bal oldali szélső címkéje c (minden
# # felmenőjére is igaz, hogy bal oldali gyermek)
# # Ha nincs bal oldali szélső érték, az :error atomot adja vissza
# def fa_balerteke(f) do
# ...
# end
# @spec fa_jobberteke(f::T.tree()) :: {:ok, c::any()} | :error
# # Egy nemüres f fa jobb oldali szélső címkéje c (minden
# # felmenőjére is igaz, hogy jobb oldali gyermek)
# # Ha nincs jobb oldali szélső érték, az :error atomot adja vissza
# def fa_jobberteke(f) do
# ...
# end
# end
# (MostLeftRight.fa_balerteke(T.t1()) === {:ok, 3}) |> IO.inspect()
# (MostLeftRight.fa_balerteke(:leaf) === :error) |> IO.inspect()
# (MostLeftRight.fa_jobberteke(T.t1()) === {:ok, 7}) |> IO.inspect()
# (MostLeftRight.fa_jobberteke(:leaf) === :error) |> IO.inspect()
# ```
#
defmodule MostLeftRight do
@spec fa_balerteke(f :: T.tree()) :: {:ok, c :: any()} | :error
# Egy nemüres f fa bal oldali szélső címkéje c (minden
# felmenőjére is igaz, hogy bal oldali gyermek)
# Ha nincs bal oldali szélső érték, az :error atomot adja vissza
def fa_balerteke(:leaf), do: :error
def fa_balerteke({c, :leaf, _}), do: {:ok, c}
def fa_balerteke({_, bfa, _}), do: fa_balerteke(bfa)
@spec fa_jobberteke(f :: T.tree()) :: {:ok, c :: any()} | :error
# Egy nemüres f fa jobb oldali szélső címkéje c (minden
# felmenőjére is igaz, hogy jobb oldali gyermek)
# Ha nincs jobb oldali szélső érték, az :error atomot adja vissza
def fa_jobberteke(:leaf), do: :error
def fa_jobberteke({c, _, :leaf}), do: {:ok, c}
def fa_jobberteke({_, _, jfa}), do: fa_jobberteke(jfa)
end
(MostLeftRight.fa_balerteke(T.t1()) === {:ok, 3}) |> IO.inspect()
(MostLeftRight.fa_balerteke(:leaf) === :error) |> IO.inspect()
(MostLeftRight.fa_jobberteke(T.t1()) === {:ok, 7}) |> IO.inspect()
(MostLeftRight.fa_jobberteke(:leaf) === :error) |> IO.inspect()
# #### T9. Bináris fa rendezettsége
# Egy bináris fa rendezett, ha *inorder* bejárásakor a címkéi *szigorúan monoton növekednek,* azaz a csomópontjai kielégítik a keresőfa-tulajdonságot: minden egyes csomópont címkéje nagyobb a bal oldali gyermekei címkéjénél és kisebb a jobb oldali gyermekei címkéjénél. Oldja meg a feladatot
# a MostLeftRight.fa_balerteke/1 és a MostLeftRight.fa_jobberteke/1 függvényekkel.
# Megírhatja a 4. gyakorlaton szerepelt Labels.cimkek/1, valamint az Enum.sort/1 vagy más függényekkel (pl. saját segédfüggénnyel) is.
#
# ```elixir
# defmodule Ordered do
# @spec rendezett(f::T.tree()) :: b::boolean()
# # b igaz, ha az f fa rendezett
# def rendezett(f) do
# ...
# end
# end
# (Ordered.rendezett(T.t1()) === true) |> IO.inspect()
# (Ordered.rendezett(T.t2()) === false) |> IO.inspect()
# ```
#
defmodule Ordered do
@spec rendezett(f :: T.tree()) :: b :: boolean()
# b igaz, ha az f fa rendezett
def rendezett(:leaf), do: true
def rendezett({c, bfa, jfa}) do
case MostLeftRight.fa_jobberteke(bfa) do
:error -> true
{:ok, j} -> j < c
end and
rendezett(bfa) and
case MostLeftRight.fa_balerteke(jfa) do
:error -> true
{:ok, b} -> c < b
end and
rendezett(jfa)
end
end
(Ordered.rendezett(T.t1()) === true) |> IO.inspect()
(Ordered.rendezett(T.t2()) === false) |> IO.inspect()
# #### T10. Bináris fa összes címkéjének útvonala
# Egy adott csomópont útvonalának nevezzük azon csomópontok címkéinek listáját, amelyeken át a fa gyökerétől az adott csomópontig el lehet jutni.
# Javasoljuk a Route.utak/2 segédfüggvény definiálását és használatát.
#
# ```elixir
# defmodule Route do
# @type route() :: [any()]
# @spec utak(f::T.tree()) :: cimkezett_utak::[{c::any(), cu::route()}]
# # A cimkezett_utak lista az f fa minden csomópontjához egy {c,cu} párt
# # társít, ahol c az adott csomópont címkéje, cu pedig az adott
# # csomóponthoz vezető útvonal
# def utak(f) do
# ...
# end
# @spec utak(f::T.tree(), eddigi_ut::route()) :: cimkezett_utak::[{c::any(), cu::route()}]
# # A cimkezett_utak lista az f fa minden csomópontjához egy {c,cu} párt
# # társít, ahol c az adott csomópont címkéje, cu pedig az adott
# # csomóponthoz vezető útvonal az eddigi_ut eddigi útvonal elé fűzve
# def utak(f) do
# ...
# end
# end
# (Route.utak(T.t1()) === [{4,[]},{3,[4]},{6,[4]},{5,[4,6]},{7,[4,6]}]) |> IO.inspect()
# (Route.utak(T.t2()) === [{:a,[]},
# {:b,[:a]},
# {:v,[:a,:b]},
# {:c,[:a]},
# {:d,[:a,:c]},
# {:w,[:a,:c,:d]},
# {:x,[:a,:c,:d,:w]},
# {:f,[:a,:c,:d]},
# {:x,[:a,:c,:d,:f]},
# {:y,[:a,:c,:d,:f]}
# ]) |> IO.inspect()
# ```
#
defmodule Route do
@type route() :: [any()]
@spec utak(f :: T.tree()) :: cimkezett_utak :: [{c :: any(), cu :: route()}]
# A cimkezett_utak lista az f fa minden csomópontjához egy {c,cu} párt
# társít, ahol c az adott csomópont címkéje, cu pedig az adott
# csomóponthoz vezető útvonal
def utak(fa), do: utak(fa, [])
@spec utak(f :: T.tree(), eddigi_ut :: route()) ::
cimkezett_utak :: [{c :: any(), cu :: route()}]
# A cimkezett_utak lista az f fa minden csomópontjához egy {c,cu} párt
# társít, ahol c az adott csomópont címkéje, cu pedig az adott
# csomóponthoz vezető útvonal az eddigi_ut eddigi útvonal elé fűzve
def utak(:leaf, _), do: []
def utak({c, bfa, jfa}, eddigi) do
# eddigi1 = eddigi ++ [c] # Költséges művelet: O(n2)!
# Kevésbé: O(2n)
eddigi1 = Enum.reverse([c | Enum.reverse(eddigi)])
[{c, eddigi} | utak(bfa, eddigi1)] ++ utak(jfa, eddigi1)
end
end
(Route.utak(T.t1()) === [{4, []}, {3, [4]}, {6, [4]}, {5, [4, 6]}, {7, [4, 6]}]) |> IO.inspect()
(Route.utak(T.t2()) === [
{:a, []},
{:b, [:a]},
{:v, [:a, :b]},
{:c, [:a]},
{:d, [:a, :c]},
{:w, [:a, :c, :d]},
{:x, [:a, :c, :d, :w]},
{:f, [:a, :c, :d]},
{:x, [:a, :c, :d, :f]},
{:y, [:a, :c, :d, :f]}
])
|> IO.inspect()
# #### T11. Adott címke összes előfordulása bináris fában útvonallal
# Oldja meg a feladatot
# 1. for-jelöléssel és a Route.utak/1 függvény felhasználásával;
# 2. takarékoskodva a memóriával, azaz az összes útvonal helyett csak a keresett útvonalakat tárolja el. (A megoldásban a Route.utak/2 függvényhez hasonló segédfüggvényt használjon, de a gyökér címkéjét csak egy bizonyos feltétel teljesülésekor tárolja el.)
#
# ```elixir
# defmodule Occurrences do
# @spec cutak(f::T.tree(), c::any()) :: utak::[{c::any(), cu::Route.route()}]
# # utak azon csomópontok útvonalainak listája f-ben, amelyek címkéje c
# def cutak(f) do
# ...
# end
# end
# (Occurrences.cutak(T.t1(), :x) === []) |> IO.inspect()
# (Occurrences.cutak(T.t2(), :x) === [{:x,[:a,:c,:d,:w]},{:x,[:a,:c,:d,:f]}]) |> IO.inspect()
# ```
#
defmodule Occurrences do
@spec cutak(f :: T.tree(), c :: any()) :: utak :: [{c :: any(), cu :: Route.route()}]
# utak azon csomópontok útvonalainak listája f-ben, amelyek címkéje c
def cutak(fa, c), do: for({c0, _} = cu <- Route.utak(fa), c0 === c, do: cu)
end
(Occurrences.cutak(T.t1(), :x) === []) |> IO.inspect()
(Occurrences.cutak(T.t2(), :x) === [{:x, [:a, :c, :d, :w]}, {:x, [:a, :c, :d, :f]}])
|> IO.inspect()
defmodule Occurrences2 do
@spec cutak(f :: T.tree(), c :: any()) :: utak :: [{c :: any(), cu :: Route.route()}]
# utak azon csomópontok útvonalainak listája f-ben, amelyek címkéje c
def cutak(fa, c), do: cutak(fa, c, [])
@spec cutak(f :: T.tree(), c :: any(), eddigi :: Route.route()) ::
cimkezett_utak :: [{c :: any(), u :: Route.route()}]
# A cimkezett_utak lista az f fa minden c címkéjű csomópontjához egy {c,u}
# párt társít, ahol c az adott csomópont címkéje, u pedig az adott
# csomóponthoz vezető útvonal az eddigi eddigi útvonal elé fűzve
def cutak(:leaf, _, _), do: []
def cutak({r, bfa, jfa}, c, eddigi) do
eddigi1 = eddigi ++ [r]
cutak = cutak(bfa, c, eddigi1) ++ cutak(jfa, c, eddigi1)
if r === c, do: [{c, eddigi} | cutak], else: cutak
end
end
(Occurrences2.cutak(T.t1(), :x) === []) |> IO.inspect()
(Occurrences2.cutak(T.t2(), :x) === [{:x, [:a, :c, :d, :w]}, {:x, [:a, :c, :d, :f]}])
|> IO.inspect()