Skip to content

Commit

Permalink
🔨 feat(lib): LLRB tree & TreeDict -> deletion 🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
worthant committed Oct 19, 2024
1 parent 49c337c commit 60755d6
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 16 deletions.
188 changes: 172 additions & 16 deletions lib/red_black_tree.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule RedBlackTree do
right: t() | nil
}

# ОПЕРАЦИЯ ПОИСКА !!!
# LOOKUP OPERATION
@spec get(t() | nil, any()) :: any() | nil
def get(nil, _key), do: nil

Expand All @@ -31,7 +31,7 @@ defmodule RedBlackTree do
end
end

# ОПЕРАЦИЯ ВСТАВКА !!!
# INSERT OPERATION
@spec insert(t() | nil, any(), any()) :: t()
def insert(tree, key, value) do
tree
Expand All @@ -52,7 +52,7 @@ defmodule RedBlackTree do
key < k ->
%RedBlackTree{node | left: do_insert(node.left, key, value)}

key > k ->
true ->
%RedBlackTree{node | right: do_insert(node.right, key, value)}
end

Expand All @@ -65,27 +65,166 @@ defmodule RedBlackTree do

defp ensure_black_root(tree), do: tree

defp rotate_left(%RedBlackTree{right: right} = h) do
h = %RedBlackTree{h | right: right.left}
# DELETE OPERATION
@spec delete(t() | nil, any()) :: t() | nil
def delete(tree, key) do
if tree == nil do
nil
else
tree = do_delete(tree, key)

if tree != nil do
%RedBlackTree{tree | color: :black}
else
nil
end
end
end

defp do_delete(nil, _key), do: nil

defp do_delete(node, key) do

Check warning on line 86 in lib/red_black_tree.ex

View workflow job for this annotation

GitHub Actions / elixir_ci

Function is too complex (cyclomatic complexity is 13, max is 9).
node =
if key < node.key do
node =
if node.left != nil do
node =
if not red?(node.left) and not red?(node.left.left) do
move_red_left(node)
else
node
end

%RedBlackTree{node | left: do_delete(node.left, key)}
else
node
end

node
else
node =
if red?(node.left) do
rotate_right(node)
else
node
end

if key == node.key and node.right == nil do
# Node to delete found and has no right child
nil
else
node =
if node.right != nil do
node =
if not red?(node.right) and not red?(node.right.left) do
move_red_right(node)
else
node
end

if key == node.key do

Check warning on line 125 in lib/red_black_tree.ex

View workflow job for this annotation

GitHub Actions / elixir_ci

Function body is nested too deep (max depth is 2, was 4).
# Node to delete found
min_node = min(node.right)

%RedBlackTree{
node
| key: min_node.key,
value: min_node.value,
right: delete_min(node.right)
}
else
%RedBlackTree{node | right: do_delete(node.right, key)}
end
else
node
end

node
end
end

if node != nil do
balance(node)
else
nil
end
end

# MOVE RED LEFT
defp move_red_left(node) do
node = flip_colors(node)

node =
if node.right != nil and red?(node.right.left) do
node = %RedBlackTree{node | right: rotate_right(node.right)}
node = rotate_left(node)
flip_colors(node)
else
node
end

node
end

# MOVE RED RIGHT
defp move_red_right(node) do
node = flip_colors(node)

node =
if node.left != nil and red?(node.left.left) do
node = rotate_right(node)
flip_colors(node)
else
node
end

node
end

# FIND MINIMUM NODE
defp min(node) do
if node.left == nil do
node
else
min(node.left)
end
end

# DELETE MINIMUM NODE
defp delete_min(node) do
if node.left == nil do
nil
else
node =
if not red?(node.left) and not red?(node.left.left) do
move_red_left(node)
else
node
end

node = %RedBlackTree{node | left: delete_min(node.left)}
balance(node)
end
end

# ROTATIONS AND BALANCING
defp rotate_left(%RedBlackTree{right: right} = h) do
%RedBlackTree{
color: h.color,
key: right.key,
value: right.value,
left: %RedBlackTree{h | color: :red},
left: %RedBlackTree{h | right: right.left, color: :red},
right: right.right
}
end

defp rotate_right(%RedBlackTree{left: left} = h) do
h = %RedBlackTree{h | left: left.right}

%RedBlackTree{
color: h.color,
key: left.key,
value: left.value,
left: left.left,
right: %RedBlackTree{h | color: :red}
right: %RedBlackTree{h | left: left.right, color: :red}
}
end

Expand All @@ -99,13 +238,31 @@ defmodule RedBlackTree do
end

defp balance(node) do
node = if red?(node.right), do: rotate_left(node), else: node
node = if red?(node.left) and red?(node.left.left), do: rotate_right(node), else: node
node = if red?(node.left) and red?(node.right), do: flip_colors(node), else: node
node =
if red?(node.right) do
rotate_left(node)
else
node
end

node =
if red?(node.left) and red?(node.left.left) do
rotate_right(node)
else
node
end

node =
if red?(node.left) and red?(node.right) do
flip_colors(node)
else
node
end

node
end

# Вспомогательные функции
# HELPER FUNCTIONS
defp red?(%RedBlackTree{color: :red}), do: true
defp red?(_), do: false

