Skip to content

Commit

Permalink
docs: 📚 update README
Browse files Browse the repository at this point in the history
  • Loading branch information
maxbarsukov committed Oct 13, 2024
1 parent cbca70b commit accd689
Showing 1 changed file with 336 additions and 3 deletions.
339 changes: 336 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Markdown](https://github.com/maxbarsukov-itmo/functional-programming-2/actions/workflows/markdown.yml/badge.svg?branch=master)](https://github.com/maxbarsukov-itmo/functional-programming-2/actions/workflows/markdown.yml)
[![Coverage Status](https://coveralls.io/repos/github/maxbarsukov-itmo/functional-programming-2/badge.svg?branch=master)](https://coveralls.io/github/maxbarsukov-itmo/functional-programming-2?branch=master)

## Вариант: `rb-set`
## Вариант `rb-set`

<img alt="dancing" src="./.resources/anime.gif" height="240">

Expand All @@ -26,8 +26,341 @@

---

Интерфейс — *Red-Black Tree*, структура данных — *Set*.
## Требования

Интерфейс — `Set`, структура данных — `Red-Black Tree`.

1. Функции:
* [x] добавление и удаление элементов;
* [x] фильтрация;
* [x] отображение (`map`);
* [x] свертки (левая и правая);
* [x] структура должна быть [моноидом](https://ru.m.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BD%D0%BE%D0%B8%D0%B4).
2. Структуры данных должны быть **неизменяемыми**.
3. Библиотека должна быть протестирована в рамках **unit testing**.
4. Библиотека должна быть протестирована в рамках **property-based** тестирования (*как минимум 3 свойства*, включая свойства моноида).
5. Структура должна быть **полиморфной**.
6. Требуется использовать идиоматичный для технологии стиль программирования. Примечание: некоторые языки позволяют получить большую часть API через реализацию небольшого интерфейса. Так как лабораторная работа про ФП, а не про экосистему языка — необходимо реализовать их вручную и по возможности — обеспечить совместимость.

---

## Ключевые элементы реализации

Добавление, получение и удаление элементов:

```elixir
# in `rb_set.ex`

@spec put(t, any) :: t
def put(%RBSet{tree: tree}, element) do
%RBSet{tree: RedBlackTree.insert(tree, element, element)}
end

@spec delete(t, any) :: t
def delete(%RBSet{tree: tree}, element) do
%RBSet{tree: RedBlackTree.delete(tree, element)}
end

@spec member?(t, any) :: boolean
def member?(%RBSet{tree: tree}, element) do
RedBlackTree.has_key?(tree, element)
end


# in `red_black_tree.ex`

def insert(%RedBlackTree{root: nil} = tree, key, value) do
%RedBlackTree{tree | root: Node.new(key, value), size: 1}
end

def insert(%RedBlackTree{root: root, size: size, comparator: comparator} = tree, key, value) do
{nodes_added, new_root} = do_insert(root, key, value, 1, comparator)

%RedBlackTree{
tree
| root: make_node_black(new_root),
size: size + nodes_added
}
end

defp do_insert(nil, insert_key, insert_value, depth, _comparator) do
{
1,
%Node{
Node.new(insert_key, insert_value, depth)
| color: :red
}
}
end

defp do_insert(%Node{key: node_key} = node, insert_key, insert_value, depth, comparator) do
case comparator.(insert_key, node_key) do
0 -> {0, %Node{node | value: insert_value}}
-1 -> do_insert_left(node, insert_key, insert_value, depth, comparator)
1 -> do_insert_right(node, insert_key, insert_value, depth, comparator)
end
end

defp do_insert_left(%Node{left: left} = node, insert_key, insert_value, depth, comparator) do
{nodes_added, new_left} = do_insert(left, insert_key, insert_value, depth + 1, comparator)
{nodes_added, %Node{node | left: do_balance(new_left)}}
end

defp do_insert_right(%Node{right: right} = node, insert_key, insert_value, depth, comparator) do
{nodes_added, new_right} = do_insert(right, insert_key, insert_value, depth + 1, comparator)
{nodes_added, %Node{node | right: do_balance(new_right)}}
end

def delete(%RedBlackTree{root: root, size: size, comparator: comparator} = tree, key) do
{nodes_removed, new_root} = do_delete(root, key, comparator)

%RedBlackTree{
tree
| root: new_root,
size: size - nodes_removed
}
end

defp do_delete(nil, _key, _comparator) do
{0, nil}
end

defp do_delete(%Node{key: node_key} = node, delete_key, comparator) do
case comparator.(delete_key, node_key) do
0 -> do_delete_node(node)
-1 -> do_delete_left(node, delete_key, comparator)
1 -> do_delete_right(node, delete_key, comparator)
end
end

defp do_delete_node(%Node{left: left, right: right}) do
cond do
# If both the right and left are nil, the new tree is nil. For example,
# deleting A in the following tree results in B having no left
#
# B
# / \
# A C
#
left === nil && right === nil ->
{1, nil}

# If left is nil and there is a right, promote the right. For example,
# deleting C in the following tree results in B's right becoming D
#
# B
# / \
# A C
# \
# D
#
left === nil && right ->
{1, %Node{right | depth: right.depth - 1}}

# If there is only a left promote it. For example,
# deleting B in the following tree results in C's left becoming A
#
# C
# / \
# B D
# /
# A
#
left && right === nil ->
{1, %Node{left | depth: left.depth - 1}}

# If there are both left and right nodes, recursively promote the left-most
# nodes. For example, deleting E below results in the following:
#
# G => G
# / \ / \
# E H => C H
# / \ / \
# C F => B D
# / \ / \
# A D => A F
# \
# B
#
#
true ->
{
1,
do_balance(%Node{
left
| depth: left.depth - 1,
left: do_balance(promote(left)),
right: right
})
}
end
end

defp do_delete_left(%Node{left: left} = node, delete_key, comparator) do
{nodes_removed, new_left} = do_delete(left, delete_key, comparator)

{
nodes_removed,
%Node{
node
| left: do_balance(new_left)
}
}
end

defp do_delete_right(%Node{right: right} = node, delete_key, comparator) do
{nodes_removed, new_right} = do_delete(right, delete_key, comparator)

{
nodes_removed,
%Node{
node
| right: do_balance(new_right)
}
}
end


def get(%RedBlackTree{root: root, comparator: comparator}, key) do
do_get(root, key, comparator)
end

defp do_get(nil, _key, _comparator) do
nil
end

defp do_get(%Node{key: node_key, left: left, right: right, value: value}, get_key, comparator) do
case comparator.(get_key, node_key) do
0 -> value
-1 -> do_get(left, get_key, comparator)
1 -> do_get(right, get_key, comparator)
end
end
```

Фильтрация:

```elixir
# in `rb_set.ex`

@spec filter(t, (any -> boolean)) :: t
def filter(set, predicate) do
Enum.reduce(set, new(), fn element, acc ->
if predicate.(element) do
put(acc, element)
else
acc
end
end)
end
```

Отображение (`map`):

```elixir
# in `rb_set.ex`

@spec map(t, (any -> any)) :: t
def map(set, fun) do
Enum.reduce(set, new(), fn element, acc -> put(acc, fun.(element)) end)
end
```

Свертки (левая и правая):

```elixir
# in `rb_set.ex`

@spec reduce(t, any, any) :: any
def reduce(set, acc, fun), do: fold_right(set, acc, fun)
def fold_left(set, acc, fun), do: RedBlackTree.fold_left(RBSet.to_list(set), acc, fun)
def fold_right(set, acc, fun), do: RedBlackTree.fold_right(RBSet.to_list(set), acc, fun)


# in `red_black_tree.ex`

def fold_left(list, acc, fun), do: do_fold_left(list, acc, fun)
defp do_fold_left([], acc, _fun), do: acc

defp do_fold_left([head | tail], acc, fun) do
new_acc = fun.(acc, head)
do_fold_left(tail, new_acc, fun)
end

def fold_right(list, acc, fun), do: do_fold_right(list, acc, fun)
defp do_fold_right([], acc, _fun), do: acc

defp do_fold_right([head | tail], acc, fun) do
do_fold_right(tail, fun.(head, acc), fun)
end

def reduce(%RedBlackTree{root: nil}, acc, _fun), do: acc

def reduce(tree, acc, fun) do
to_list(tree) |> fold_right(acc, fun)
end
```

### Соответствие свойству [моноида]((https://ru.m.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BD%D0%BE%D0%B8%D0%B4))

Определили пустой элемент:

```elixir
# in `rb_set.ex`

@spec new() :: t
def new do
%RBSet{tree: RedBlackTree.new()}
end


# in `red_black_tree.ex`

def empty, do: new()
def new, do: %RedBlackTree{}
```

Определили бинарную операцию `union`:

```elixir
# in `rb_set.ex`

@spec union(t, t) :: t
def union(%RBSet{tree: tree1}, %RBSet{tree: tree2}) do
%RBSet{tree: RedBlackTree.union(tree1, tree2)}
end

@spec t ||| t :: t
def set1 ||| set2 do
union(set1, set2)
end


# in `red_black_tree.ex`

@spec union(t(), t()) :: t
def union(%RedBlackTree{root: nil} = _tree1, %RedBlackTree{} = tree2), do: tree2
def union(%RedBlackTree{} = tree1, %RedBlackTree{root: nil}), do: tree1
def union(%RedBlackTree{} = tree1, %RedBlackTree{} = tree2) do
RedBlackTree.reduce(tree1, tree2, fn {k, v}, acc -> RedBlackTree.insert(acc, k, v) end)
end
```

## Тестирование

В рамках данной работы были применены два инструмента:

* [ExUnit](https://hexdocs.pm/ex_unit/ExUnit.html) - для модульного тестирования;
* [Quixir](https://github.com/pragdave/quixir) - для тестирования свойств (property-based).

## Выводы

...
В данной лабораторной работе была реализована структура данных "Красно-чёрное дерево" (Red-Black Tree). Красно-чёрное дерево - это самобалансирующееся двоичное дерево поиска, которое обеспечивает эффективную вставку, удаление и поиск элементов.

В реализации были использованы следующие приёмы программирования:

* Использование модулей: для реализации красно-чёрного дерева был создан отдельный модуль `RedBlackTree`, а для реализации множества на основе красно-чёрного дерева - модуль `RBSet`.
* Использование структур данных: для представления узлов дерева была использована структура `Node`, а для представления самого дерева - структура `RedBlackTree`.
* Для реализации операций над деревом, таких как вставка, удаление и поиск, были использованы функции высшего порядка.
* Для реализации логики работы с деревом был использован pattern matching, который позволяет эффективно обрабатывать различные случаи и исключения.
* Для реализации операций над деревом, таких как обход дерева и поиск элементов, была использована рекурсия -- обычная и хвостовая.

0 comments on commit accd689

Please sign in to comment.