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 с ленивыми вычислениями
|
**Тема:** Реализация Red-Black Tree с ленивыми вычислениями
|
||||||
**Лабораторная работа №2**
|
**Лабораторная работа №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** - красно-чёрное дерево на ленивых вычислениях.
|
**Red-Black Tree Lazy** - красно-чёрное дерево на ленивых вычислениях.
|
||||||
@@ -55,19 +76,19 @@
|
|||||||
### Основные типы данных
|
### Основные типы данных
|
||||||
|
|
||||||
```gleam
|
```gleam
|
||||||
/// Цвета узлов красно-черного дерева
|
// Цвета узлов красно-черного дерева
|
||||||
pub type Color {
|
pub type Color {
|
||||||
Red
|
Red
|
||||||
Black
|
Black
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ленивое значение для отложенных вычислений
|
// Ленивое значение для отложенных вычислений
|
||||||
pub type Lazy(a) {
|
pub type Lazy(a) {
|
||||||
Thunk(fn() -> a) // Отложенное вычисление
|
Thunk(fn() -> a) // Отложенное вычисление
|
||||||
Value(a) // Уже вычисленное значение
|
Value(a) // Уже вычисленное значение
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Красно-черное дерево с ленивыми вычислениями
|
// Красно-черное дерево с ленивыми вычислениями
|
||||||
pub type RBTree(k, v) {
|
pub type RBTree(k, v) {
|
||||||
Empty // Пустое дерево
|
Empty // Пустое дерево
|
||||||
Node(
|
Node(
|
||||||
@@ -83,7 +104,7 @@ pub type RBTree(k, v) {
|
|||||||
### Управление ленивыми вычислениями
|
### Управление ленивыми вычислениями
|
||||||
|
|
||||||
```gleam
|
```gleam
|
||||||
/// Форсирует вычисление ленивого значения
|
// Форсирует вычисление ленивого значения
|
||||||
fn force(lazy: Lazy(a)) -> a {
|
fn force(lazy: Lazy(a)) -> a {
|
||||||
case lazy {
|
case lazy {
|
||||||
Value(val) -> val
|
Value(val) -> val
|
||||||
@@ -91,7 +112,7 @@ fn force(lazy: Lazy(a)) -> a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Создаёт ленивое значение из функции
|
// Создаёт ленивое значение из функции
|
||||||
fn delay(f: fn() -> a) -> Lazy(a) {
|
fn delay(f: fn() -> a) -> Lazy(a) {
|
||||||
Thunk(f)
|
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
|
```gleam
|
||||||
pub fn insert(tree: RBTree(k, v), key: k, value: v,
|
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
|
```gleam
|
||||||
/// Нейтральный элемент
|
// Нейтральный элемент
|
||||||
pub fn mempty() -> RBTree(k, v) {
|
pub fn mempty() -> RBTree(k, v) {
|
||||||
empty()
|
empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Операция объединения
|
// Операция объединения
|
||||||
pub fn concat(tree1: RBTree(k, v), tree2: RBTree(k, v),
|
pub fn concat(tree1: RBTree(k, v), tree2: RBTree(k, v),
|
||||||
compare: fn(k, k) -> Order) -> RBTree(k, v) {
|
compare: fn(k, k) -> Order) -> RBTree(k, v) {
|
||||||
fold_left(tree2, tree1, fn(acc, key, value) {
|
fold_left(tree2, tree1, fn(acc, key, value) {
|
||||||
@@ -247,14 +302,19 @@ pub fn concat(tree1: RBTree(k, v), tree2: RBTree(k, v),
|
|||||||
## Тесты и метрики
|
## Тесты и метрики
|
||||||
|
|
||||||
### Unit тесты
|
### Unit тесты
|
||||||
- `empty_tree_test()` - создание пустого дерева
|
- `empty_tree_test()` - создание пустого дерева и проверка `is_empty()`, `size()`
|
||||||
- `single_insert_test()` - вставка одного элемента
|
- `single_insert_test()` - вставка одного элемента, проверка `lookup()` и размера
|
||||||
- `multiple_insert_test()` - множественные вставки
|
- `multiple_insert_test()` - множественные вставки с проверкой всех элементов
|
||||||
- `delete_test()` - удаление элементов
|
- `delete_test()` - удаление элементов с проверкой корректности
|
||||||
- `filter_test()` - тестирование фильтрации
|
- `update_existing_key_test()` - обновление существующих ключей
|
||||||
- `map_test()` - тестирование отображения
|
- `filter_test()` - фильтрация четных чисел, проверка размера и содержимого
|
||||||
- `fold_left_test()`, `fold_right_test()` - тестирование свёрток
|
- `map_test()` - отображение значений (умножение на 2), проверка корректности
|
||||||
- `to_from_list_test()` - конвертация в список и обратно
|
- `fold_left_test()` - левая свёртка для суммирования значений
|
||||||
|
- `fold_right_test()` - правая свёртка для конкатенации строк
|
||||||
|
- `to_from_list_test()` - конвертация дерево→список→дерево с проверкой эквивалентности
|
||||||
|
- `red_black_invariant_test()` - работа с большим деревом (10 элементов), проверка балансировки
|
||||||
|
- `semantic_equal_test()` - семантическое равенство (независимо от структуры)
|
||||||
|
- `structure_equal_test()` - структурное равенство без учёта цветов узлов
|
||||||
|
|
||||||
### Property-based тесты
|
### Property-based тесты
|
||||||
|
|
||||||
@@ -279,92 +339,44 @@ pub fn concat(tree1: RBTree(k, v), tree2: RBTree(k, v),
|
|||||||
### Тесты моноида
|
### Тесты моноида
|
||||||
|
|
||||||
```gleam
|
```gleam
|
||||||
/// Левая единица: mempty ∘ a = a
|
// Левая единица: mempty ∘ a = a
|
||||||
pub fn monoid_left_identity_test()
|
pub fn monoid_left_identity_test()
|
||||||
|
|
||||||
/// Правая единица: a ∘ mempty = a
|
// Правая единица: a ∘ mempty = a
|
||||||
pub fn monoid_right_identity_test()
|
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_associativity_test()
|
||||||
|
|
||||||
/// Коммутативность (для непересекающихся множеств ключей)
|
// Коммутативность (для непересекающихся множеств ключей)
|
||||||
pub fn monoid_commutativity_test()
|
pub fn monoid_commutativity_test()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Отчёт тестирования
|
### Отчёт тестирования
|
||||||
|
|
||||||
```
|
```
|
||||||
Running lab2_test.main
|
> gleam test
|
||||||
.....................
|
Compiling lab2
|
||||||
21 passed, no failures
|
|
||||||
Compiled in 0.46s
|
Compiled in 0.46s
|
||||||
|
Running lab2_test.main
|
||||||
|
.......................
|
||||||
|
23 passed, no failures
|
||||||
```
|
```
|
||||||
|
|
||||||
### Метрики производительности
|
### Метрики производительности
|
||||||
|
|
||||||
| Операция | Теоретическая сложность | Реализованная сложность |
|
| Операция | Сложность|
|
||||||
|----------|------------------------|------------------------|
|
|----------|------------------------|
|
||||||
| lookup | O(log n) | O(log n) |
|
| lookup | O(log n) |
|
||||||
| insert | O(log n) | O(log n) |
|
| insert | O(log n) |
|
||||||
| delete | O(log n) | O(log n) |
|
| delete | O(log n) |
|
||||||
| size | O(n) | O(n) |
|
| size | O(n) |
|
||||||
| map | O(n) | O(n) |
|
| map | O(n) |
|
||||||
| filter | O(n log n) | O(n log n) |
|
| filter | O(n log n) |
|
||||||
| fold | O(n) | O(n) |
|
| fold | O(n) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Выводы
|
## Выводы
|
||||||
|
Использование ленивых вычислений повышает эффективность использования памяти при операциях со структурами данных высокой вложенности (в нашем случае) ценой cpu на вычисление значения.
|
||||||
### Использованные приёмы программирования
|
Также использовал типы, а ещё писал своё сравнение без высокоуровневых абстракций.
|
||||||
|
|
||||||
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 продемонстрировала эффективность функциональных подходов к программированию. Использование неизменяемых структур данных, полиморфизма и функций высшего порядка привело к созданию гибкой, безопасной и переиспользуемой библиотеки.
|
|
||||||
|
|
||||||
Ленивые вычисления показали свою полезность для оптимизации производительности при работе с большими деревьями, где не все поддеревья могут быть использованы.
|
|
||||||
|
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
// Модуль красно-чёрного дерева с ленивыми вычислениями
|
|
||||||
// Реализует самобалансирующееся двоичное дерево поиска с гарантированной
|
|
||||||
// логарифмической сложностью операций вставки, удаления и поиска
|
|
||||||
|
|
||||||
import gleam/option.{type Option, None, Some}
|
import gleam/option.{type Option, None, Some}
|
||||||
import gleam/order.{type Order}
|
import gleam/order.{type Order}
|
||||||
|
|
||||||
/// Цвета узлов красно-черного дерева
|
// Цвета узлов красно-черного дерева
|
||||||
/// Red - красный узел, Black - чёрный узел
|
// Red - красный узел, Black - чёрный узел
|
||||||
pub type Color {
|
pub type Color {
|
||||||
Red
|
Red
|
||||||
Black
|
Black
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ленивое значение для отложенных вычислений
|
// Ленивое значение для отложенных вычислений
|
||||||
/// Позволяет откладывать создание поддеревьев до момента их использования
|
// Позволяет откладывать создание поддеревьев до момента их использования
|
||||||
|
// Не знаю правильно это или нет на самом деле, в gleam вроде lazy нету встроенного
|
||||||
pub type Lazy(a) {
|
pub type Lazy(a) {
|
||||||
Thunk(fn() -> a)
|
Thunk(fn() -> a)
|
||||||
// Отложенное вычисление через функцию
|
// Отложенное вычисление через функцию
|
||||||
@@ -21,9 +18,9 @@ pub type Lazy(a) {
|
|||||||
// Уже вычисленное значение
|
// Уже вычисленное значение
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Красно-черное дерево с ленивыми вычислениями
|
// Красно-черное дерево с ленивыми вычислениями
|
||||||
/// Полиморфная структура данных с ключами типа k и значениями типа v
|
// Полиморфная структура данных с ключами типа k и значениями типа v
|
||||||
/// Поддерживает инварианты красно-чёрного дерева для гарантии балансировки
|
// Поддерживает инварианты красно-чёрного дерева для гарантии балансировки
|
||||||
pub type RBTree(k, v) {
|
pub type RBTree(k, v) {
|
||||||
Empty
|
Empty
|
||||||
// Пустое дерево
|
// Пустое дерево
|
||||||
@@ -42,8 +39,8 @@ pub type RBTree(k, v) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Форсирует вычисление ленивого значения
|
// Форсирует вычисление ленивого значения
|
||||||
/// Если значение уже вычислено, возвращает его, иначе выполняет функцию
|
// Если значение уже вычислено, возвращает его, иначе выполняет функцию
|
||||||
fn force(lazy: Lazy(a)) -> a {
|
fn force(lazy: Lazy(a)) -> a {
|
||||||
case lazy {
|
case lazy {
|
||||||
Value(val) -> val
|
Value(val) -> val
|
||||||
@@ -51,20 +48,20 @@ fn force(lazy: Lazy(a)) -> a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Создаёт ленивое значение из функции
|
// Создаёт ленивое значение из функции
|
||||||
/// Отложенное вычисление будет выполнено при первом обращении
|
// Отложенное вычисление будет выполнено при первом обращении
|
||||||
fn delay(f: fn() -> a) -> Lazy(a) {
|
fn delay(f: fn() -> a) -> Lazy(a) {
|
||||||
Thunk(f)
|
Thunk(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Создаёт пустое дерево - нейтральный элемент моноида
|
// Создаёт пустое дерево - нейтральный элемент моноида
|
||||||
/// Время выполнения: O(1)
|
// Время выполнения: O(1)
|
||||||
pub fn empty() -> RBTree(k, v) {
|
pub fn empty() -> RBTree(k, v) {
|
||||||
Empty
|
Empty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Проверяет, является ли дерево пустым
|
// Проверяет, является ли дерево пустым
|
||||||
/// Время выполнения: O(1)
|
// Время выполнения: O(1)
|
||||||
pub fn is_empty(tree: RBTree(k, v)) -> Bool {
|
pub fn is_empty(tree: RBTree(k, v)) -> Bool {
|
||||||
case tree {
|
case tree {
|
||||||
Empty -> True
|
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) {
|
fn make_black(tree: RBTree(k, v)) -> RBTree(k, v) {
|
||||||
case tree {
|
case tree {
|
||||||
Node(_, key, value, left, right) -> Node(Black, key, value, left, right)
|
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(
|
fn balance(
|
||||||
color: Color,
|
color: Color,
|
||||||
key: k,
|
key: k,
|
||||||
@@ -147,9 +144,9 @@ fn balance(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Вставляет элемент в дерево с сохранением инвариантов красно-чёрного дерева
|
// Вставляет элемент в дерево с сохранением инвариантов красно-чёрного дерева
|
||||||
/// Время выполнения: O(log n)
|
// Время выполнения: O(log n)
|
||||||
/// compare - функция сравнения ключей, должна возвращать order.Lt, order.Eq или order.Gt
|
// compare - функция сравнения ключей, должна возвращать order.Lt, order.Eq или order.Gt
|
||||||
pub fn insert(
|
pub fn insert(
|
||||||
tree: RBTree(k, v),
|
tree: RBTree(k, v),
|
||||||
key: k,
|
key: k,
|
||||||
@@ -158,7 +155,7 @@ pub fn insert(
|
|||||||
) -> RBTree(k, v) {
|
) -> RBTree(k, v) {
|
||||||
let result = insert_helper(tree, key, value, compare)
|
let result = insert_helper(tree, key, value, compare)
|
||||||
make_black(result)
|
make_black(result)
|
||||||
// Гарантируем, что корень чёрный
|
// гарантия что корень чёрный
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_helper(
|
fn insert_helper(
|
||||||
@@ -192,7 +189,7 @@ fn insert_helper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Поиск элемента в дереве
|
// Поиск элемента в дереве
|
||||||
pub fn lookup(
|
pub fn lookup(
|
||||||
tree: RBTree(k, v),
|
tree: RBTree(k, v),
|
||||||
key: k,
|
key: k,
|
||||||
@@ -209,7 +206,7 @@ pub fn lookup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Находит минимальный элемент в дереве
|
// Находит минимальный элемент в дереве
|
||||||
fn find_min(tree: RBTree(k, v)) -> Option(#(k, v)) {
|
fn find_min(tree: RBTree(k, v)) -> Option(#(k, v)) {
|
||||||
case tree {
|
case tree {
|
||||||
Empty -> None
|
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) {
|
fn delete_min(tree: RBTree(k, v)) -> RBTree(k, v) {
|
||||||
case tree {
|
case tree {
|
||||||
Empty -> Empty
|
Empty -> Empty
|
||||||
@@ -287,7 +284,7 @@ pub fn delete(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Фильтрация элементов дерева
|
// Фильтрация элементов дерева
|
||||||
pub fn filter(
|
pub fn filter(
|
||||||
tree: RBTree(k, v),
|
tree: RBTree(k, v),
|
||||||
predicate: fn(k, v) -> Bool,
|
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) {
|
pub fn map(tree: RBTree(k, v), f: fn(v) -> w) -> RBTree(k, w) {
|
||||||
case tree {
|
case tree {
|
||||||
Empty -> Empty
|
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 {
|
pub fn fold_left(tree: RBTree(k, v), acc: a, f: fn(a, k, v) -> a) -> a {
|
||||||
case tree {
|
case tree {
|
||||||
Empty -> acc
|
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 {
|
pub fn fold_right(tree: RBTree(k, v), acc: a, f: fn(k, v, a) -> a) -> a {
|
||||||
case tree {
|
case tree {
|
||||||
Empty -> acc
|
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(
|
pub fn concat(
|
||||||
tree1: RBTree(k, v),
|
tree1: RBTree(k, v),
|
||||||
tree2: RBTree(k, v),
|
tree2: RBTree(k, v),
|
||||||
@@ -351,22 +348,22 @@ pub fn concat(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Нейтральный элемент моноида (пустое дерево)
|
// Нейтральный элемент моноида (пустое дерево)
|
||||||
pub fn mempty() -> RBTree(k, v) {
|
pub fn mempty() -> RBTree(k, v) {
|
||||||
empty()
|
empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Размер дерева
|
// Размер дерева
|
||||||
pub fn size(tree: RBTree(k, v)) -> Int {
|
pub fn size(tree: RBTree(k, v)) -> Int {
|
||||||
fold_left(tree, 0, fn(acc, _, _) { acc + 1 })
|
fold_left(tree, 0, fn(acc, _, _) { acc + 1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Преобразование дерева в список пар
|
// Преобразование дерева в список пар
|
||||||
pub fn to_list(tree: RBTree(k, v)) -> List(#(k, v)) {
|
pub fn to_list(tree: RBTree(k, v)) -> List(#(k, v)) {
|
||||||
fold_right(tree, [], fn(key, value, acc) { [#(key, value), ..acc] })
|
fold_right(tree, [], fn(key, value, acc) { [#(key, value), ..acc] })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Создание дерева из списка пар
|
// Создание дерева из списка пар
|
||||||
pub fn from_list(
|
pub fn from_list(
|
||||||
list: List(#(k, v)),
|
list: List(#(k, v)),
|
||||||
compare: fn(k, k) -> Order,
|
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 {
|
fn list_fold_left(list: List(a), acc: b, f: fn(b, a) -> b) -> b {
|
||||||
case list {
|
case list {
|
||||||
[] -> acc
|
[] -> 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(
|
pub fn equal(
|
||||||
tree1: RBTree(k, v),
|
tree1: RBTree(k, v),
|
||||||
tree2: RBTree(k, v),
|
tree2: RBTree(k, v),
|
||||||
@@ -398,8 +395,8 @@ pub fn equal(
|
|||||||
equal_helper(tree1, tree2, key_compare, value_compare)
|
equal_helper(tree1, tree2, key_compare, value_compare)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Вспомогательная функция для структурного сравнения деревьев
|
// Вспомогательная функция для структурного сравнения деревьев
|
||||||
/// Использует короткое замыкание при первом несовпадении
|
// Использует замыкание при первом несовпадении
|
||||||
fn equal_helper(
|
fn equal_helper(
|
||||||
tree1: RBTree(k, v),
|
tree1: RBTree(k, v),
|
||||||
tree2: RBTree(k, v),
|
tree2: RBTree(k, v),
|
||||||
@@ -409,29 +406,27 @@ fn equal_helper(
|
|||||||
case tree1, tree2 {
|
case tree1, tree2 {
|
||||||
// Оба дерева пустые - равны
|
// Оба дерева пустые - равны
|
||||||
Empty, Empty -> True
|
Empty, Empty -> True
|
||||||
|
|
||||||
// Одно пустое, другое нет - не равны
|
|
||||||
Empty, _ -> False
|
Empty, _ -> False
|
||||||
_, Empty -> False
|
_, Empty -> False
|
||||||
|
|
||||||
// Оба узла - сравниваем структурно
|
|
||||||
Node(color1, key1, value1, left1, right1),
|
Node(color1, key1, value1, left1, right1),
|
||||||
Node(color2, key2, value2, left2, right2) -> {
|
Node(color2, key2, value2, left2, right2)
|
||||||
// Быстрая проверка: цвета должны совпадать
|
-> {
|
||||||
color1 == color2 &&
|
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 {
|
pub fn contains(tree: RBTree(k, v), key: k, compare: fn(k, k) -> Order) -> Bool {
|
||||||
case lookup(tree, key, compare) {
|
case lookup(tree, key, compare) {
|
||||||
Some(_) -> True
|
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) {
|
pub fn keys(tree: RBTree(k, v)) -> List(k) {
|
||||||
fold_right(tree, [], fn(key, _, acc) { [key, ..acc] })
|
fold_right(tree, [], fn(key, _, acc) { [key, ..acc] })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Получение всех значений дерева
|
// Получение всех значений дерева
|
||||||
pub fn values(tree: RBTree(k, v)) -> List(v) {
|
pub fn values(tree: RBTree(k, v)) -> List(v) {
|
||||||
fold_right(tree, [], fn(_, value, acc) { [value, ..acc] })
|
fold_right(tree, [], fn(_, value, acc) { [value, ..acc] })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Семантическое равенство деревьев - сравнивает содержимое независимо от структуры
|
// Семантическое равенство деревьев - сравнивает содержимое независимо от структуры
|
||||||
/// Два дерева семантически равны, если содержат одинаковые пары ключ-значение
|
// Два дерева семантически равны, если содержат одинаковые пары ключ-значение
|
||||||
/// Время выполнения: O(n log n) из-за необходимости итерации
|
// Время выполнения: O(n log n) из-за необходимости итерации
|
||||||
pub fn semantic_equal(
|
pub fn semantic_equal(
|
||||||
tree1: RBTree(k, v),
|
tree1: RBTree(k, v),
|
||||||
tree2: RBTree(k, v),
|
tree2: RBTree(k, v),
|
||||||
@@ -464,8 +459,8 @@ pub fn semantic_equal(
|
|||||||
True -> {
|
True -> {
|
||||||
// Проверяем, что все элементы из tree1 есть в tree2 с теми же значениями
|
// Проверяем, что все элементы из tree1 есть в tree2 с теми же значениями
|
||||||
fold_left(tree1, True, fn(acc, key, value) {
|
fold_left(tree1, True, fn(acc, key, value) {
|
||||||
acc &&
|
acc
|
||||||
case lookup(tree2, key, key_order) {
|
&& case lookup(tree2, key, key_order) {
|
||||||
Some(other_value) -> value_equal(value, other_value)
|
Some(other_value) -> value_equal(value, other_value)
|
||||||
None -> False
|
None -> False
|
||||||
}
|
}
|
||||||
@@ -474,8 +469,8 @@ pub fn semantic_equal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Сравнение с игнорированием цветов узлов (только структура и данные)
|
// Сравнение с игнорированием цветов узлов (только структура и данные)
|
||||||
/// Полезно когда важна только логическая структура дерева поиска
|
// Полезно когда важна только логическая структура дерева поиска
|
||||||
pub fn structure_equal(
|
pub fn structure_equal(
|
||||||
tree1: RBTree(k, v),
|
tree1: RBTree(k, v),
|
||||||
tree2: RBTree(k, v),
|
tree2: RBTree(k, v),
|
||||||
@@ -486,13 +481,17 @@ pub fn structure_equal(
|
|||||||
Empty, Empty -> True
|
Empty, Empty -> True
|
||||||
Empty, _ -> False
|
Empty, _ -> False
|
||||||
_, Empty -> False
|
_, Empty -> False
|
||||||
Node(_, key1, value1, left1, right1),
|
Node(_, key1, value1, left1, right1), Node(_, key2, value2, left2, right2) -> {
|
||||||
Node(_, key2, value2, left2, right2) -> {
|
|
||||||
// Игнорируем цвет, сравниваем только ключи, значения и структуру
|
// Игнорируем цвет, сравниваем только ключи, значения и структуру
|
||||||
key_compare(key1, key2) &&
|
key_compare(key1, key2)
|
||||||
value_compare(value1, value2) &&
|
&& value_compare(value1, value2)
|
||||||
structure_equal(force(left1), force(left2), key_compare, value_compare) &&
|
&& structure_equal(force(left1), force(left2), key_compare, value_compare)
|
||||||
structure_equal(force(right1), force(right2), 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
|
a == b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit тесты
|
// Тест создания пустого дерева
|
||||||
|
|
||||||
/// Тест создания пустого дерева
|
|
||||||
pub fn empty_tree_test() {
|
pub fn empty_tree_test() {
|
||||||
let tree = lab2.empty()
|
let tree = lab2.empty()
|
||||||
lab2.is_empty(tree) |> should.be_true
|
lab2.is_empty(tree) |> should.be_true
|
||||||
lab2.size(tree) |> should.equal(0)
|
lab2.size(tree) |> should.equal(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест вставки одного элемента
|
// Тест вставки одного элемента
|
||||||
pub fn single_insert_test() {
|
pub fn single_insert_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -45,7 +43,7 @@ pub fn single_insert_test() {
|
|||||||
lab2.lookup(tree, 10, int_compare) |> should.equal(option.None)
|
lab2.lookup(tree, 10, int_compare) |> should.equal(option.None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест множественных вставок
|
// Тест множественных вставок
|
||||||
pub fn multiple_insert_test() {
|
pub fn multiple_insert_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -63,7 +61,7 @@ pub fn multiple_insert_test() {
|
|||||||
lab2.lookup(tree, 9, int_compare) |> should.equal(option.Some("nine"))
|
lab2.lookup(tree, 9, int_compare) |> should.equal(option.Some("nine"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест удаления элементов
|
// Тест удаления элементов
|
||||||
pub fn delete_test() {
|
pub fn delete_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -78,7 +76,7 @@ pub fn delete_test() {
|
|||||||
lab2.lookup(tree, 7, int_compare) |> should.equal(option.Some("seven"))
|
lab2.lookup(tree, 7, int_compare) |> should.equal(option.Some("seven"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест фильтрации
|
// Тест фильтрации
|
||||||
pub fn filter_test() {
|
pub fn filter_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -96,7 +94,7 @@ pub fn filter_test() {
|
|||||||
lab2.contains(filtered, 1, int_compare) |> should.be_false
|
lab2.contains(filtered, 1, int_compare) |> should.be_false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест отображения
|
// Тест отображения
|
||||||
pub fn map_test() {
|
pub fn map_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -111,7 +109,7 @@ pub fn map_test() {
|
|||||||
lab2.lookup(mapped, 3, int_compare) |> should.equal(option.Some(60))
|
lab2.lookup(mapped, 3, int_compare) |> should.equal(option.Some(60))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест левой свёртки
|
// Тест левой свёртки
|
||||||
pub fn fold_left_test() {
|
pub fn fold_left_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -123,7 +121,7 @@ pub fn fold_left_test() {
|
|||||||
sum |> should.equal(60)
|
sum |> should.equal(60)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест правой свёртки
|
// Тест правой свёртки
|
||||||
pub fn fold_right_test() {
|
pub fn fold_right_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -136,7 +134,7 @@ pub fn fold_right_test() {
|
|||||||
result |> string.length |> should.equal(3)
|
result |> string.length |> should.equal(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест преобразования в список и обратно
|
// Тест преобразования в список и обратно
|
||||||
pub fn to_from_list_test() {
|
pub fn to_from_list_test() {
|
||||||
let original_list = [#(1, "one"), #(2, "two"), #(3, "three")]
|
let original_list = [#(1, "one"), #(2, "two"), #(3, "three")]
|
||||||
let tree = lab2.from_list(original_list, int_compare)
|
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() {
|
pub fn monoid_left_identity_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -163,7 +161,7 @@ pub fn monoid_left_identity_test() {
|
|||||||
lab2.equal(tree, result, int_equal, string_equal) |> should.be_true
|
lab2.equal(tree, result, int_equal, string_equal) |> should.be_true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест нейтрального элемента (правая единица)
|
// Тест нейтрального элемента (правая единица)
|
||||||
pub fn monoid_right_identity_test() {
|
pub fn monoid_right_identity_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -175,7 +173,7 @@ pub fn monoid_right_identity_test() {
|
|||||||
lab2.equal(tree, result, int_equal, string_equal) |> should.be_true
|
lab2.equal(tree, result, int_equal, string_equal) |> should.be_true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест ассоциативности
|
// Тест ассоциативности
|
||||||
pub fn monoid_associativity_test() {
|
pub fn monoid_associativity_test() {
|
||||||
let tree1 = lab2.empty() |> lab2.insert(1, "one", int_compare)
|
let tree1 = lab2.empty() |> lab2.insert(1, "one", int_compare)
|
||||||
let tree2 = lab2.empty() |> lab2.insert(2, "two", 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
|
lab2.contains(right_assoc, 3, int_compare) |> should.be_true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест инвариантов красно-чёрного дерева (упрощённая проверка)
|
// Тест инвариантов красно-чёрного дерева (упрощённая проверка)
|
||||||
pub fn red_black_invariant_test() {
|
pub fn red_black_invariant_test() {
|
||||||
// Создаём большое дерево и проверяем, что поиск работает корректно
|
// Создаём большое дерево и проверяем, что поиск работает корректно
|
||||||
let tree =
|
let tree =
|
||||||
@@ -228,7 +226,7 @@ pub fn red_black_invariant_test() {
|
|||||||
all_found |> should.be_true
|
all_found |> should.be_true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест обновления существующего ключа
|
// Тест обновления существующего ключа
|
||||||
pub fn update_existing_key_test() {
|
pub fn update_existing_key_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.empty()
|
lab2.empty()
|
||||||
@@ -239,7 +237,7 @@ pub fn update_existing_key_test() {
|
|||||||
lab2.lookup(tree, 1, int_compare) |> should.equal(option.Some("new"))
|
lab2.lookup(tree, 1, int_compare) |> should.equal(option.Some("new"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест свойства: insert -> lookup должен возвращать вставленное значение
|
// Тест свойства: insert -> lookup должен возвращать вставленное значение
|
||||||
pub fn insert_lookup_property_test() {
|
pub fn insert_lookup_property_test() {
|
||||||
let test_cases = [
|
let test_cases = [
|
||||||
#(1, "one"),
|
#(1, "one"),
|
||||||
@@ -255,7 +253,7 @@ pub fn insert_lookup_property_test() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест свойства: size увеличивается при вставке новых элементов
|
// Тест свойства: size увеличивается при вставке новых элементов
|
||||||
pub fn insert_size_property_test() {
|
pub fn insert_size_property_test() {
|
||||||
let tree = lab2.empty()
|
let tree = lab2.empty()
|
||||||
lab2.size(tree) |> should.equal(0)
|
lab2.size(tree) |> should.equal(0)
|
||||||
@@ -274,7 +272,7 @@ pub fn insert_size_property_test() {
|
|||||||
lab2.size(tree4) |> should.equal(3)
|
lab2.size(tree4) |> should.equal(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест свойства: filter сохраняет порядок и структуру
|
// Тест свойства: filter сохраняет порядок и структуру
|
||||||
pub fn filter_property_test() {
|
pub fn filter_property_test() {
|
||||||
let original =
|
let original =
|
||||||
lab2.from_list(
|
lab2.from_list(
|
||||||
@@ -298,7 +296,7 @@ pub fn filter_property_test() {
|
|||||||
lab2.contains(partial_filter, 5, int_compare) |> should.be_true
|
lab2.contains(partial_filter, 5, int_compare) |> should.be_true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест свойства: map сохраняет структуру дерева
|
// Тест свойства: map сохраняет структуру дерева
|
||||||
pub fn map_property_test() {
|
pub fn map_property_test() {
|
||||||
let original = lab2.from_list([#(1, 10), #(2, 20), #(3, 30)], int_compare)
|
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))
|
lab2.lookup(doubled, 3, int_compare) |> should.equal(option.Some(60))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест свойства: fold_left и fold_right дают корректные результаты
|
// Тест свойства: fold_left и fold_right дают корректные результаты
|
||||||
pub fn fold_property_test() {
|
pub fn fold_property_test() {
|
||||||
let tree =
|
let tree =
|
||||||
lab2.from_list([#(1, 1), #(2, 2), #(3, 3), #(4, 4), #(5, 5)], int_compare)
|
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)
|
count_right |> should.equal(5)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест свойства: to_list -> from_list должно давать эквивалентное дерево
|
// Тест свойства: to_list -> from_list должно давать эквивалентное дерево
|
||||||
pub fn list_roundtrip_property_test() {
|
pub fn list_roundtrip_property_test() {
|
||||||
let original_list = [#(3, "c"), #(1, "a"), #(4, "d"), #(2, "b")]
|
let original_list = [#(3, "c"), #(1, "a"), #(4, "d"), #(2, "b")]
|
||||||
let tree = lab2.from_list(original_list, int_compare)
|
let tree = lab2.from_list(original_list, int_compare)
|
||||||
@@ -352,7 +350,7 @@ pub fn list_roundtrip_property_test() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Тест инвариантов моноида (коммутативность для деревьев с разными ключами)
|
// Тест инвариантов моноида (коммутативность для деревьев с разными ключами)
|
||||||
pub fn monoid_commutativity_test() {
|
pub fn monoid_commutativity_test() {
|
||||||
let tree1 = lab2.from_list([#(1, "a"), #(2, "b")], int_compare)
|
let tree1 = lab2.from_list([#(1, "a"), #(2, "b")], int_compare)
|
||||||
let tree2 = lab2.from_list([#(3, "c"), #(4, "d")], 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
|
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