Expand All @@ -118,7 +275,7 @@ defmodule RedBlackTree do
defp toggle_color(:red), do: :black
defp toggle_color(:black), do: :red

# ПРОВЕРЯЕМ ДЕРЕВО НА КОРРЕКТНОСТЬ
# VALIDATE RED-BLACK TREE PROPERTIES
@spec valid_red_black_tree?(t()) :: boolean()
def valid_red_black_tree?(tree) do
case check_properties(tree) do
Expand All @@ -130,11 +287,10 @@ defmodule RedBlackTree do
defp check_properties(nil), do: {:ok, 1}

defp check_properties(%RedBlackTree{color: color, left: left, right: right} = node) do
# Проверяем отсутствие последовательных красных узлов
# Check for consecutive red nodes
if red?(node) and (red?(left) or red?(right)) do
{:error, :red_red_violation}
else
# Рекурсивно проверяем левое и правое поддеревья
with {:ok, left_black_height} <- check_properties(left),
{:ok, right_black_height} <- check_properties(right),
true <- left_black_height == right_black_height do
Expand Down
6 changes: 6 additions & 0 deletions lib/tree_dict.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ defmodule TreeDict do
RedBlackTree.get(tree, key)
end

@spec delete(t(), any()) :: t()
def delete(%TreeDict{tree: tree} = dict, key) do
new_tree = RedBlackTree.delete(tree, key)
%TreeDict{dict | tree: new_tree}
end

# def insert(%TreeDict{tree: tree} = dict, key, value) do
# %TreeDict{tree: insert_into_tree(tree, key, value)}
# end
Expand Down
83 changes: 83 additions & 0 deletions test/red_black_tree_unit_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,87 @@ defmodule RedBlackTreeUnitTest do
# Проверяем, что дерево является корректным красно-чёрным деревом
assert RedBlackTree.valid_red_black_tree?(tree)
end

test "delete from empty tree returns nil" do
tree = nil
assert RedBlackTree.delete(tree, 10) == nil
end

test "delete non-existing key does not change tree" do
tree = RedBlackTree.insert(nil, 10, "value")
tree_after_delete = RedBlackTree.delete(tree, 20)
assert tree == tree_after_delete
end

test "delete leaf node" do
tree = nil
tree = RedBlackTree.insert(tree, 10, "value")
tree = RedBlackTree.insert(tree, 5, "value5")
tree = RedBlackTree.insert(tree, 15, "value15")

tree = RedBlackTree.delete(tree, 5)
assert RedBlackTree.get(tree, 5) == nil
assert RedBlackTree.valid_red_black_tree?(tree)
end

test "delete node with one child" do
tree = nil
tree = RedBlackTree.insert(tree, 10, "value")
tree = RedBlackTree.insert(tree, 5, "value5")
tree = RedBlackTree.insert(tree, 3, "value3")

tree = RedBlackTree.delete(tree, 5)
assert RedBlackTree.get(tree, 5) == nil
assert RedBlackTree.get(tree, 3) == "value3"
assert RedBlackTree.valid_red_black_tree?(tree)
end

test "delete node with two children" do
tree = nil
tree = RedBlackTree.insert(tree, 10, "value")
tree = RedBlackTree.insert(tree, 5, "value5")
tree = RedBlackTree.insert(tree, 15, "value15")
tree = RedBlackTree.insert(tree, 12, "value12")
tree = RedBlackTree.insert(tree, 18, "value18")

tree = RedBlackTree.delete(tree, 15)
assert RedBlackTree.get(tree, 15) == nil
assert RedBlackTree.get(tree, 12) == "value12"
assert RedBlackTree.get(tree, 18) == "value18"
assert RedBlackTree.valid_red_black_tree?(tree)
end

test "delete root node" do
tree = nil
tree = RedBlackTree.insert(tree, 10, "value")
tree = RedBlackTree.insert(tree, 5, "value5")
tree = RedBlackTree.insert(tree, 15, "value15")

tree = RedBlackTree.delete(tree, 10)
assert RedBlackTree.get(tree, 10) == nil
assert RedBlackTree.get(tree, 5) == "value5"
assert RedBlackTree.get(tree, 15) == "value15"
assert RedBlackTree.valid_red_black_tree?(tree)
end

test "delete multiple nodes" do
tree = nil
keys = Enum.shuffle(1..20)
tree = Enum.reduce(keys, tree, fn key, acc -> RedBlackTree.insert(acc, key, "value") end)

keys_to_delete = Enum.shuffle(5..15)
tree = Enum.reduce(keys_to_delete, tree, fn key, acc -> RedBlackTree.delete(acc, key) end)

Enum.each(keys_to_delete, fn key ->
assert RedBlackTree.get(tree, key) == nil
end)

remaining_keys = Enum.filter(1..20, fn key -> not Enum.member?(keys_to_delete, key) end)

Enum.each(remaining_keys, fn key ->
assert RedBlackTree.get(tree, key) == "value"
end)

assert RedBlackTree.valid_red_black_tree?(tree)
end
end
Loading

0 comments on commit 60755d6

Please sign in to comment.