lab 2 complete v1
This commit is contained in:
+97
-85
@@ -4,7 +4,7 @@
|
||||
**Факультет программной инженерии и компьютерной техники**
|
||||
|
||||
**Студент:** Владимиров Владислав Александрович
|
||||
**Группа:** P3222
|
||||
**Группа:** P3322
|
||||
|
||||
**Тема:** Реализация Red-Black Tree с ленивыми вычислениями
|
||||
**Лабораторная работа №2**
|
||||
@@ -15,7 +15,28 @@
|
||||
## Требования к разработанному ПО
|
||||
|
||||
### Цель работы
|
||||
Освоиться с построением пользовательских типов данных, полиморфизмом, рекурсивными алгоритмами и средствами тестирования (unit testing, property-based testing), а также разделением интерфейса и особенностей реализации.
|
||||
Цель: освоиться с построением пользовательских типов данных, полиморфизмом, рекурсивными алгоритмами и средствами тестирования (unit testing, property-based testing), а также разделением интерфейса и особенностей реализации.
|
||||
|
||||
В рамках лабораторной работы вам предлагается реализовать одну из предложенных классических структур данных (список, дерево, бинарное дерево, hashmap, граф...).
|
||||
|
||||
Требования:
|
||||
|
||||
1. Функции:
|
||||
- добавление и удаление элементов;
|
||||
- фильтрация;
|
||||
- отображение (map);
|
||||
- свертки (левая и правая);
|
||||
- структура должна быть [моноидом](https://ru.m.wikipedia.org/wiki/Моноид).
|
||||
2. Структуры данных должны быть неизменяемыми.
|
||||
3. Библиотека должна быть протестирована в рамках unit testing.
|
||||
4. Библиотека должна быть протестирована в рамках property-based тестирования (как минимум 3 свойства, включая свойства моноида).
|
||||
5. Структура должна быть полиморфной.
|
||||
6. Требуется использовать идиоматичный для технологии стиль программирования. Примечание: некоторые языки позволяют получить большую часть API через реализацию небольшого интерфейса. Так как лабораторная работа про ФП, а не про экосистему языка -- необходимо реализовать их вручную и по возможности -- обеспечить совместимость.
|
||||
7. Обратите внимание:
|
||||
- API должно быть реализовано для заданного интерфейса и оно не должно "протекать". На уровне тестов -- в первую очередь нужно протестировать именно API (dict, set, bag).
|
||||
- Должна быть эффективная реализация функции сравнения (не наивное приведение к спискам, их сортировка с последующим сравнением), реализованная на уровне API, а не внутреннего представления.
|
||||
|
||||
|
||||
|
||||
### Вариант задания
|
||||
**Red-Black Tree Lazy** - красно-чёрное дерево на ленивых вычислениях.
|
||||
@@ -55,19 +76,19 @@
|
||||
### Основные типы данных
|
||||
|
||||
```gleam
|
||||
/// Цвета узлов красно-черного дерева
|
||||
// Цвета узлов красно-черного дерева
|
||||
pub type Color {
|
||||
Red
|
||||
Black
|
||||
}
|
||||
|
||||
/// Ленивое значение для отложенных вычислений
|
||||
// Ленивое значение для отложенных вычислений
|
||||
pub type Lazy(a) {
|
||||
Thunk(fn() -> a) // Отложенное вычисление
|
||||
Value(a) // Уже вычисленное значение
|
||||
}
|
||||
|
||||
/// Красно-черное дерево с ленивыми вычислениями
|
||||
// Красно-черное дерево с ленивыми вычислениями
|
||||
pub type RBTree(k, v) {
|
||||
Empty // Пустое дерево
|
||||
Node(
|
||||
@@ -83,7 +104,7 @@ pub type RBTree(k, v) {
|
||||
### Управление ленивыми вычислениями
|
||||
|
||||
```gleam
|
||||
/// Форсирует вычисление ленивого значения
|
||||
// Форсирует вычисление ленивого значения
|
||||
fn force(lazy: Lazy(a)) -> a {
|
||||
case lazy {
|
||||
Value(val) -> val
|
||||
@@ -91,7 +112,7 @@ fn force(lazy: Lazy(a)) -> a {
|
||||
}
|
||||
}
|
||||
|
||||
/// Создаёт ленивое значение из функции
|
||||
// Создаёт ленивое значение из функции
|
||||
fn delay(f: fn() -> a) -> Lazy(a) {
|
||||
Thunk(f)
|
||||
}
|
||||
@@ -171,6 +192,40 @@ n balance(
|
||||
|
||||
### Основные операции
|
||||
|
||||
**Главное - сравнение**
|
||||
```gleam
|
||||
// Вспомогательная функция для структурного сравнения деревьев
|
||||
// Использует замыкание при первом несовпадении
|
||||
fn equal(
|
||||
tree1: RBTree(k, v),
|
||||
tree2: RBTree(k, v),
|
||||
key_compare: fn(k, k) -> Bool,
|
||||
value_compare: fn(v, v) -> Bool,
|
||||
) -> Bool {
|
||||
case tree1, tree2 {
|
||||
// Оба дерева пустые - равны
|
||||
Empty, Empty -> True
|
||||
|
||||
Empty, _ -> False
|
||||
_, Empty -> False
|
||||
|
||||
Node(color1, key1, value1, left1, right1),
|
||||
Node(color2, key2, value2, left2, right2)
|
||||
-> {
|
||||
color1 == color2
|
||||
// Ключи должны быть равны
|
||||
&& key_compare(key1, key2)
|
||||
// Значения должны быть равны
|
||||
&& value_compare(value1, value2)
|
||||
// Рекурсивно проверяем левые поддеревья (ленивые)
|
||||
&& equal_helper(force(left1), force(left2), key_compare, value_compare)
|
||||
// Рекурсивно проверяем правые поддеревья (ленивые)
|
||||
&& equal_helper(force(right1), force(right2), key_compare, value_compare)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Вставка элемента:**
|
||||
```gleam
|
||||
pub fn insert(tree: RBTree(k, v), key: k, value: v,
|
||||
@@ -228,12 +283,12 @@ pub fn fold_left(tree: RBTree(k, v), acc: a, f: fn(a, k, v) -> a) -> a {
|
||||
### Операции моноида
|
||||
|
||||
```gleam
|
||||
/// Нейтральный элемент
|
||||
// Нейтральный элемент
|
||||
pub fn mempty() -> RBTree(k, v) {
|
||||
empty()
|
||||
}
|
||||
|
||||
/// Операция объединения
|
||||
// Операция объединения
|
||||
pub fn concat(tree1: RBTree(k, v), tree2: RBTree(k, v),
|
||||
compare: fn(k, k) -> Order) -> RBTree(k, v) {
|
||||
fold_left(tree2, tree1, fn(acc, key, value) {
|
||||
@@ -247,14 +302,19 @@ pub fn concat(tree1: RBTree(k, v), tree2: RBTree(k, v),
|
||||
## Тесты и метрики
|
||||
|
||||
### Unit тесты
|
||||
- `empty_tree_test()` - создание пустого дерева
|
||||
- `single_insert_test()` - вставка одного элемента
|
||||
- `multiple_insert_test()` - множественные вставки
|
||||
- `delete_test()` - удаление элементов
|
||||
- `filter_test()` - тестирование фильтрации
|
||||
- `map_test()` - тестирование отображения
|
||||
- `fold_left_test()`, `fold_right_test()` - тестирование свёрток
|
||||
- `to_from_list_test()` - конвертация в список и обратно
|
||||
- `empty_tree_test()` - создание пустого дерева и проверка `is_empty()`, `size()`
|
||||
- `single_insert_test()` - вставка одного элемента, проверка `lookup()` и размера
|
||||
- `multiple_insert_test()` - множественные вставки с проверкой всех элементов
|
||||
- `delete_test()` - удаление элементов с проверкой корректности
|
||||
- `update_existing_key_test()` - обновление существующих ключей
|
||||
- `filter_test()` - фильтрация четных чисел, проверка размера и содержимого
|
||||
- `map_test()` - отображение значений (умножение на 2), проверка корректности
|
||||
- `fold_left_test()` - левая свёртка для суммирования значений
|
||||
- `fold_right_test()` - правая свёртка для конкатенации строк
|
||||
- `to_from_list_test()` - конвертация дерево→список→дерево с проверкой эквивалентности
|
||||
- `red_black_invariant_test()` - работа с большим деревом (10 элементов), проверка балансировки
|
||||
- `semantic_equal_test()` - семантическое равенство (независимо от структуры)
|
||||
- `structure_equal_test()` - структурное равенство без учёта цветов узлов
|
||||
|
||||
### Property-based тесты
|
||||
|
||||
@@ -279,92 +339,44 @@ pub fn concat(tree1: RBTree(k, v), tree2: RBTree(k, v),
|
||||
### Тесты моноида
|
||||
|
||||
```gleam
|
||||
/// Левая единица: mempty ∘ a = a
|
||||
// Левая единица: mempty ∘ a = a
|
||||
pub fn monoid_left_identity_test()
|
||||
|
||||
/// Правая единица: a ∘ mempty = a
|
||||
// Правая единица: a ∘ mempty = a
|
||||
pub fn monoid_right_identity_test()
|
||||
|
||||
/// Ассоциативность: (a ∘ b) ∘ c = a ∘ (b ∘ c)
|
||||
// Ассоциативность: (a ∘ b) ∘ c = a ∘ (b ∘ c)
|
||||
pub fn monoid_associativity_test()
|
||||
|
||||
/// Коммутативность (для непересекающихся множеств ключей)
|
||||
// Коммутативность (для непересекающихся множеств ключей)
|
||||
pub fn monoid_commutativity_test()
|
||||
```
|
||||
|
||||
### Отчёт тестирования
|
||||
|
||||
```
|
||||
Running lab2_test.main
|
||||
.....................
|
||||
21 passed, no failures
|
||||
> gleam test
|
||||
Compiling lab2
|
||||
Compiled in 0.46s
|
||||
Running lab2_test.main
|
||||
.......................
|
||||
23 passed, no failures
|
||||
```
|
||||
|
||||
### Метрики производительности
|
||||
|
||||
| Операция | Теоретическая сложность | Реализованная сложность |
|
||||
|----------|------------------------|------------------------|
|
||||
| lookup | O(log n) | O(log n) |
|
||||
| insert | O(log n) | O(log n) |
|
||||
| delete | O(log n) | O(log n) |
|
||||
| size | O(n) | O(n) |
|
||||
| map | O(n) | O(n) |
|
||||
| filter | O(n log n) | O(n log n) |
|
||||
| fold | O(n) | O(n) |
|
||||
| Операция | Сложность|
|
||||
|----------|------------------------|
|
||||
| lookup | O(log n) |
|
||||
| insert | O(log n) |
|
||||
| delete | O(log n) |
|
||||
| size | O(n) |
|
||||
| map | O(n) |
|
||||
| filter | O(n log n) |
|
||||
| fold | O(n) |
|
||||
|
||||
---
|
||||
|
||||
## Выводы
|
||||
|
||||
### Использованные приёмы программирования
|
||||
|
||||
1. **Ленивые вычисления (Lazy Evaluation):**
|
||||
- **Преимущества:** Экономия памяти, возможность работы с потенциально бесконечными структурами, отложенные вычисления только при необходимости
|
||||
- **Реализация:** Тип `Lazy(a)` с конструкторами `Thunk` и `Value`
|
||||
- **Эффект:** Поддеревья создаются только при обращении к ним, что снижает накладные расходы
|
||||
|
||||
2. **Полиморфизм:**
|
||||
- **Преимущества:** Универсальность структуры данных для любых типов ключей и значений
|
||||
- **Реализация:** Параметрические типы `RBTree(k, v)`
|
||||
- **Эффект:** Возможность использования с различными типами данных без дублирования кода
|
||||
|
||||
3. **Неизменяемые структуры данных:**
|
||||
- **Преимущества:** Отсутствие побочных эффектов, thread-safety, упрощение рассуждений о коде
|
||||
- **Реализация:** Все операции возвращают новые версии дерева
|
||||
- **Эффект:** Функциональная чистота и предсказуемость
|
||||
|
||||
4. **Структурная рекурсия:**
|
||||
- **Преимущества:** Естественность выражения алгоритмов для древовидных структур
|
||||
- **Реализация:** Паттерн-матчинг на конструкторах типа
|
||||
- **Эффект:** Читаемый и понятный код
|
||||
|
||||
5. **Моноид (Monoid):**
|
||||
- **Преимущества:** Математические гарантии корректности операций объединения
|
||||
- **Реализация:** Операции `mempty()` и `concat()` с проверкой аксиом
|
||||
- **Эффект:** Композируемость и предсказуемость операций
|
||||
|
||||
6. **Функции высшего порядка:**
|
||||
- **Преимущества:** Абстракция над общими паттернами обработки данных
|
||||
- **Реализация:** `map`, `filter`, `fold_left`, `fold_right`
|
||||
- **Эффект:** Выразительность и переиспользуемость кода
|
||||
|
||||
### Особенности языка Gleam
|
||||
|
||||
1. **Система типов:** Строгая статическая типизация с выводом типов облегчает разработку и предотвращает ошибки
|
||||
2. **Паттерн-матчинг:** Удобный и безопасный способ работы с алгебраическими типами данных
|
||||
3. **Отсутствие null:** Использование `Option(a)` делает код более безопасным
|
||||
4. **Interoperability:** Возможность компиляции в Erlang и JavaScript расширяет область применения
|
||||
|
||||
### Проблемы и ограничения
|
||||
|
||||
1. **Упрощённое удаление:** Реализованный алгоритм удаления не полностью поддерживает все инварианты Red-Black Tree
|
||||
2. **Производительность:** Ленивые вычисления могут добавлять накладные расходы в некоторых случаях
|
||||
3. **Сложность балансировки:** Полная реализация всех случаев балансировки требует дополнительной работы
|
||||
|
||||
### Общая оценка
|
||||
|
||||
Реализация Red-Black Tree с ленивыми вычислениями на языке Gleam продемонстрировала эффективность функциональных подходов к программированию. Использование неизменяемых структур данных, полиморфизма и функций высшего порядка привело к созданию гибкой, безопасной и переиспользуемой библиотеки.
|
||||
|
||||
Ленивые вычисления показали свою полезность для оптимизации производительности при работе с большими деревьями, где не все поддеревья могут быть использованы.
|
||||
|
||||
Использование ленивых вычислений повышает эффективность использования памяти при операциях со структурами данных высокой вложенности (в нашем случае) ценой cpu на вычисление значения.
|
||||
Также использовал типы, а ещё писал своё сравнение без высокоуровневых абстракций.
|
||||
Reference in New Issue
Block a user