lab 2 complete v1
This commit is contained in:
147
lab2/README.md
147
lab2/README.md
@@ -1,147 +0,0 @@
|
||||
# lab2
|
||||
|
||||
[](https://hex.pm/packages/lab2)
|
||||
[](https://hexdocs.pm/lab2/)
|
||||
|
||||
```sh
|
||||
gleam add lab2@1
|
||||
```
|
||||
```gleam
|
||||
import lab2
|
||||
|
||||
pub fn main() -> Nil {
|
||||
// TODO: An example of the project in use
|
||||
}
|
||||
```
|
||||
|
||||
Further documentation can be found at <https://hexdocs.pm/lab2>.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
gleam run # Run the project
|
||||
gleam test # Run the tests
|
||||
```
|
||||
|
||||
# Red-Black Tree с ленивыми вычислениями
|
||||
|
||||
## Описание
|
||||
|
||||
Реализация красно-чёрного дерева (Red-Black Tree) с ленивыми вычислениями на языке Gleam. Красно-чёрное дерево - это самобалансирующееся двоичное дерево поиска, которое гарантирует логарифмическую сложность операций вставки, удаления и поиска.
|
||||
|
||||
### Особенности реализации
|
||||
|
||||
- **Ленивые вычисления**: Поддеревья создаются только при необходимости, что экономит память и время
|
||||
- **Полиморфность**: Дерево работает с любыми типами ключей и значений
|
||||
- **Неизменяемость**: Все операции создают новые версии дерева, не изменяя исходное
|
||||
- **Структура моноида**: Поддерживает операции объединения с нейтральным элементом
|
||||
|
||||
## API
|
||||
|
||||
### Основные операции
|
||||
|
||||
- `empty()` - создание пустого дерева
|
||||
- `insert(tree, key, value, compare)` - вставка элемента
|
||||
- `lookup(tree, key, compare)` - поиск элемента
|
||||
- `delete(tree, key, compare)` - удаление элемента
|
||||
- `contains(tree, key, compare)` - проверка наличия ключа
|
||||
|
||||
### Функции высшего порядка
|
||||
|
||||
- `map(tree, f)` - отображение функции на все значения
|
||||
- `filter(tree, predicate, compare)` - фильтрация элементов
|
||||
- `fold_left(tree, acc, f)` - левая свёртка
|
||||
- `fold_right(tree, acc, f)` - правая свёртка
|
||||
|
||||
### Операции моноида
|
||||
|
||||
- `mempty()` - нейтральный элемент (пустое дерево)
|
||||
- `concat(tree1, tree2, compare)` - объединение деревьев
|
||||
|
||||
### Вспомогательные функции
|
||||
|
||||
- `size(tree)` - размер дерева
|
||||
- `to_list(tree)` - преобразование в список
|
||||
- `from_list(list, compare)` - создание из списка
|
||||
- `keys(tree)` - получение всех ключей
|
||||
- `values(tree)` - получение всех значений
|
||||
- `equal(tree1, tree2, key_eq, value_eq)` - сравнение деревьев
|
||||
|
||||
## Сложность операций
|
||||
|
||||
| Операция | Сложность |
|
||||
|----------|-----------|
|
||||
| lookup | O(log n) |
|
||||
| insert | O(log n) |
|
||||
| delete | O(log n) |
|
||||
| size | O(n) |
|
||||
| map | O(n) |
|
||||
| filter | O(n) |
|
||||
| fold | O(n) |
|
||||
|
||||
## Пример использования
|
||||
|
||||
```gleam
|
||||
import lab2
|
||||
import gleam/int
|
||||
|
||||
// Функция сравнения для целых чисел
|
||||
fn int_compare(a: Int, b: Int) -> order.Order {
|
||||
int.compare(a, b)
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
// Создание дерева и вставка элементов
|
||||
let tree = lab2.empty()
|
||||
|> lab2.insert(5, "five", int_compare)
|
||||
|> lab2.insert(3, "three", int_compare)
|
||||
|> lab2.insert(7, "seven", int_compare)
|
||||
|> lab2.insert(1, "one", int_compare)
|
||||
|
||||
// Поиск элемента
|
||||
case lab2.lookup(tree, 5, int_compare) {
|
||||
Some(value) -> io.println("Found: " <> value)
|
||||
None -> io.println("Not found")
|
||||
}
|
||||
|
||||
// Фильтрация чётных ключей
|
||||
let even_tree = lab2.filter(tree, fn(key, _) { key % 2 == 0 }, int_compare)
|
||||
|
||||
// Отображение: удвоение длины строк
|
||||
let doubled = lab2.map(tree, fn(value) { value <> value })
|
||||
|
||||
// Свёртка: подсчёт элементов
|
||||
let count = lab2.fold_left(tree, 0, fn(acc, _, _) { acc + 1 })
|
||||
}
|
||||
```
|
||||
|
||||
## Свойства моноида
|
||||
|
||||
Структура данных удовлетворяет аксиомам моноида:
|
||||
|
||||
1. **Ассоциативность**: `concat(concat(a, b), c) ≡ concat(a, concat(b, c))`
|
||||
2. **Левая единица**: `concat(mempty(), a) ≡ a`
|
||||
3. **Правая единица**: `concat(a, mempty()) ≡ a`
|
||||
|
||||
## Тестирование
|
||||
|
||||
Проект включает в себя comprehensive test suite:
|
||||
|
||||
- **Unit тесты**: Проверка корректности отдельных функций
|
||||
- **Property-based тесты**: Проверка инвариантов и свойств структуры данных
|
||||
- **Тесты моноида**: Проверка аксиом моноида
|
||||
|
||||
Запуск тестов:
|
||||
```bash
|
||||
gleam test
|
||||
```
|
||||
|
||||
## Инварианты красно-чёрного дерева
|
||||
|
||||
1. Каждый узел либо красный, либо чёрный
|
||||
2. Корень дерева чёрный
|
||||
3. Все листья (NULL-узлы) чёрные
|
||||
4. Если узел красный, то оба его потомка чёрные
|
||||
5. Для каждого узла все пути от него до листьев содержат одинаковое количество чёрных узлов
|
||||
|
||||
Эти инварианты гарантируют, что самый длинный путь от корня до листа не более чем в 2 раза длиннее самого короткого пути, что обеспечивает логарифмическую сложность операций.
|
||||
182
lab2/Report.md
182
lab2/Report.md
@@ -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 на вычисление значения.
|
||||
Также использовал типы, а ещё писал своё сравнение без высокоуровневых абстракций.
|
||||
@@ -1,19 +1,16 @@
|
||||
// Модуль красно-чёрного дерева с ленивыми вычислениями
|
||||
// Реализует самобалансирующееся двоичное дерево поиска с гарантированной
|
||||
// логарифмической сложностью операций вставки, удаления и поиска
|
||||
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/order.{type Order}
|
||||
|
||||
/// Цвета узлов красно-черного дерева
|
||||
/// Red - красный узел, Black - чёрный узел
|
||||
// Цвета узлов красно-черного дерева
|
||||
// Red - красный узел, Black - чёрный узел
|
||||
pub type Color {
|
||||
Red
|
||||
Black
|
||||
}
|
||||
|
||||
/// Ленивое значение для отложенных вычислений
|
||||
/// Позволяет откладывать создание поддеревьев до момента их использования
|
||||
// Ленивое значение для отложенных вычислений
|
||||
// Позволяет откладывать создание поддеревьев до момента их использования
|
||||
// Не знаю правильно это или нет на самом деле, в gleam вроде lazy нету встроенного
|
||||
pub type Lazy(a) {
|
||||
Thunk(fn() -> a)
|
||||
// Отложенное вычисление через функцию
|
||||
@@ -21,9 +18,9 @@ pub type Lazy(a) {
|
||||
// Уже вычисленное значение
|
||||
}
|
||||
|
||||
/// Красно-черное дерево с ленивыми вычислениями
|
||||
/// Полиморфная структура данных с ключами типа k и значениями типа v
|
||||
/// Поддерживает инварианты красно-чёрного дерева для гарантии балансировки
|
||||
// Красно-черное дерево с ленивыми вычислениями
|
||||
// Полиморфная структура данных с ключами типа k и значениями типа v
|
||||
// Поддерживает инварианты красно-чёрного дерева для гарантии балансировки
|
||||
pub type RBTree(k, v) {
|
||||
Empty
|
||||
// Пустое дерево
|
||||
@@ -42,8 +39,8 @@ pub type RBTree(k, v) {
|
||||
)
|
||||
}
|
||||
|
||||
/// Форсирует вычисление ленивого значения
|
||||
/// Если значение уже вычислено, возвращает его, иначе выполняет функцию
|
||||
// Форсирует вычисление ленивого значения
|
||||
// Если значение уже вычислено, возвращает его, иначе выполняет функцию
|
||||
fn force(lazy: Lazy(a)) -> a {
|
||||
case lazy {
|
||||
Value(val) -> val
|
||||
@@ -51,20 +48,20 @@ fn force(lazy: Lazy(a)) -> a {
|
||||
}
|
||||
}
|
||||
|
||||
/// Создаёт ленивое значение из функции
|
||||
/// Отложенное вычисление будет выполнено при первом обращении
|
||||
// Создаёт ленивое значение из функции
|
||||
// Отложенное вычисление будет выполнено при первом обращении
|
||||
fn delay(f: fn() -> a) -> Lazy(a) {
|
||||
Thunk(f)
|
||||
}
|
||||
|
||||
/// Создаёт пустое дерево - нейтральный элемент моноида
|
||||
/// Время выполнения: O(1)
|
||||
// Создаёт пустое дерево - нейтральный элемент моноида
|
||||
// Время выполнения: O(1)
|
||||
pub fn empty() -> RBTree(k, v) {
|
||||
Empty
|
||||
}
|
||||
|
||||
/// Проверяет, является ли дерево пустым
|
||||
/// Время выполнения: O(1)
|
||||
// Проверяет, является ли дерево пустым
|
||||
// Время выполнения: O(1)
|
||||
pub fn is_empty(tree: RBTree(k, v)) -> Bool {
|
||||
case tree {
|
||||
Empty -> True
|
||||
@@ -72,7 +69,7 @@ pub fn is_empty(tree: RBTree(k, v)) -> Bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Создаёт узел с чёрным цветом
|
||||
// Создаёт узел с чёрным цветом
|
||||
fn make_black(tree: RBTree(k, v)) -> RBTree(k, v) {
|
||||
case tree {
|
||||
Node(_, key, value, left, right) -> Node(Black, key, value, left, right)
|
||||
@@ -80,7 +77,7 @@ fn make_black(tree: RBTree(k, v)) -> RBTree(k, v) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Балансировка красно-черного дерева
|
||||
// Балансировка RB tree
|
||||
fn balance(
|
||||
color: Color,
|
||||
key: k,
|
||||
@@ -147,9 +144,9 @@ fn balance(
|
||||
}
|
||||
}
|
||||
|
||||
/// Вставляет элемент в дерево с сохранением инвариантов красно-чёрного дерева
|
||||
/// Время выполнения: O(log n)
|
||||
/// compare - функция сравнения ключей, должна возвращать order.Lt, order.Eq или order.Gt
|
||||
// Вставляет элемент в дерево с сохранением инвариантов красно-чёрного дерева
|
||||
// Время выполнения: O(log n)
|
||||
// compare - функция сравнения ключей, должна возвращать order.Lt, order.Eq или order.Gt
|
||||
pub fn insert(
|
||||
tree: RBTree(k, v),
|
||||
key: k,
|
||||
@@ -158,7 +155,7 @@ pub fn insert(
|
||||
) -> RBTree(k, v) {
|
||||
let result = insert_helper(tree, key, value, compare)
|
||||
make_black(result)
|
||||
// Гарантируем, что корень чёрный
|
||||
// гарантия что корень чёрный
|
||||
}
|
||||
|
||||
fn insert_helper(
|
||||
@@ -192,7 +189,7 @@ fn insert_helper(
|
||||
}
|
||||
}
|
||||
|
||||
/// Поиск элемента в дереве
|
||||
// Поиск элемента в дереве
|
||||
pub fn lookup(
|
||||
tree: RBTree(k, v),
|
||||
key: k,
|
||||
@@ -209,7 +206,7 @@ pub fn lookup(
|
||||
}
|
||||
}
|
||||
|
||||
/// Находит минимальный элемент в дереве
|
||||
// Находит минимальный элемент в дереве
|
||||
fn find_min(tree: RBTree(k, v)) -> Option(#(k, v)) {
|
||||
case tree {
|
||||
Empty -> None
|
||||
@@ -221,7 +218,7 @@ fn find_min(tree: RBTree(k, v)) -> Option(#(k, v)) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Удаляет минимальный элемент из дерева
|
||||
// Удаляет минимальный элемент из дерева
|
||||
fn delete_min(tree: RBTree(k, v)) -> RBTree(k, v) {
|
||||
case tree {
|
||||
Empty -> Empty
|
||||
@@ -287,7 +284,7 @@ pub fn delete(
|
||||
}
|
||||
}
|
||||
|
||||
/// Фильтрация элементов дерева
|
||||
// Фильтрация элементов дерева
|
||||
pub fn filter(
|
||||
tree: RBTree(k, v),
|
||||
predicate: fn(k, v) -> Bool,
|
||||
@@ -301,7 +298,7 @@ pub fn filter(
|
||||
})
|
||||
}
|
||||
|
||||
/// Отображение значений в дереве
|
||||
// Отображение значений в дереве
|
||||
pub fn map(tree: RBTree(k, v), f: fn(v) -> w) -> RBTree(k, w) {
|
||||
case tree {
|
||||
Empty -> Empty
|
||||
@@ -316,7 +313,7 @@ pub fn map(tree: RBTree(k, v), f: fn(v) -> w) -> RBTree(k, w) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Левая свёртка дерева
|
||||
// Левая свёртка дерева
|
||||
pub fn fold_left(tree: RBTree(k, v), acc: a, f: fn(a, k, v) -> a) -> a {
|
||||
case tree {
|
||||
Empty -> acc
|
||||
@@ -328,7 +325,7 @@ pub fn fold_left(tree: RBTree(k, v), acc: a, f: fn(a, k, v) -> a) -> a {
|
||||
}
|
||||
}
|
||||
|
||||
/// Правая свёртка дерева
|
||||
// Правая свёртка дерева
|
||||
pub fn fold_right(tree: RBTree(k, v), acc: a, f: fn(k, v, a) -> a) -> a {
|
||||
case tree {
|
||||
Empty -> acc
|
||||
@@ -340,7 +337,7 @@ pub fn fold_right(tree: RBTree(k, v), acc: a, f: fn(k, v, a) -> a) -> a {
|
||||
}
|
||||
}
|
||||
|
||||
/// Объединение двух деревьев (операция моноида)
|
||||
// Объединение двух деревьев (операция моноида)
|
||||
pub fn concat(
|
||||
tree1: RBTree(k, v),
|
||||
tree2: RBTree(k, v),
|
||||
@@ -351,22 +348,22 @@ pub fn concat(
|
||||
})
|
||||
}
|
||||
|
||||
/// Нейтральный элемент моноида (пустое дерево)
|
||||
// Нейтральный элемент моноида (пустое дерево)
|
||||
pub fn mempty() -> RBTree(k, v) {
|
||||
empty()
|
||||
}
|
||||
|
||||
/// Размер дерева
|
||||
// Размер дерева
|
||||
pub fn size(tree: RBTree(k, v)) -> Int {
|
||||
fold_left(tree, 0, fn(acc, _, _) { acc + 1 })
|
||||
}
|
||||
|
||||
/// Преобразование дерева в список пар
|
||||
// Преобразование дерева в список пар
|
||||
pub fn to_list(tree: RBTree(k, v)) -> List(#(k, v)) {
|
||||
fold_right(tree, [], fn(key, value, acc) { [#(key, value), ..acc] })
|
||||
}
|
||||
|
||||
/// Создание дерева из списка пар
|
||||
// Создание дерева из списка пар
|
||||
pub fn from_list(
|
||||
list: List(#(k, v)),
|
||||
compare: fn(k, k) -> Order,
|
||||
@@ -378,7 +375,7 @@ pub fn from_list(
|
||||
})
|
||||
}
|
||||
|
||||
/// Вспомогательная функция для свёртки списка
|
||||
// Вспомогательная функция для свёртки списка
|
||||
fn list_fold_left(list: List(a), acc: b, f: fn(b, a) -> b) -> b {
|
||||
case list {
|
||||
[] -> acc
|
||||
@@ -386,9 +383,9 @@ fn list_fold_left(list: List(a), acc: b, f: fn(b, a) -> b) -> b {
|
||||
}
|
||||
}
|
||||
|
||||
/// Эффективная проверка равенства двух деревьев
|
||||
/// Сравнивает деревья структурно без преобразования в списки
|
||||
/// Время выполнения: O(min(n, m)) где n, m - размеры деревьев
|
||||
// Эффективная проверка равенства двух деревьев
|
||||
// Сравнивает деревья структурно без преобразования в списки
|
||||
// Время выполнения: O(min(n, m)) где n, m - размеры деревьев
|
||||
pub fn equal(
|
||||
tree1: RBTree(k, v),
|
||||
tree2: RBTree(k, v),
|
||||
@@ -398,8 +395,8 @@ pub fn equal(
|
||||
equal_helper(tree1, tree2, key_compare, value_compare)
|
||||
}
|
||||
|
||||
/// Вспомогательная функция для структурного сравнения деревьев
|
||||
/// Использует короткое замыкание при первом несовпадении
|
||||
// Вспомогательная функция для структурного сравнения деревьев
|
||||
// Использует замыкание при первом несовпадении
|
||||
fn equal_helper(
|
||||
tree1: RBTree(k, v),
|
||||
tree2: RBTree(k, v),
|
||||
@@ -410,28 +407,26 @@ fn equal_helper(
|
||||
// Оба дерева пустые - равны
|
||||
Empty, Empty -> True
|
||||
|
||||
// Одно пустое, другое нет - не равны
|
||||
Empty, _ -> False
|
||||
_, Empty -> False
|
||||
|
||||
// Оба узла - сравниваем структурно
|
||||
Node(color1, key1, value1, left1, right1),
|
||||
Node(color2, key2, value2, left2, right2) -> {
|
||||
// Быстрая проверка: цвета должны совпадать
|
||||
color1 == color2 &&
|
||||
Node(color2, key2, value2, left2, right2)
|
||||
-> {
|
||||
color1 == color2
|
||||
// Ключи должны быть равны
|
||||
key_compare(key1, key2) &&
|
||||
&& key_compare(key1, key2)
|
||||
// Значения должны быть равны
|
||||
value_compare(value1, value2) &&
|
||||
&& value_compare(value1, value2)
|
||||
// Рекурсивно проверяем левые поддеревья (ленивые)
|
||||
equal_helper(force(left1), force(left2), key_compare, value_compare) &&
|
||||
&& equal_helper(force(left1), force(left2), key_compare, value_compare)
|
||||
// Рекурсивно проверяем правые поддеревья (ленивые)
|
||||
equal_helper(force(right1), force(right2), key_compare, value_compare)
|
||||
&& equal_helper(force(right1), force(right2), key_compare, value_compare)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Проверка, содержится ли ключ в дереве
|
||||
// Проверка, содержится ли ключ в дереве
|
||||
pub fn contains(tree: RBTree(k, v), key: k, compare: fn(k, k) -> Order) -> Bool {
|
||||
case lookup(tree, key, compare) {
|
||||
Some(_) -> True
|
||||
@@ -439,19 +434,19 @@ pub fn contains(tree: RBTree(k, v), key: k, compare: fn(k, k) -> Order) -> Bool
|
||||
}
|
||||
}
|
||||
|
||||
/// Получение всех ключей дерева
|
||||
// Получение всех ключей дерева
|
||||
pub fn keys(tree: RBTree(k, v)) -> List(k) {
|
||||
fold_right(tree, [], fn(key, _, acc) { [key, ..acc] })
|
||||
}
|
||||
|
||||
/// Получение всех значений дерева
|
||||
// Получение всех значений дерева
|
||||
pub fn values(tree: RBTree(k, v)) -> List(v) {
|
||||
fold_right(tree, [], fn(_, value, acc) { [value, ..acc] })
|
||||
}
|
||||
|
||||
/// Семантическое равенство деревьев - сравнивает содержимое независимо от структуры
|
||||
/// Два дерева семантически равны, если содержат одинаковые пары ключ-значение
|
||||
/// Время выполнения: O(n log n) из-за необходимости итерации
|
||||
// Семантическое равенство деревьев - сравнивает содержимое независимо от структуры
|
||||
// Два дерева семантически равны, если содержат одинаковые пары ключ-значение
|
||||
// Время выполнения: O(n log n) из-за необходимости итерации
|
||||
pub fn semantic_equal(
|
||||
tree1: RBTree(k, v),
|
||||
tree2: RBTree(k, v),
|
||||
@@ -464,8 +459,8 @@ pub fn semantic_equal(
|
||||
True -> {
|
||||
// Проверяем, что все элементы из tree1 есть в tree2 с теми же значениями
|
||||
fold_left(tree1, True, fn(acc, key, value) {
|
||||
acc &&
|
||||
case lookup(tree2, key, key_order) {
|
||||
acc
|
||||
&& case lookup(tree2, key, key_order) {
|
||||
Some(other_value) -> value_equal(value, other_value)
|
||||
None -> False
|
||||
}
|
||||
@@ -474,8 +469,8 @@ pub fn semantic_equal(
|
||||
}
|
||||
}
|
||||
|
||||
/// Сравнение с игнорированием цветов узлов (только структура и данные)
|
||||
/// Полезно когда важна только логическая структура дерева поиска
|
||||
// Сравнение с игнорированием цветов узлов (только структура и данные)
|
||||
// Полезно когда важна только логическая структура дерева поиска
|
||||
pub fn structure_equal(
|
||||
tree1: RBTree(k, v),
|
||||
tree2: RBTree(k, v),
|
||||
@@ -486,13 +481,17 @@ pub fn structure_equal(
|
||||
Empty, Empty -> True
|
||||
Empty, _ -> False
|
||||
_, Empty -> False
|
||||
Node(_, key1, value1, left1, right1),
|
||||
Node(_, key2, value2, left2, right2) -> {
|
||||
Node(_, key1, value1, left1, right1), Node(_, key2, value2, left2, right2) -> {
|
||||
// Игнорируем цвет, сравниваем только ключи, значения и структуру
|
||||
key_compare(key1, key2) &&
|
||||
value_compare(value1, value2) &&
|
||||
structure_equal(force(left1), force(left2), key_compare, value_compare) &&
|
||||
structure_equal(force(right1), force(right2), key_compare, value_compare)
|
||||
key_compare(key1, key2)
|
||||
&& value_compare(value1, value2)
|
||||
&& structure_equal(force(left1), force(left2), key_compare, value_compare)
|
||||
&& structure_equal(
|
||||
force(right1),
|
||||
force(right2),
|
||||
key_compare,
|
||||
value_compare,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,14 @@ fn string_equal(a: String, b: String) -> Bool {
|
||||
a == b
|
||||
}
|
||||
|
||||
// Unit тесты
|
||||
|
||||
/// Тест создания пустого дерева
|
||||
// Тест создания пустого дерева
|
||||
pub fn empty_tree_test() {
|
||||
let tree = lab2.empty()
|
||||
lab2.is_empty(tree) |> should.be_true
|
||||
lab2.size(tree) |> should.equal(0)
|
||||
}
|
||||
|
||||
/// Тест вставки одного элемента
|
||||
// Тест вставки одного элемента
|
||||
pub fn single_insert_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -45,7 +43,7 @@ pub fn single_insert_test() {
|
||||
lab2.lookup(tree, 10, int_compare) |> should.equal(option.None)
|
||||
}
|
||||
|
||||
/// Тест множественных вставок
|
||||
// Тест множественных вставок
|
||||
pub fn multiple_insert_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -63,7 +61,7 @@ pub fn multiple_insert_test() {
|
||||
lab2.lookup(tree, 9, int_compare) |> should.equal(option.Some("nine"))
|
||||
}
|
||||
|
||||
/// Тест удаления элементов
|
||||
// Тест удаления элементов
|
||||
pub fn delete_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -78,7 +76,7 @@ pub fn delete_test() {
|
||||
lab2.lookup(tree, 7, int_compare) |> should.equal(option.Some("seven"))
|
||||
}
|
||||
|
||||
/// Тест фильтрации
|
||||
// Тест фильтрации
|
||||
pub fn filter_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -96,7 +94,7 @@ pub fn filter_test() {
|
||||
lab2.contains(filtered, 1, int_compare) |> should.be_false
|
||||
}
|
||||
|
||||
/// Тест отображения
|
||||
// Тест отображения
|
||||
pub fn map_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -111,7 +109,7 @@ pub fn map_test() {
|
||||
lab2.lookup(mapped, 3, int_compare) |> should.equal(option.Some(60))
|
||||
}
|
||||
|
||||
/// Тест левой свёртки
|
||||
// Тест левой свёртки
|
||||
pub fn fold_left_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -123,7 +121,7 @@ pub fn fold_left_test() {
|
||||
sum |> should.equal(60)
|
||||
}
|
||||
|
||||
/// Тест правой свёртки
|
||||
// Тест правой свёртки
|
||||
pub fn fold_right_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -136,7 +134,7 @@ pub fn fold_right_test() {
|
||||
result |> string.length |> should.equal(3)
|
||||
}
|
||||
|
||||
/// Тест преобразования в список и обратно
|
||||
// Тест преобразования в список и обратно
|
||||
pub fn to_from_list_test() {
|
||||
let original_list = [#(1, "one"), #(2, "two"), #(3, "three")]
|
||||
let tree = lab2.from_list(original_list, int_compare)
|
||||
@@ -151,7 +149,7 @@ pub fn to_from_list_test() {
|
||||
|
||||
// Тесты свойств моноида
|
||||
|
||||
/// Тест нейтрального элемента (левая единица)
|
||||
// Тест нейтрального элемента (левая единица)
|
||||
pub fn monoid_left_identity_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -163,7 +161,7 @@ pub fn monoid_left_identity_test() {
|
||||
lab2.equal(tree, result, int_equal, string_equal) |> should.be_true
|
||||
}
|
||||
|
||||
/// Тест нейтрального элемента (правая единица)
|
||||
// Тест нейтрального элемента (правая единица)
|
||||
pub fn monoid_right_identity_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -175,7 +173,7 @@ pub fn monoid_right_identity_test() {
|
||||
lab2.equal(tree, result, int_equal, string_equal) |> should.be_true
|
||||
}
|
||||
|
||||
/// Тест ассоциативности
|
||||
// Тест ассоциативности
|
||||
pub fn monoid_associativity_test() {
|
||||
let tree1 = lab2.empty() |> lab2.insert(1, "one", int_compare)
|
||||
let tree2 = lab2.empty() |> lab2.insert(2, "two", int_compare)
|
||||
@@ -197,7 +195,7 @@ pub fn monoid_associativity_test() {
|
||||
lab2.contains(right_assoc, 3, int_compare) |> should.be_true
|
||||
}
|
||||
|
||||
/// Тест инвариантов красно-чёрного дерева (упрощённая проверка)
|
||||
// Тест инвариантов красно-чёрного дерева (упрощённая проверка)
|
||||
pub fn red_black_invariant_test() {
|
||||
// Создаём большое дерево и проверяем, что поиск работает корректно
|
||||
let tree =
|
||||
@@ -228,7 +226,7 @@ pub fn red_black_invariant_test() {
|
||||
all_found |> should.be_true
|
||||
}
|
||||
|
||||
/// Тест обновления существующего ключа
|
||||
// Тест обновления существующего ключа
|
||||
pub fn update_existing_key_test() {
|
||||
let tree =
|
||||
lab2.empty()
|
||||
@@ -239,7 +237,7 @@ pub fn update_existing_key_test() {
|
||||
lab2.lookup(tree, 1, int_compare) |> should.equal(option.Some("new"))
|
||||
}
|
||||
|
||||
/// Тест свойства: insert -> lookup должен возвращать вставленное значение
|
||||
// Тест свойства: insert -> lookup должен возвращать вставленное значение
|
||||
pub fn insert_lookup_property_test() {
|
||||
let test_cases = [
|
||||
#(1, "one"),
|
||||
@@ -255,7 +253,7 @@ pub fn insert_lookup_property_test() {
|
||||
})
|
||||
}
|
||||
|
||||
/// Тест свойства: size увеличивается при вставке новых элементов
|
||||
// Тест свойства: size увеличивается при вставке новых элементов
|
||||
pub fn insert_size_property_test() {
|
||||
let tree = lab2.empty()
|
||||
lab2.size(tree) |> should.equal(0)
|
||||
@@ -274,7 +272,7 @@ pub fn insert_size_property_test() {
|
||||
lab2.size(tree4) |> should.equal(3)
|
||||
}
|
||||
|
||||
/// Тест свойства: filter сохраняет порядок и структуру
|
||||
// Тест свойства: filter сохраняет порядок и структуру
|
||||
pub fn filter_property_test() {
|
||||
let original =
|
||||
lab2.from_list(
|
||||
@@ -298,7 +296,7 @@ pub fn filter_property_test() {
|
||||
lab2.contains(partial_filter, 5, int_compare) |> should.be_true
|
||||
}
|
||||
|
||||
/// Тест свойства: map сохраняет структуру дерева
|
||||
// Тест свойства: map сохраняет структуру дерева
|
||||
pub fn map_property_test() {
|
||||
let original = lab2.from_list([#(1, 10), #(2, 20), #(3, 30)], int_compare)
|
||||
|
||||
@@ -314,7 +312,7 @@ pub fn map_property_test() {
|
||||
lab2.lookup(doubled, 3, int_compare) |> should.equal(option.Some(60))
|
||||
}
|
||||
|
||||
/// Тест свойства: fold_left и fold_right дают корректные результаты
|
||||
// Тест свойства: fold_left и fold_right дают корректные результаты
|
||||
pub fn fold_property_test() {
|
||||
let tree =
|
||||
lab2.from_list([#(1, 1), #(2, 2), #(3, 3), #(4, 4), #(5, 5)], int_compare)
|
||||
@@ -334,7 +332,7 @@ pub fn fold_property_test() {
|
||||
count_right |> should.equal(5)
|
||||
}
|
||||
|
||||
/// Тест свойства: to_list -> from_list должно давать эквивалентное дерево
|
||||
// Тест свойства: to_list -> from_list должно давать эквивалентное дерево
|
||||
pub fn list_roundtrip_property_test() {
|
||||
let original_list = [#(3, "c"), #(1, "a"), #(4, "d"), #(2, "b")]
|
||||
let tree = lab2.from_list(original_list, int_compare)
|
||||
@@ -352,7 +350,7 @@ pub fn list_roundtrip_property_test() {
|
||||
})
|
||||
}
|
||||
|
||||
/// Тест инвариантов моноида (коммутативность для деревьев с разными ключами)
|
||||
// Тест инвариантов моноида (коммутативность для деревьев с разными ключами)
|
||||
pub fn monoid_commutativity_test() {
|
||||
let tree1 = lab2.from_list([#(1, "a"), #(2, "b")], int_compare)
|
||||
let tree2 = lab2.from_list([#(3, "c"), #(4, "d")], int_compare)
|
||||
@@ -371,3 +369,41 @@ pub fn monoid_commutativity_test() {
|
||||
lab2.contains(concat2, key, int_compare) |> should.be_true
|
||||
})
|
||||
}
|
||||
|
||||
// Тест семантического равенства деревьев
|
||||
// Проверяет, что деревья с одинаковым содержимым, но разной структурой считаются равными
|
||||
pub fn semantic_equal_test() {
|
||||
// Создаём два дерева с одинаковыми данными, но разным порядком вставки
|
||||
let tree1 =
|
||||
lab2.empty()
|
||||
|> lab2.insert(2, "two", int_compare)
|
||||
|> lab2.insert(1, "one", int_compare)
|
||||
|> lab2.insert(3, "three", int_compare)
|
||||
|
||||
let tree2 =
|
||||
lab2.empty()
|
||||
|> lab2.insert(1, "one", int_compare)
|
||||
|> lab2.insert(3, "three", int_compare)
|
||||
|> lab2.insert(2, "two", int_compare)
|
||||
|
||||
// Семантически они должны быть равны
|
||||
lab2.semantic_equal(tree1, tree2, int_compare, string_equal) |> should.be_true
|
||||
// Структурно они могут быть разными (зависит от балансировки)
|
||||
// lab2.equal(tree1, tree2, int_equal, string_equal) может быть false
|
||||
}
|
||||
|
||||
// Тест структурного равенства без учёта цветов узлов
|
||||
pub fn structure_equal_test() {
|
||||
let tree1 =
|
||||
lab2.empty()
|
||||
|> lab2.insert(2, "two", int_compare)
|
||||
|> lab2.insert(1, "one", int_compare)
|
||||
|
||||
let tree2 =
|
||||
lab2.empty()
|
||||
|> lab2.insert(2, "two", int_compare)
|
||||
|> lab2.insert(1, "one", int_compare)
|
||||
|
||||
// Структурно должны быть равны (игнорируя цвета)
|
||||
lab2.structure_equal(tree1, tree2, int_equal, string_equal) |> should.be_true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user