Косяк с функцией сравнения

This commit is contained in:
2025-12-06 12:58:06 +03:00
parent 3bf2db9b30
commit 9023a42c46
11 changed files with 1450 additions and 0 deletions

370
lab2/Report.md Normal file
View File

@@ -0,0 +1,370 @@
# Отчёт по лабораторной работе №2
**Университет ИТМО**
**Факультет программной инженерии и компьютерной техники**
**Студент:** Владимиров Владислав Александрович
**Группа:** P3222
**Тема:** Реализация Red-Black Tree с ленивыми вычислениями
**Лабораторная работа №2**
**Дисциплина:** Функциональное программирование
---
## Требования к разработанному ПО
### Цель работы
Освоиться с построением пользовательских типов данных, полиморфизмом, рекурсивными алгоритмами и средствами тестирования (unit testing, property-based testing), а также разделением интерфейса и особенностей реализации.
### Вариант задания
**Red-Black Tree Lazy** - красно-чёрное дерево на ленивых вычислениях.
### Функциональные требования
1. **Основные операции:**
- Добавление элементов (`insert`)
- Удаление элементов (`delete`)
- Фильтрация (`filter`)
- Отображение (`map`)
- Свёртки левая и правая (`fold_left`, `fold_right`)
2. **Структурные требования:**
- Структура должна быть моноидом
- Неизменяемые структуры данных
- Полиморфная реализация
- Использование ленивых вычислений
3. **Требования к тестированию:**
- Unit testing для всех функций
- Property-based тестирование (минимум 3 свойства)
- Тестирование свойств моноида
### Технические требования
- **Язык программирования:** Gleam
- **Сложность операций:** O(log n) для insert, delete, lookup
- **API не должно "протекать"**
- **Эффективная реализация сравнения деревьев**
- **Идиоматичный стиль программирования**
---
## Ключевые элементы реализации
### Основные типы данных
```gleam
/// Цвета узлов красно-черного дерева
pub type Color {
Red
Black
}
/// Ленивое значение для отложенных вычислений
pub type Lazy(a) {
Thunk(fn() -> a) // Отложенное вычисление
Value(a) // Уже вычисленное значение
}
/// Красно-черное дерево с ленивыми вычислениями
pub type RBTree(k, v) {
Empty // Пустое дерево
Node(
color: Color, // Цвет узла
key: k, // Ключ
value: v, // Значение
left: Lazy(RBTree(k, v)), // Левое поддерево (ленивое)
right: Lazy(RBTree(k, v)), // Правое поддерево (ленивое)
)
}
```
### Управление ленивыми вычислениями
```gleam
/// Форсирует вычисление ленивого значения
fn force(lazy: Lazy(a)) -> a {
case lazy {
Value(val) -> val
Thunk(f) -> f()
}
}
/// Создаёт ленивое значение из функции
fn delay(f: fn() -> a) -> Lazy(a) {
Thunk(f)
}
```
### Балансировка красно-чёрного дерева
Реализованы 4 случая нарушения инвариантов Red-Black Tree:
```gleam
n balance(
color: Color,
key: k,
value: v,
left: Lazy(RBTree(k, v)),
right: Lazy(RBTree(k, v)),
) -> RBTree(k, v) {
let left_tree = force(left)
let right_tree = force(right)
case color, left_tree, right_tree {
// Случай 1: красный левый дедушка с красными детьми
Black, Node(Red, lk, lv, ll, lr), r ->
case force(ll) {
Node(Red, llk, llv, lll, llr) ->
Node(
Red,
lk,
lv,
delay(fn() { Node(Black, llk, llv, lll, llr) }),
delay(fn() { Node(Black, key, value, lr, Value(r)) }),
)
_ ->
case force(lr) {
Node(Red, lrk, lrv, lrl, lrr) ->
Node(
Red,
lrk,
lrv,
delay(fn() { Node(Black, lk, lv, ll, lrl) }),
delay(fn() { Node(Black, key, value, lrr, Value(r)) }),
)
_ -> Node(color, key, value, left, right)
}
}
// Случай 3: красный правый дедушка с красным левым внуком
Black, l, Node(Red, rk, rv, rl, rr) ->
case force(rl) {
Node(Red, rlk, rlv, rll, rlr) ->
Node(
Red,
rlk,
rlv,
delay(fn() { Node(Black, key, value, Value(l), rll) }),
delay(fn() { Node(Black, rk, rv, rlr, rr) }),
)
_ ->
case force(rr) {
Node(Red, rrk, rrv, rrl, rrr) ->
Node(
Red,
rk,
rv,
delay(fn() { Node(Black, key, value, Value(l), rl) }),
delay(fn() { Node(Black, rrk, rrv, rrl, rrr) }),
)
_ -> Node(color, key, value, left, right)
}
}
// Базовый случай - балансировка не нужна
_, _, _ -> Node(color, key, value, left, right)
}
}
```
### Основные операции
**Вставка элемента:**
```gleam
pub fn insert(tree: RBTree(k, v), key: k, value: v,
compare: fn(k, k) -> Order) -> RBTree(k, v) {
let result = insert_helper(tree, key, value, compare)
make_black(result) // Корень всегда чёрный
}
```
**Поиск элемента:**
```gleam
pub fn lookup(tree: RBTree(k, v), key: k,
compare: fn(k, k) -> Order) -> Option(v) {
case tree {
Empty -> None
Node(_, k, v, left, right) ->
case compare(key, k) {
order.Lt -> lookup(force(left), key, compare)
order.Gt -> lookup(force(right), key, compare)
order.Eq -> Some(v)
}
}
}
```
### Функции высшего порядка
**Отображение:**
```gleam
pub fn map(tree: RBTree(k, v), f: fn(v) -> w) -> RBTree(k, w) {
case tree {
Empty -> Empty
Node(color, key, value, left, right) ->
Node(color, key, f(value),
delay(fn() { map(force(left), f) }),
delay(fn() { map(force(right), f) }))
}
}
```
**Левая свёртка:**
```gleam
pub fn fold_left(tree: RBTree(k, v), acc: a, f: fn(a, k, v) -> a) -> a {
case tree {
Empty -> acc
Node(_, key, value, left, right) -> {
let left_acc = fold_left(force(left), acc, f)
let current_acc = f(left_acc, key, value)
fold_left(force(right), current_acc, f)
}
}
}
```
### Операции моноида
```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) {
insert(acc, key, value, compare)
})
}
```
---
## Тесты и метрики
### 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()` - конвертация в список и обратно
### Property-based тесты
1. **Свойства основных операций:**
```gleam
pub fn insert_lookup_property_test() // insert -> lookup инвариант
pub fn insert_size_property_test() // размер увеличивается корректно
pub fn filter_property_test() // фильтр сохраняет структуру
pub fn map_property_test() // map сохраняет структуру
```
2. **Свойства свёрток:**
```gleam
pub fn fold_property_test() // корректность левой/правой свёртки
```
3. **Свойства преобразований:**
```gleam
pub fn list_roundtrip_property_test() // to_list -> from_list эквивалентность
```
### Тесты моноида
```gleam
/// Левая единица: mempty ∘ a = a
pub fn monoid_left_identity_test()
/// Правая единица: a ∘ mempty = a
pub fn monoid_right_identity_test()
/// Ассоциативность: (a ∘ b) ∘ c = a ∘ (b ∘ c)
pub fn monoid_associativity_test()
/// Коммутативность (для непересекающихся множеств ключей)
pub fn monoid_commutativity_test()
```
### Отчёт тестирования
```
Running lab2_test.main
.....................
21 passed, no failures
Compiled in 0.46s
```
### Метрики производительности
| Операция | Теоретическая сложность | Реализованная сложность |
|----------|------------------------|------------------------|
| 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) |
---
## Выводы
### Использованные приёмы программирования
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 продемонстрировала эффективность функциональных подходов к программированию. Использование неизменяемых структур данных, полиморфизма и функций высшего порядка привело к созданию гибкой, безопасной и переиспользуемой библиотеки.
Ленивые вычисления показали свою полезность для оптимизации производительности при работе с большими деревьями, где не все поддеревья могут быть использованы.