Косяк с функцией сравнения
This commit is contained in:
@@ -16,6 +16,7 @@ version = "1.0.0"
|
|||||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||||
gleam_regexp = ">= 1.1.1 and < 2.0.0"
|
gleam_regexp = ">= 1.1.1 and < 2.0.0"
|
||||||
colored = ">= 1.0.2 and < 2.0.0"
|
colored = ">= 1.0.2 and < 2.0.0"
|
||||||
|
iterator = ">= 0.0.5 and < 1.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ packages = [
|
|||||||
{ name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" },
|
{ name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" },
|
||||||
{ name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
|
{ name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
|
||||||
{ name = "gleeunit", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "CD701726CBCE5588B375D157B4391CFD0F2F134CD12D9B6998A395484DE05C58" },
|
{ name = "gleeunit", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "CD701726CBCE5588B375D157B4391CFD0F2F134CD12D9B6998A395484DE05C58" },
|
||||||
|
{ name = "iterator", version = "0.0.5", build_tools = ["rebar3"], requirements = [], otp_app = "iterator", source = "hex", outer_checksum = "8D7FECF393DC4D62FA95005AF3F17400DC0AF18A8FE87B6EC3107FBBB6009139" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[requirements]
|
[requirements]
|
||||||
@@ -13,3 +14,4 @@ colored = { version = ">= 1.0.2 and < 2.0.0" }
|
|||||||
gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" }
|
gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" }
|
||||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
|
iterator = { version = ">= 0.0.5 and < 1.0.0" }
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import gleam/list
|
|||||||
|
|
||||||
// 1. Хвостовая рекурсия
|
// 1. Хвостовая рекурсия
|
||||||
pub fn sum_multiples_tail_recursive(limit: Int) -> Int {
|
pub fn sum_multiples_tail_recursive(limit: Int) -> Int {
|
||||||
|
// Это всё можно оформить в одно выражение
|
||||||
|
// матчить лимит в 0 и в других случаях
|
||||||
case limit {
|
case limit {
|
||||||
n if n <= 0 -> 0
|
n if n <= 0 -> 0
|
||||||
_ -> sum_multiples_tail_recursive_helper(limit - 1, 0)
|
_ -> sum_multiples_tail_recursive_helper(limit - 1, 0)
|
||||||
|
|||||||
23
lab2/.github/workflows/test.yml
vendored
Normal file
23
lab2/.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: erlef/setup-beam@v1
|
||||||
|
with:
|
||||||
|
otp-version: "28"
|
||||||
|
gleam-version: "1.13.0"
|
||||||
|
rebar3-version: "3"
|
||||||
|
# elixir-version: "1"
|
||||||
|
- run: gleam deps download
|
||||||
|
- run: gleam test
|
||||||
|
- run: gleam format --check src test
|
||||||
4
lab2/.gitignore
vendored
Normal file
4
lab2/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
/build
|
||||||
|
erl_crash.dump
|
||||||
147
lab2/README.md
Normal file
147
lab2/README.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# 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 раза длиннее самого короткого пути, что обеспечивает логарифмическую сложность операций.
|
||||||
370
lab2/Report.md
Normal file
370
lab2/Report.md
Normal 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 продемонстрировала эффективность функциональных подходов к программированию. Использование неизменяемых структур данных, полиморфизма и функций высшего порядка привело к созданию гибкой, безопасной и переиспользуемой библиотеки.
|
||||||
|
|
||||||
|
Ленивые вычисления показали свою полезность для оптимизации производительности при работе с большими деревьями, где не все поддеревья могут быть использованы.
|
||||||
|
|
||||||
19
lab2/gleam.toml
Normal file
19
lab2/gleam.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name = "lab2"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||||
|
# your project to the Hex package manager.
|
||||||
|
#
|
||||||
|
# description = ""
|
||||||
|
# licences = ["Apache-2.0"]
|
||||||
|
# repository = { type = "github", user = "", repo = "" }
|
||||||
|
# links = [{ title = "Website", href = "" }]
|
||||||
|
#
|
||||||
|
# For a full reference of all the available options, you can have a look at
|
||||||
|
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||||
11
lab2/manifest.toml
Normal file
11
lab2/manifest.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# This file was generated by Gleam
|
||||||
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
{ name = "gleam_stdlib", version = "0.67.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "6CE3E4189A8B8EC2F73AB61A2FBDE49F159D6C9C61C49E3B3082E439F260D3D0" },
|
||||||
|
{ name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[requirements]
|
||||||
|
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||||
|
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
498
lab2/src/lab2.gleam
Normal file
498
lab2/src/lab2.gleam
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
// Модуль красно-чёрного дерева с ленивыми вычислениями
|
||||||
|
// Реализует самобалансирующееся двоичное дерево поиска с гарантированной
|
||||||
|
// логарифмической сложностью операций вставки, удаления и поиска
|
||||||
|
|
||||||
|
import gleam/option.{type Option, None, Some}
|
||||||
|
import gleam/order.{type Order}
|
||||||
|
|
||||||
|
/// Цвета узлов красно-черного дерева
|
||||||
|
/// Red - красный узел, Black - чёрный узел
|
||||||
|
pub type Color {
|
||||||
|
Red
|
||||||
|
Black
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ленивое значение для отложенных вычислений
|
||||||
|
/// Позволяет откладывать создание поддеревьев до момента их использования
|
||||||
|
pub type Lazy(a) {
|
||||||
|
Thunk(fn() -> a)
|
||||||
|
// Отложенное вычисление через функцию
|
||||||
|
Value(a)
|
||||||
|
// Уже вычисленное значение
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Красно-черное дерево с ленивыми вычислениями
|
||||||
|
/// Полиморфная структура данных с ключами типа k и значениями типа v
|
||||||
|
/// Поддерживает инварианты красно-чёрного дерева для гарантии балансировки
|
||||||
|
pub type RBTree(k, v) {
|
||||||
|
Empty
|
||||||
|
// Пустое дерево
|
||||||
|
Node(
|
||||||
|
// Узел дерева
|
||||||
|
color: Color,
|
||||||
|
// Цвет узла (красный или чёрный)
|
||||||
|
key: k,
|
||||||
|
// Ключ для поиска и сортировки
|
||||||
|
value: v,
|
||||||
|
// Значение, связанное с ключом
|
||||||
|
left: Lazy(RBTree(k, v)),
|
||||||
|
// Левое поддерево (ленивое)
|
||||||
|
right: Lazy(RBTree(k, v)),
|
||||||
|
// Правое поддерево (ленивое)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Форсирует вычисление ленивого значения
|
||||||
|
/// Если значение уже вычислено, возвращает его, иначе выполняет функцию
|
||||||
|
fn force(lazy: Lazy(a)) -> a {
|
||||||
|
case lazy {
|
||||||
|
Value(val) -> val
|
||||||
|
Thunk(f) -> f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Создаёт ленивое значение из функции
|
||||||
|
/// Отложенное вычисление будет выполнено при первом обращении
|
||||||
|
fn delay(f: fn() -> a) -> Lazy(a) {
|
||||||
|
Thunk(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Создаёт пустое дерево - нейтральный элемент моноида
|
||||||
|
/// Время выполнения: O(1)
|
||||||
|
pub fn empty() -> RBTree(k, v) {
|
||||||
|
Empty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверяет, является ли дерево пустым
|
||||||
|
/// Время выполнения: O(1)
|
||||||
|
pub fn is_empty(tree: RBTree(k, v)) -> Bool {
|
||||||
|
case tree {
|
||||||
|
Empty -> True
|
||||||
|
_ -> False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Создаёт узел с чёрным цветом
|
||||||
|
fn make_black(tree: RBTree(k, v)) -> RBTree(k, v) {
|
||||||
|
case tree {
|
||||||
|
Node(_, key, value, left, right) -> Node(Black, key, value, left, right)
|
||||||
|
Empty -> Empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Балансировка красно-черного дерева
|
||||||
|
fn 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: красный левый parent с красными child
|
||||||
|
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: красный правый parent с красным левым child
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Вставляет элемент в дерево с сохранением инвариантов красно-чёрного дерева
|
||||||
|
/// Время выполнения: O(log n)
|
||||||
|
/// compare - функция сравнения ключей, должна возвращать order.Lt, order.Eq или order.Gt
|
||||||
|
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)
|
||||||
|
// Гарантируем, что корень чёрный
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_helper(
|
||||||
|
tree: RBTree(k, v),
|
||||||
|
key: k,
|
||||||
|
value: v,
|
||||||
|
compare: fn(k, k) -> Order,
|
||||||
|
) -> RBTree(k, v) {
|
||||||
|
case tree {
|
||||||
|
Empty -> Node(Red, key, value, Value(Empty), Value(Empty))
|
||||||
|
Node(color, k, v, left, right) ->
|
||||||
|
case compare(key, k) {
|
||||||
|
order.Lt ->
|
||||||
|
balance(
|
||||||
|
color,
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
delay(fn() { insert_helper(force(left), key, value, compare) }),
|
||||||
|
right,
|
||||||
|
)
|
||||||
|
order.Gt ->
|
||||||
|
balance(
|
||||||
|
color,
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
left,
|
||||||
|
delay(fn() { insert_helper(force(right), key, value, compare) }),
|
||||||
|
)
|
||||||
|
order.Eq -> Node(color, key, value, left, right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Поиск элемента в дереве
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Находит минимальный элемент в дереве
|
||||||
|
fn find_min(tree: RBTree(k, v)) -> Option(#(k, v)) {
|
||||||
|
case tree {
|
||||||
|
Empty -> None
|
||||||
|
Node(_, key, value, left, _) ->
|
||||||
|
case force(left) {
|
||||||
|
Empty -> Some(#(key, value))
|
||||||
|
_ -> find_min(force(left))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Удаляет минимальный элемент из дерева
|
||||||
|
fn delete_min(tree: RBTree(k, v)) -> RBTree(k, v) {
|
||||||
|
case tree {
|
||||||
|
Empty -> Empty
|
||||||
|
Node(color, key, value, left, right) ->
|
||||||
|
case force(left) {
|
||||||
|
Empty -> force(right)
|
||||||
|
_ ->
|
||||||
|
Node(
|
||||||
|
color,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
delay(fn() { delete_min(force(left)) }),
|
||||||
|
right,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(
|
||||||
|
tree: RBTree(k, v),
|
||||||
|
key: k,
|
||||||
|
compare: fn(k, k) -> Order,
|
||||||
|
) -> RBTree(k, v) {
|
||||||
|
case tree {
|
||||||
|
Empty -> Empty
|
||||||
|
Node(_, k, v, left, right) ->
|
||||||
|
case compare(key, k) {
|
||||||
|
order.Lt ->
|
||||||
|
Node(
|
||||||
|
Black,
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
delay(fn() { delete(force(left), key, compare) }),
|
||||||
|
right,
|
||||||
|
)
|
||||||
|
order.Gt ->
|
||||||
|
Node(
|
||||||
|
Black,
|
||||||
|
k,
|
||||||
|
v,
|
||||||
|
left,
|
||||||
|
delay(fn() { delete(force(right), key, compare) }),
|
||||||
|
)
|
||||||
|
order.Eq ->
|
||||||
|
case force(left), force(right) {
|
||||||
|
Empty, Empty -> Empty
|
||||||
|
_, Empty -> force(left)
|
||||||
|
Empty, _ -> force(right)
|
||||||
|
_, _ ->
|
||||||
|
case find_min(force(right)) {
|
||||||
|
None -> force(left)
|
||||||
|
Some(#(min_key, min_value)) ->
|
||||||
|
Node(
|
||||||
|
Black,
|
||||||
|
min_key,
|
||||||
|
min_value,
|
||||||
|
left,
|
||||||
|
delay(fn() { delete_min(force(right)) }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Фильтрация элементов дерева
|
||||||
|
pub fn filter(
|
||||||
|
tree: RBTree(k, v),
|
||||||
|
predicate: fn(k, v) -> Bool,
|
||||||
|
compare: fn(k, k) -> Order,
|
||||||
|
) -> RBTree(k, v) {
|
||||||
|
fold_left(tree, empty(), fn(acc, key, value) {
|
||||||
|
case predicate(key, value) {
|
||||||
|
True -> insert(acc, key, value, compare)
|
||||||
|
False -> acc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Отображение значений в дереве
|
||||||
|
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) }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Левая свёртка дерева
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Правая свёртка дерева
|
||||||
|
pub fn fold_right(tree: RBTree(k, v), acc: a, f: fn(k, v, a) -> a) -> a {
|
||||||
|
case tree {
|
||||||
|
Empty -> acc
|
||||||
|
Node(_, key, value, left, right) -> {
|
||||||
|
let right_acc = fold_right(force(right), acc, f)
|
||||||
|
let current_acc = f(key, value, right_acc)
|
||||||
|
fold_right(force(left), current_acc, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Объединение двух деревьев (операция моноида)
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Нейтральный элемент моноида (пустое дерево)
|
||||||
|
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,
|
||||||
|
) -> RBTree(k, v) {
|
||||||
|
list
|
||||||
|
|> list_fold_left(empty(), fn(acc, pair) {
|
||||||
|
let #(key, value) = pair
|
||||||
|
insert(acc, key, value, compare)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Вспомогательная функция для свёртки списка
|
||||||
|
fn list_fold_left(list: List(a), acc: b, f: fn(b, a) -> b) -> b {
|
||||||
|
case list {
|
||||||
|
[] -> acc
|
||||||
|
[head, ..tail] -> list_fold_left(tail, f(acc, head), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Эффективная проверка равенства двух деревьев
|
||||||
|
/// Сравнивает деревья структурно без преобразования в списки
|
||||||
|
/// Время выполнения: O(min(n, m)) где n, m - размеры деревьев
|
||||||
|
pub fn equal(
|
||||||
|
tree1: RBTree(k, v),
|
||||||
|
tree2: RBTree(k, v),
|
||||||
|
key_compare: fn(k, k) -> Bool,
|
||||||
|
value_compare: fn(v, v) -> Bool,
|
||||||
|
) -> Bool {
|
||||||
|
equal_helper(tree1, tree2, key_compare, value_compare)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Вспомогательная функция для структурного сравнения деревьев
|
||||||
|
/// Использует короткое замыкание при первом несовпадении
|
||||||
|
fn equal_helper(
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Проверка, содержится ли ключ в дереве
|
||||||
|
pub fn contains(tree: RBTree(k, v), key: k, compare: fn(k, k) -> Order) -> Bool {
|
||||||
|
case lookup(tree, key, compare) {
|
||||||
|
Some(_) -> True
|
||||||
|
None -> False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Получение всех ключей дерева
|
||||||
|
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) из-за необходимости итерации
|
||||||
|
pub fn semantic_equal(
|
||||||
|
tree1: RBTree(k, v),
|
||||||
|
tree2: RBTree(k, v),
|
||||||
|
key_order: fn(k, k) -> Order,
|
||||||
|
value_equal: fn(v, v) -> Bool,
|
||||||
|
) -> Bool {
|
||||||
|
// Быстрая проверка размеров
|
||||||
|
case size(tree1) == size(tree2) {
|
||||||
|
False -> False
|
||||||
|
True -> {
|
||||||
|
// Проверяем, что все элементы из tree1 есть в tree2 с теми же значениями
|
||||||
|
fold_left(tree1, True, fn(acc, key, value) {
|
||||||
|
acc &&
|
||||||
|
case lookup(tree2, key, key_order) {
|
||||||
|
Some(other_value) -> value_equal(value, other_value)
|
||||||
|
None -> False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Сравнение с игнорированием цветов узлов (только структура и данные)
|
||||||
|
/// Полезно когда важна только логическая структура дерева поиска
|
||||||
|
pub fn structure_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(_, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
373
lab2/test/lab2_test.gleam
Normal file
373
lab2/test/lab2_test.gleam
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
import gleam/int
|
||||||
|
import gleam/list
|
||||||
|
import gleam/option
|
||||||
|
import gleam/order
|
||||||
|
import gleam/string
|
||||||
|
import gleeunit
|
||||||
|
import gleeunit/should
|
||||||
|
import lab2
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
gleeunit.main()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательные функции для тестов
|
||||||
|
fn int_compare(a: Int, b: Int) -> order.Order {
|
||||||
|
int.compare(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int_equal(a: Int, b: Int) -> Bool {
|
||||||
|
a == b
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|> lab2.insert(5, "five", int_compare)
|
||||||
|
|
||||||
|
lab2.is_empty(tree) |> should.be_false
|
||||||
|
lab2.size(tree) |> should.equal(1)
|
||||||
|
lab2.lookup(tree, 5, int_compare) |> should.equal(option.Some("five"))
|
||||||
|
lab2.lookup(tree, 10, int_compare) |> should.equal(option.None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест множественных вставок
|
||||||
|
pub fn multiple_insert_test() {
|
||||||
|
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)
|
||||||
|
|> lab2.insert(9, "nine", int_compare)
|
||||||
|
|
||||||
|
lab2.size(tree) |> should.equal(5)
|
||||||
|
lab2.lookup(tree, 1, int_compare) |> should.equal(option.Some("one"))
|
||||||
|
lab2.lookup(tree, 3, int_compare) |> should.equal(option.Some("three"))
|
||||||
|
lab2.lookup(tree, 5, int_compare) |> should.equal(option.Some("five"))
|
||||||
|
lab2.lookup(tree, 7, int_compare) |> should.equal(option.Some("seven"))
|
||||||
|
lab2.lookup(tree, 9, int_compare) |> should.equal(option.Some("nine"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест удаления элементов
|
||||||
|
pub fn delete_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(5, "five", int_compare)
|
||||||
|
|> lab2.insert(3, "three", int_compare)
|
||||||
|
|> lab2.insert(7, "seven", int_compare)
|
||||||
|
|> lab2.delete(3, int_compare)
|
||||||
|
|
||||||
|
lab2.size(tree) |> should.equal(2)
|
||||||
|
lab2.lookup(tree, 3, int_compare) |> should.equal(option.None)
|
||||||
|
lab2.lookup(tree, 5, int_compare) |> should.equal(option.Some("five"))
|
||||||
|
lab2.lookup(tree, 7, int_compare) |> should.equal(option.Some("seven"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест фильтрации
|
||||||
|
pub fn filter_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(1, "one", int_compare)
|
||||||
|
|> lab2.insert(2, "two", int_compare)
|
||||||
|
|> lab2.insert(3, "three", int_compare)
|
||||||
|
|> lab2.insert(4, "four", int_compare)
|
||||||
|
|> lab2.insert(5, "five", int_compare)
|
||||||
|
|
||||||
|
let filtered = lab2.filter(tree, fn(key, _) { key % 2 == 0 }, int_compare)
|
||||||
|
|
||||||
|
lab2.size(filtered) |> should.equal(2)
|
||||||
|
lab2.contains(filtered, 2, int_compare) |> should.be_true
|
||||||
|
lab2.contains(filtered, 4, int_compare) |> should.be_true
|
||||||
|
lab2.contains(filtered, 1, int_compare) |> should.be_false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест отображения
|
||||||
|
pub fn map_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(1, 10, int_compare)
|
||||||
|
|> lab2.insert(2, 20, int_compare)
|
||||||
|
|> lab2.insert(3, 30, int_compare)
|
||||||
|
|
||||||
|
let mapped = lab2.map(tree, fn(value) { value * 2 })
|
||||||
|
|
||||||
|
lab2.lookup(mapped, 1, int_compare) |> should.equal(option.Some(20))
|
||||||
|
lab2.lookup(mapped, 2, int_compare) |> should.equal(option.Some(40))
|
||||||
|
lab2.lookup(mapped, 3, int_compare) |> should.equal(option.Some(60))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест левой свёртки
|
||||||
|
pub fn fold_left_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(1, 10, int_compare)
|
||||||
|
|> lab2.insert(2, 20, int_compare)
|
||||||
|
|> lab2.insert(3, 30, int_compare)
|
||||||
|
|
||||||
|
let sum = lab2.fold_left(tree, 0, fn(acc, _, value) { acc + value })
|
||||||
|
sum |> should.equal(60)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест правой свёртки
|
||||||
|
pub fn fold_right_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(1, "a", int_compare)
|
||||||
|
|> lab2.insert(2, "b", int_compare)
|
||||||
|
|> lab2.insert(3, "c", int_compare)
|
||||||
|
|
||||||
|
let result = lab2.fold_right(tree, "", fn(_, value, acc) { value <> acc })
|
||||||
|
// Порядок может отличаться в зависимости от структуры дерева
|
||||||
|
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)
|
||||||
|
let result_list = lab2.to_list(tree)
|
||||||
|
|
||||||
|
lab2.size(tree) |> should.equal(3)
|
||||||
|
list.length(result_list) |> should.equal(3)
|
||||||
|
lab2.contains(tree, 1, int_compare) |> should.be_true
|
||||||
|
lab2.contains(tree, 2, int_compare) |> should.be_true
|
||||||
|
lab2.contains(tree, 3, int_compare) |> should.be_true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Тесты свойств моноида
|
||||||
|
|
||||||
|
/// Тест нейтрального элемента (левая единица)
|
||||||
|
pub fn monoid_left_identity_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(1, "one", int_compare)
|
||||||
|
|> lab2.insert(2, "two", int_compare)
|
||||||
|
|
||||||
|
let result = lab2.concat(lab2.mempty(), tree, int_compare)
|
||||||
|
|
||||||
|
lab2.equal(tree, result, int_equal, string_equal) |> should.be_true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест нейтрального элемента (правая единица)
|
||||||
|
pub fn monoid_right_identity_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(1, "one", int_compare)
|
||||||
|
|> lab2.insert(2, "two", int_compare)
|
||||||
|
|
||||||
|
let result = lab2.concat(tree, lab2.mempty(), int_compare)
|
||||||
|
|
||||||
|
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)
|
||||||
|
let tree3 = lab2.empty() |> lab2.insert(3, "three", int_compare)
|
||||||
|
|
||||||
|
let left_assoc =
|
||||||
|
lab2.concat(lab2.concat(tree1, tree2, int_compare), tree3, int_compare)
|
||||||
|
let right_assoc =
|
||||||
|
lab2.concat(tree1, lab2.concat(tree2, tree3, int_compare), int_compare)
|
||||||
|
|
||||||
|
// Проверяем, что оба дерева содержат одинаковые элементы
|
||||||
|
lab2.size(left_assoc) |> should.equal(3)
|
||||||
|
lab2.size(right_assoc) |> should.equal(3)
|
||||||
|
lab2.contains(left_assoc, 1, int_compare) |> should.be_true
|
||||||
|
lab2.contains(left_assoc, 2, int_compare) |> should.be_true
|
||||||
|
lab2.contains(left_assoc, 3, int_compare) |> should.be_true
|
||||||
|
lab2.contains(right_assoc, 1, int_compare) |> should.be_true
|
||||||
|
lab2.contains(right_assoc, 2, int_compare) |> should.be_true
|
||||||
|
lab2.contains(right_assoc, 3, int_compare) |> should.be_true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест инвариантов красно-чёрного дерева (упрощённая проверка)
|
||||||
|
pub fn red_black_invariant_test() {
|
||||||
|
// Создаём большое дерево и проверяем, что поиск работает корректно
|
||||||
|
let tree =
|
||||||
|
lab2.from_list(
|
||||||
|
[
|
||||||
|
#(1, "one"),
|
||||||
|
#(2, "two"),
|
||||||
|
#(3, "three"),
|
||||||
|
#(4, "four"),
|
||||||
|
#(5, "five"),
|
||||||
|
#(6, "six"),
|
||||||
|
#(7, "seven"),
|
||||||
|
#(8, "eight"),
|
||||||
|
#(9, "nine"),
|
||||||
|
#(10, "ten"),
|
||||||
|
],
|
||||||
|
int_compare,
|
||||||
|
)
|
||||||
|
|
||||||
|
lab2.size(tree) |> should.equal(10)
|
||||||
|
|
||||||
|
// Проверяем, что все элементы можно найти
|
||||||
|
let all_found =
|
||||||
|
list.all([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], fn(key) {
|
||||||
|
lab2.contains(tree, key, int_compare)
|
||||||
|
})
|
||||||
|
|
||||||
|
all_found |> should.be_true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест обновления существующего ключа
|
||||||
|
pub fn update_existing_key_test() {
|
||||||
|
let tree =
|
||||||
|
lab2.empty()
|
||||||
|
|> lab2.insert(1, "old", int_compare)
|
||||||
|
|> lab2.insert(1, "new", int_compare)
|
||||||
|
|
||||||
|
lab2.size(tree) |> should.equal(1)
|
||||||
|
lab2.lookup(tree, 1, int_compare) |> should.equal(option.Some("new"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест свойства: insert -> lookup должен возвращать вставленное значение
|
||||||
|
pub fn insert_lookup_property_test() {
|
||||||
|
let test_cases = [
|
||||||
|
#(1, "one"),
|
||||||
|
#(42, "forty-two"),
|
||||||
|
#(0, "zero"),
|
||||||
|
#(-5, "minus-five"),
|
||||||
|
]
|
||||||
|
|
||||||
|
list.each(test_cases, fn(test_case) {
|
||||||
|
let #(key, value) = test_case
|
||||||
|
let tree = lab2.empty() |> lab2.insert(key, value, int_compare)
|
||||||
|
lab2.lookup(tree, key, int_compare) |> should.equal(option.Some(value))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест свойства: size увеличивается при вставке новых элементов
|
||||||
|
pub fn insert_size_property_test() {
|
||||||
|
let tree = lab2.empty()
|
||||||
|
lab2.size(tree) |> should.equal(0)
|
||||||
|
|
||||||
|
let tree1 = lab2.insert(tree, 1, "one", int_compare)
|
||||||
|
lab2.size(tree1) |> should.equal(1)
|
||||||
|
|
||||||
|
let tree2 = lab2.insert(tree1, 2, "two", int_compare)
|
||||||
|
lab2.size(tree2) |> should.equal(2)
|
||||||
|
|
||||||
|
let tree3 = lab2.insert(tree2, 3, "three", int_compare)
|
||||||
|
lab2.size(tree3) |> should.equal(3)
|
||||||
|
|
||||||
|
// Вставка существующего ключа не должна увеличивать размер
|
||||||
|
let tree4 = lab2.insert(tree3, 2, "two-updated", int_compare)
|
||||||
|
lab2.size(tree4) |> should.equal(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест свойства: filter сохраняет порядок и структуру
|
||||||
|
pub fn filter_property_test() {
|
||||||
|
let original =
|
||||||
|
lab2.from_list(
|
||||||
|
[#(1, "a"), #(2, "b"), #(3, "c"), #(4, "d"), #(5, "e")],
|
||||||
|
int_compare,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Фильтр, который пропускает все элементы
|
||||||
|
let all_filter = lab2.filter(original, fn(_, _) { True }, int_compare)
|
||||||
|
lab2.equal(original, all_filter, int_equal, string_equal) |> should.be_true
|
||||||
|
|
||||||
|
// Фильтр, который не пропускает ничего
|
||||||
|
let none_filter = lab2.filter(original, fn(_, _) { False }, int_compare)
|
||||||
|
lab2.is_empty(none_filter) |> should.be_true
|
||||||
|
|
||||||
|
// Частичный фильтр
|
||||||
|
let partial_filter =
|
||||||
|
lab2.filter(original, fn(key, _) { key > 3 }, int_compare)
|
||||||
|
lab2.size(partial_filter) |> should.equal(2)
|
||||||
|
lab2.contains(partial_filter, 4, int_compare) |> should.be_true
|
||||||
|
lab2.contains(partial_filter, 5, int_compare) |> should.be_true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест свойства: map сохраняет структуру дерева
|
||||||
|
pub fn map_property_test() {
|
||||||
|
let original = lab2.from_list([#(1, 10), #(2, 20), #(3, 30)], int_compare)
|
||||||
|
|
||||||
|
// Тождественное отображение
|
||||||
|
let identity_map = lab2.map(original, fn(x) { x })
|
||||||
|
lab2.equal(original, identity_map, int_equal, int_equal) |> should.be_true
|
||||||
|
|
||||||
|
// Отображение с изменением
|
||||||
|
let doubled = lab2.map(original, fn(x) { x * 2 })
|
||||||
|
lab2.size(doubled) |> should.equal(lab2.size(original))
|
||||||
|
lab2.lookup(doubled, 1, int_compare) |> should.equal(option.Some(20))
|
||||||
|
lab2.lookup(doubled, 2, int_compare) |> should.equal(option.Some(40))
|
||||||
|
lab2.lookup(doubled, 3, int_compare) |> should.equal(option.Some(60))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест свойства: 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)
|
||||||
|
|
||||||
|
// Сумма всех значений
|
||||||
|
let sum_left = lab2.fold_left(tree, 0, fn(acc, _, value) { acc + value })
|
||||||
|
let sum_right = lab2.fold_right(tree, 0, fn(_, value, acc) { acc + value })
|
||||||
|
|
||||||
|
sum_left |> should.equal(15)
|
||||||
|
sum_right |> should.equal(15)
|
||||||
|
|
||||||
|
// Количество элементов
|
||||||
|
let count_left = lab2.fold_left(tree, 0, fn(acc, _, _) { acc + 1 })
|
||||||
|
let count_right = lab2.fold_right(tree, 0, fn(_, _, acc) { acc + 1 })
|
||||||
|
|
||||||
|
count_left |> should.equal(5)
|
||||||
|
count_right |> should.equal(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест свойства: 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)
|
||||||
|
let result_list = lab2.to_list(tree)
|
||||||
|
let restored_tree = lab2.from_list(result_list, int_compare)
|
||||||
|
|
||||||
|
lab2.size(tree) |> should.equal(lab2.size(restored_tree))
|
||||||
|
lab2.equal(tree, restored_tree, int_equal, string_equal) |> should.be_true
|
||||||
|
|
||||||
|
// Проверяем, что все элементы присутствуют
|
||||||
|
list.each(original_list, fn(item) {
|
||||||
|
let #(key, value) = item
|
||||||
|
lab2.lookup(restored_tree, key, int_compare)
|
||||||
|
|> should.equal(option.Some(value))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Тест инвариантов моноида (коммутативность для деревьев с разными ключами)
|
||||||
|
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)
|
||||||
|
|
||||||
|
let concat1 = lab2.concat(tree1, tree2, int_compare)
|
||||||
|
let concat2 = lab2.concat(tree2, tree1, int_compare)
|
||||||
|
|
||||||
|
// Для деревьев с разными ключами конкатенация должна быть коммутативной
|
||||||
|
lab2.size(concat1) |> should.equal(4)
|
||||||
|
lab2.size(concat2) |> should.equal(4)
|
||||||
|
|
||||||
|
// Оба дерева должны содержать все элементы
|
||||||
|
[1, 2, 3, 4]
|
||||||
|
|> list.each(fn(key) {
|
||||||
|
lab2.contains(concat1, key, int_compare) |> should.be_true
|
||||||
|
lab2.contains(concat2, key, int_compare) |> should.be_true
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user