Current section
Files
Jump to
Current section
Files
lib/rb_tree/might_deletion.ex
defmodule A.RBTree.MightDeletion do
@moduledoc false
# Deletion algorithm as described in this article by Matt Might:
# http://matt.might.net/articles/red-black-delete/
# It involves temporary trees with more colors.
# Those should disappear once they have been rebalanced thoug to become regular red-black trees.
@typedoc """
:R -> red
:B -> black
:BB -> double black (temporary)
:NB -> negative black (temporary)
"""
@type tmp_color :: :R | :B | :BB | :NB
# empty | double black empty | tree
@type tmp_tree(elem) ::
:E | :EE | {tmp_color, tmp_tree(elem), elem, tmp_tree(elem)}
@type key :: term
@type value :: term
@type elem :: term
@type tmp_tree :: tmp_tree(elem)
@spec map_pop(A.RBTree.tree({k, v}), k) :: {:ok, v, A.RBTree.tree({k, v})} | :error
when k: key, v: value
def map_pop(root, key) do
case do_map_pop(root, key) do
:error -> :error
{:ok, value, new_root} -> {:ok, value, make_black(new_root)}
end
end
defp do_map_pop(:E, _key), do: :error
defp do_map_pop({color, a, {y, value} = yy, b} = tree, x) do
cond do
x < y ->
case map_pop(a, x) do
:error -> :error
{:ok, popped, new_a} -> {:ok, popped, bubble({color, new_a, yy, b})}
end
x > y ->
case map_pop(b, x) do
:error -> :error
{:ok, popped, new_b} -> {:ok, popped, bubble({color, a, yy, new_b})}
end
true ->
{:ok, value, remove(tree)}
end
end
# Private functions
defp remove(tree) do
case tree do
{:R, :E, _, :E} ->
:E
{:B, :E, _, :E} ->
:EE
{:B, :E, _, {:R, a, x, b}} ->
{:B, a, x, b}
{:B, {:R, a, x, b}, _, :E} ->
{:B, a, x, b}
{color, a, _x, b} ->
{:ok, max_elem} = A.RBTree.max(a)
new_a = remove_max(a)
bubble({color, new_a, max_elem, b})
end
end
defp remove_max({_color, _left, _elem, :E} = tree), do: remove(tree)
defp remove_max({color, left, elem, right}),
do: bubble({color, left, elem, remove_max(right)})
# will never redden empty tree
@spec make_red(tmp_tree(el)) :: tmp_tree(el) when el: elem
defp make_red({_color, l, x, r}), do: {:R, l, x, r}
@spec make_black(tmp_tree(el)) :: tmp_tree(el) when el: elem
defp make_black({_color, l, x, r}), do: {:B, l, x, r}
defp make_black(_empty), do: :E
@spec double_black?(tmp_tree) :: boolean
defp double_black?(:EE), do: true
defp double_black?({:BB, _, _, _}), do: true
defp double_black?(_), do: false
# double black cannot be any blacker
@spec blacker_color(tmp_color) :: tmp_color
defp blacker_color(:NB), do: :R
defp blacker_color(:R), do: :B
defp blacker_color(:B), do: :BB
# negative black cannot be any redder
@spec redder_color(tmp_color) :: tmp_color
defp redder_color(:BB), do: :B
defp redder_color(:B), do: :R
defp redder_color(:R), do: :NB
@spec make_redder(tmp_tree(el)) :: tmp_tree(el) when el: elem
defp make_redder(:EE), do: :E
defp make_redder({color, l, x, r}), do: {redder_color(color), l, x, r}
# probably less optimized but not sure about bubble
@spec balance_both(tmp_tree(el)) :: tmp_tree(el) when el: elem
defp balance_both(tree) do
case tree do
# original cases
{:B, {:R, {:R, a, x, b}, y, c}, z, d} ->
{:R, {:B, a, x, b}, y, {:B, c, z, d}}
{:B, {:R, a, x, {:R, b, y, c}}, z, d} ->
{:R, {:B, a, x, b}, y, {:B, c, z, d}}
{:B, a, x, {:R, {:R, b, y, c}, z, d}} ->
{:R, {:B, a, x, b}, y, {:B, c, z, d}}
{:B, a, x, {:R, b, y, {:R, c, z, d}}} ->
{:R, {:B, a, x, b}, y, {:B, c, z, d}}
# extra deletion cases
{:BB, {:R, {:R, a, x, b}, y, c}, z, d} ->
{:B, {:B, a, x, b}, y, {:B, c, z, d}}
{:BB, {:R, a, x, {:R, b, y, c}}, z, d} ->
{:B, {:B, a, x, b}, y, {:B, c, z, d}}
{:BB, {:NB, {:B, _, _, _} = a, x, {:B, b, y, c}}, z, d} ->
{:B, balance_both({:B, make_red(a), x, b}), y, {:B, c, z, d}}
# extra deletion cases
{:BB, a, x, {:R, {:R, b, y, c}, z, d}} ->
{:B, {:B, a, x, b}, y, {:B, c, z, d}}
{:BB, a, x, {:R, b, y, {:R, c, z, d}}} ->
{:B, {:B, a, x, b}, y, {:B, c, z, d}}
{:BB, a, x, {:NB, {:B, b, y, c}, z, {:B, _, _, _} = d}} ->
{:B, {:B, a, x, b}, y, balance_both({:B, c, z, make_red(d)})}
# default
balanced ->
balanced
end
end
@spec bubble(tmp_tree(el)) :: tmp_tree(el) when el: elem
defp bubble({color, l, x, r}) do
# TODO figure out if we can only balance left/right, deletion not optimized
# seems hard though when looking at:
# https://github.com/sweirich/dth/blob/master/examples/red-black/MightRedBlackGADT.hs
if double_black?(l) or double_black?(r) do
balance_both({blacker_color(color), make_redder(l), x, make_redder(r)})
else
balance_both({color, l, x, r})
end
end
end