Skip to content

Commit accd689

Browse files
committed
docs: 📚 update README
1 parent cbca70b commit accd689

File tree

1 file changed

+336
-3
lines changed

1 file changed

+336
-3
lines changed

README.md

Lines changed: 336 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![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)
55
[![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)
66

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

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

@@ -26,8 +26,341 @@
2626

2727
---
2828

29-
Интерфейс — *Red-Black Tree*, структура данных — *Set*.
29+
## Требования
30+
31+
Интерфейс — `Set`, структура данных — `Red-Black Tree`.
32+
33+
1. Функции:
34+
* [x] добавление и удаление элементов;
35+
* [x] фильтрация;
36+
* [x] отображение (`map`);
37+
* [x] свертки (левая и правая);
38+
* [x] структура должна быть [моноидом](https://ru.m.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BD%D0%BE%D0%B8%D0%B4).
39+
2. Структуры данных должны быть **неизменяемыми**.
40+
3. Библиотека должна быть протестирована в рамках **unit testing**.
41+
4. Библиотека должна быть протестирована в рамках **property-based** тестирования (*как минимум 3 свойства*, включая свойства моноида).
42+
5. Структура должна быть **полиморфной**.
43+
6. Требуется использовать идиоматичный для технологии стиль программирования. Примечание: некоторые языки позволяют получить большую часть API через реализацию небольшого интерфейса. Так как лабораторная работа про ФП, а не про экосистему языка — необходимо реализовать их вручную и по возможности — обеспечить совместимость.
44+
45+
---
46+
47+
## Ключевые элементы реализации
48+
49+
Добавление, получение и удаление элементов:
50+
51+
```elixir
52+
# in `rb_set.ex`
53+
54+
@spec put(t, any) :: t
55+
def put(%RBSet{tree: tree}, element) do
56+
%RBSet{tree: RedBlackTree.insert(tree, element, element)}
57+
end
58+
59+
@spec delete(t, any) :: t
60+
def delete(%RBSet{tree: tree}, element) do
61+
%RBSet{tree: RedBlackTree.delete(tree, element)}
62+
end
63+
64+
@spec member?(t, any) :: boolean
65+
def member?(%RBSet{tree: tree}, element) do
66+
RedBlackTree.has_key?(tree, element)
67+
end
68+
69+
70+
# in `red_black_tree.ex`
71+
72+
def insert(%RedBlackTree{root: nil} = tree, key, value) do
73+
%RedBlackTree{tree | root: Node.new(key, value), size: 1}
74+
end
75+
76+
def insert(%RedBlackTree{root: root, size: size, comparator: comparator} = tree, key, value) do
77+
{nodes_added, new_root} = do_insert(root, key, value, 1, comparator)
78+
79+
%RedBlackTree{
80+
tree
81+
| root: make_node_black(new_root),
82+
size: size + nodes_added
83+
}
84+
end
85+
86+
defp do_insert(nil, insert_key, insert_value, depth, _comparator) do
87+
{
88+
1,
89+
%Node{
90+
Node.new(insert_key, insert_value, depth)
91+
| color: :red
92+
}
93+
}
94+
end
95+
96+
defp do_insert(%Node{key: node_key} = node, insert_key, insert_value, depth, comparator) do
97+
case comparator.(insert_key, node_key) do
98+
0 -> {0, %Node{node | value: insert_value}}
99+
-1 -> do_insert_left(node, insert_key, insert_value, depth, comparator)
100+
1 -> do_insert_right(node, insert_key, insert_value, depth, comparator)
101+
end
102+
end
103+
104+
defp do_insert_left(%Node{left: left} = node, insert_key, insert_value, depth, comparator) do
105+
{nodes_added, new_left} = do_insert(left, insert_key, insert_value, depth + 1, comparator)
106+
{nodes_added, %Node{node | left: do_balance(new_left)}}
107+
end
108+
109+
defp do_insert_right(%Node{right: right} = node, insert_key, insert_value, depth, comparator) do
110+
{nodes_added, new_right} = do_insert(right, insert_key, insert_value, depth + 1, comparator)
111+
{nodes_added, %Node{node | right: do_balance(new_right)}}
112+
end
113+
114+
def delete(%RedBlackTree{root: root, size: size, comparator: comparator} = tree, key) do
115+
{nodes_removed, new_root} = do_delete(root, key, comparator)
116+
117+
%RedBlackTree{
118+
tree
119+
| root: new_root,
120+
size: size - nodes_removed
121+
}
122+
end
123+
124+
defp do_delete(nil, _key, _comparator) do
125+
{0, nil}
126+
end
127+
128+
defp do_delete(%Node{key: node_key} = node, delete_key, comparator) do
129+
case comparator.(delete_key, node_key) do
130+
0 -> do_delete_node(node)
131+
-1 -> do_delete_left(node, delete_key, comparator)
132+
1 -> do_delete_right(node, delete_key, comparator)
133+
end
134+
end
135+
136+
defp do_delete_node(%Node{left: left, right: right}) do
137+
cond do
138+
# If both the right and left are nil, the new tree is nil. For example,
139+
# deleting A in the following tree results in B having no left
140+
#
141+
# B
142+
# / \
143+
# A C
144+
#
145+
left === nil && right === nil ->
146+
{1, nil}
147+
148+
# If left is nil and there is a right, promote the right. For example,
149+
# deleting C in the following tree results in B's right becoming D
150+
#
151+
# B
152+
# / \
153+
# A C
154+
# \
155+
# D
156+
#
157+
left === nil && right ->
158+
{1, %Node{right | depth: right.depth - 1}}
159+
160+
# If there is only a left promote it. For example,
161+
# deleting B in the following tree results in C's left becoming A
162+
#
163+
# C
164+
# / \
165+
# B D
166+
# /
167+
# A
168+
#
169+
left && right === nil ->
170+
{1, %Node{left | depth: left.depth - 1}}
171+
172+
# If there are both left and right nodes, recursively promote the left-most
173+
# nodes. For example, deleting E below results in the following:
174+
#
175+
# G => G
176+
# / \ / \
177+
# E H => C H
178+
# / \ / \
179+
# C F => B D
180+
# / \ / \
181+
# A D => A F
182+
# \
183+
# B
184+
#
185+
#
186+
true ->
187+
{
188+
1,
189+
do_balance(%Node{
190+
left
191+
| depth: left.depth - 1,
192+
left: do_balance(promote(left)),
193+
right: right
194+
})
195+
}
196+
end
197+
end
198+
199+
defp do_delete_left(%Node{left: left} = node, delete_key, comparator) do
200+
{nodes_removed, new_left} = do_delete(left, delete_key, comparator)
201+
202+
{
203+
nodes_removed,
204+
%Node{
205+
node
206+
| left: do_balance(new_left)
207+
}
208+
}
209+
end
210+
211+
defp do_delete_right(%Node{right: right} = node, delete_key, comparator) do
212+
{nodes_removed, new_right} = do_delete(right, delete_key, comparator)
213+
214+
{
215+
nodes_removed,
216+
%Node{
217+
node
218+
| right: do_balance(new_right)
219+
}
220+
}
221+
end
222+
223+
224+
def get(%RedBlackTree{root: root, comparator: comparator}, key) do
225+
do_get(root, key, comparator)
226+
end
227+
228+
defp do_get(nil, _key, _comparator) do
229+
nil
230+
end
231+
232+
defp do_get(%Node{key: node_key, left: left, right: right, value: value}, get_key, comparator) do
233+
case comparator.(get_key, node_key) do
234+
0 -> value
235+
-1 -> do_get(left, get_key, comparator)
236+
1 -> do_get(right, get_key, comparator)
237+
end
238+
end
239+
```
240+
241+
Фильтрация:
242+
243+
```elixir
244+
# in `rb_set.ex`
245+
246+
@spec filter(t, (any -> boolean)) :: t
247+
def filter(set, predicate) do
248+
Enum.reduce(set, new(), fn element, acc ->
249+
if predicate.(element) do
250+
put(acc, element)
251+
else
252+
acc
253+
end
254+
end)
255+
end
256+
```
257+
258+
Отображение (`map`):
259+
260+
```elixir
261+
# in `rb_set.ex`
262+
263+
@spec map(t, (any -> any)) :: t
264+
def map(set, fun) do
265+
Enum.reduce(set, new(), fn element, acc -> put(acc, fun.(element)) end)
266+
end
267+
```
268+
269+
Свертки (левая и правая):
270+
271+
```elixir
272+
# in `rb_set.ex`
273+
274+
@spec reduce(t, any, any) :: any
275+
def reduce(set, acc, fun), do: fold_right(set, acc, fun)
276+
def fold_left(set, acc, fun), do: RedBlackTree.fold_left(RBSet.to_list(set), acc, fun)
277+
def fold_right(set, acc, fun), do: RedBlackTree.fold_right(RBSet.to_list(set), acc, fun)
278+
279+
280+
# in `red_black_tree.ex`
281+
282+
def fold_left(list, acc, fun), do: do_fold_left(list, acc, fun)
283+
defp do_fold_left([], acc, _fun), do: acc
284+
285+
defp do_fold_left([head | tail], acc, fun) do
286+
new_acc = fun.(acc, head)
287+
do_fold_left(tail, new_acc, fun)
288+
end
289+
290+
def fold_right(list, acc, fun), do: do_fold_right(list, acc, fun)
291+
defp do_fold_right([], acc, _fun), do: acc
292+
293+
defp do_fold_right([head | tail], acc, fun) do
294+
do_fold_right(tail, fun.(head, acc), fun)
295+
end
296+
297+
def reduce(%RedBlackTree{root: nil}, acc, _fun), do: acc
298+
299+
def reduce(tree, acc, fun) do
300+
to_list(tree) |> fold_right(acc, fun)
301+
end
302+
```
303+
304+
### Соответствие свойству [моноида]((https://ru.m.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BD%D0%BE%D0%B8%D0%B4))
305+
306+
Определили пустой элемент:
307+
308+
```elixir
309+
# in `rb_set.ex`
310+
311+
@spec new() :: t
312+
def new do
313+
%RBSet{tree: RedBlackTree.new()}
314+
end
315+
316+
317+
# in `red_black_tree.ex`
318+
319+
def empty, do: new()
320+
def new, do: %RedBlackTree{}
321+
```
322+
323+
Определили бинарную операцию `union`:
324+
325+
```elixir
326+
# in `rb_set.ex`
327+
328+
@spec union(t, t) :: t
329+
def union(%RBSet{tree: tree1}, %RBSet{tree: tree2}) do
330+
%RBSet{tree: RedBlackTree.union(tree1, tree2)}
331+
end
332+
333+
@spec t ||| t :: t
334+
def set1 ||| set2 do
335+
union(set1, set2)
336+
end
337+
338+
339+
# in `red_black_tree.ex`
340+
341+
@spec union(t(), t()) :: t
342+
def union(%RedBlackTree{root: nil} = _tree1, %RedBlackTree{} = tree2), do: tree2
343+
def union(%RedBlackTree{} = tree1, %RedBlackTree{root: nil}), do: tree1
344+
def union(%RedBlackTree{} = tree1, %RedBlackTree{} = tree2) do
345+
RedBlackTree.reduce(tree1, tree2, fn {k, v}, acc -> RedBlackTree.insert(acc, k, v) end)
346+
end
347+
```
348+
349+
## Тестирование
350+
351+
В рамках данной работы были применены два инструмента:
352+
353+
* [ExUnit](https://hexdocs.pm/ex_unit/ExUnit.html) - для модульного тестирования;
354+
* [Quixir](https://github.com/pragdave/quixir) - для тестирования свойств (property-based).
30355

31356
## Выводы
32357

33-
...
358+
В данной лабораторной работе была реализована структура данных "Красно-чёрное дерево" (Red-Black Tree). Красно-чёрное дерево - это самобалансирующееся двоичное дерево поиска, которое обеспечивает эффективную вставку, удаление и поиск элементов.
359+
360+
В реализации были использованы следующие приёмы программирования:
361+
362+
* Использование модулей: для реализации красно-чёрного дерева был создан отдельный модуль `RedBlackTree`, а для реализации множества на основе красно-чёрного дерева - модуль `RBSet`.
363+
* Использование структур данных: для представления узлов дерева была использована структура `Node`, а для представления самого дерева - структура `RedBlackTree`.
364+
* Для реализации операций над деревом, таких как вставка, удаление и поиск, были использованы функции высшего порядка.
365+
* Для реализации логики работы с деревом был использован pattern matching, который позволяет эффективно обрабатывать различные случаи и исключения.
366+
* Для реализации операций над деревом, таких как обход дерева и поиск элементов, была использована рекурсия -- обычная и хвостовая.

0 commit comments

Comments
 (0)