Существует три функции эликсира, которые очень полезны при реализации рекурсивных решений:
- Соответствие шаблону параметра
- Несколько функциональных предложений
- Оптимизация Tail-Call
Вот пример решения, в котором используются первые две функции:
defmodule Tree do
defstruct [:left, :right]
def height(%Tree{left: nil, right: nil}), do: 0
def height(%Tree{left: nil, right: right}), do: 1 + height(right)
def height(%Tree{left: left, right: nil}), do: 1 + height(left)
def height(%Tree{left: left, right: right}), do: 1 + max(height(left), height(right))
end
Сначала определите структуру с именем Tree
, используя left
и right
для представления нашего дерева.
Затем мы определяем 4 предложения функции height
, которая использует сопоставление с образцом для проверки значений Tree
left
и right
.
- Если
left
и right
равны nil
, то мы лист и можем вернуть высоту 0
- Если один из дочерних элементов равен
nil
, то мы можем вернуть высоту дерева, отличного от нуля, плюс 1
для себя (текущий узел).
- То же, что и выше.
- В последнем пункте рассматривается случай, когда оба ребенка не равны нулю. В этом случае верните максимум роста двух детей плюс
1
для себя.
Обратите внимание, что стиль 1 + recursive_call()
приведет к стеку рекурсивных вызовов, потому что он должен отслеживать высоту дочерних узлов в стеке вызовов, чтобы наконец выполнить операцию 1 +
. Мы можем минимизировать это, передав высоту в качестве параметра-накопителя acc
в функцию и воспользовавшись преимуществом оптимизации хвостового вызова, которая позволяет компилятору уменьшить количество кадров стека, необходимых, когда функция является последним. делает, это называть себя. В этом случае нам все еще нужно вычислить max
из двух поддеревьев в последнем предложении, что означает, что мы не используем хвостовой вызов в большинстве случаев, но я включил его для полноты .:
def height_tailcall(%Tree{left: nil, right: nil}, acc), do: acc
def height_tailcall(%Tree{left: nil, right: right}, acc) do
height_tailcall(right, acc + 1)
end
def height_tailcall(%Tree{left: left, right: nil}, acc) do
height_tailcall(left, acc + 1)
end
def height_tailcall(%Tree{left: left, right: right}, acc) do
max(height_tailcall(left, acc + 1), height_tailcall(right, acc + 1))
end
Пример:
def example do
leaf = %Tree{}
height1 = %Tree{left: leaf, right: leaf}
height2 = %Tree{left: height1, right: height1}
height3 = %Tree{left: height2, right: height1}
height4 = %Tree{left: nil, right: height3}
IO.inspect(height(leaf), label: "leaf")
IO.inspect(height(height1), label: "height1")
IO.inspect(height(height2), label: "height2")
IO.inspect(height(height3), label: "height3")
IO.inspect(height(height4), label: "height4")
IO.inspect(height_tailcall(leaf, 0), label: "leaf (tail recursion)")
IO.inspect(height_tailcall(height1, 0), label: "height1 (tail recursion)")
IO.inspect(height_tailcall(height2, 0), label: "height2 (tail recursion)")
IO.inspect(height_tailcall(height3, 0), label: "height3 (tail recursion)")
IO.inspect(height_tailcall(height4, 0), label: "height4 (tail recursion)")
height4
end
Выход:
leaf: 0
height1: 1
height2: 2
height3: 3
height4: 4
leaf (tail recursion): 0
height1 (tail recursion): 1
height2 (tail recursion): 2
height3 (tail recursion): 3
height4 (tail recursion): 4
%Tree{
left: nil,
right: %Tree{
left: %Tree{
left: %Tree{
left: %Tree{left: nil, right: nil},
right: %Tree{left: nil, right: nil}
},
right: %Tree{
left: %Tree{left: nil, right: nil},
right: %Tree{left: nil, right: nil}
}
},
right: %Tree{
left: %Tree{left: nil, right: nil},
right: %Tree{left: nil, right: nil}
}
}
}