import gleam/int import gleam/io import gleam/list import gleam/option import gleam/order import gleam/string import gleeunit import gleeunit/should import lab2 import prng/random import prng/seed 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 } // Генерация случайных данных fn gk() -> Int { int.random(10_000) - 5000 } fn gv() -> String { let length = int.random(64) let generator: random.Generator(Int) = random.int(97, 122) // random string of given length let string = string.join( list.repeat("", length) |> list.map(fn(_) { let code = random.sample(generator, seed.random()) let assert Ok(cp) = string.utf_codepoint(code) string.from_utf_codepoints([cp]) }), "", ) io.println("Generating rnd s:" <> string) string } /// Тест создания пустого дерева 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 key = gk() let value = gv() let tree = lab2.empty() |> lab2.insert(key, value, int_compare) lab2.is_empty(tree) |> should.be_false lab2.size(tree) |> should.equal(1) lab2.lookup(tree, key, int_compare) |> should.equal(option.Some(value)) lab2.lookup(tree, gk(), int_compare) |> should.equal(option.None) } /// Тест множественных вставок pub fn multiple_insert_test() { let k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let k3 = gk() let v3 = gv() let k4 = gk() let v4 = gv() let k5 = gk() let v5 = gv() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) |> lab2.insert(k3, v3, int_compare) |> lab2.insert(k4, v4, int_compare) |> lab2.insert(k5, v5, int_compare) lab2.size(tree) |> should.equal(5) lab2.lookup(tree, k1, int_compare) |> should.equal(option.Some(v1)) lab2.lookup(tree, k2, int_compare) |> should.equal(option.Some(v2)) lab2.lookup(tree, k3, int_compare) |> should.equal(option.Some(v3)) lab2.lookup(tree, k4, int_compare) |> should.equal(option.Some(v4)) lab2.lookup(tree, k5, int_compare) |> should.equal(option.Some(v5)) } /// Тест удаления элементов pub fn delete_test() { let k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let k3 = gk() let v3 = gv() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) |> lab2.insert(k3, v3, int_compare) |> lab2.delete(k2, int_compare) lab2.size(tree) |> should.equal(2) lab2.lookup(tree, k2, int_compare) |> should.equal(option.None) lab2.lookup(tree, k1, int_compare) |> should.equal(option.Some(v1)) lab2.lookup(tree, k3, int_compare) |> should.equal(option.Some(v3)) } /// Тест фильтрации pub fn filter_test() { let k1 = gk() * 2 + 1 let v1 = gv() let k2 = gk() * 2 let v2 = gv() let k3 = gk() * 2 + 1 let v3 = gv() let k4 = gk() * 2 let v4 = gv() let k5 = gk() * 2 + 1 let v5 = gv() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) |> lab2.insert(k3, v3, int_compare) |> lab2.insert(k4, v4, int_compare) |> lab2.insert(k5, v5, int_compare) let filtered = lab2.filter(tree, fn(key, _) { key % 2 == 0 }, int_compare) lab2.size(filtered) |> should.equal(2) lab2.contains(filtered, k2, int_compare) |> should.be_true lab2.contains(filtered, k4, int_compare) |> should.be_true lab2.contains(filtered, k1, int_compare) |> should.be_false } /// Тест отображения pub fn map_test() { let k1 = gk() let v1 = gk() let k2 = gk() let v2 = gk() let k3 = gk() let v3 = gk() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) |> lab2.insert(k3, v3, int_compare) let mapped = lab2.map(tree, fn(value) { value * 2 }) lab2.lookup(mapped, k1, int_compare) |> should.equal(option.Some(v1 * 2)) lab2.lookup(mapped, k2, int_compare) |> should.equal(option.Some(v2 * 2)) lab2.lookup(mapped, k3, int_compare) |> should.equal(option.Some(v3 * 2)) } /// Тест левой свёртки pub fn fold_left_test() { let k1 = gk() let v1 = gk() let k2 = gk() let v2 = gk() let k3 = gk() let v3 = gk() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) |> lab2.insert(k3, v3, int_compare) let sum = lab2.fold_left(tree, 0, fn(acc, _, value) { acc + value }) sum |> should.equal(v1 + v2 + v3) } /// Тест правой свёртки pub fn fold_right_test() { let k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let k3 = gk() let v3 = gv() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) |> lab2.insert(k3, v3, int_compare) let result = lab2.fold_right(tree, "", fn(_, value, acc) { value <> acc }) // Порядок может отличаться в зависимости от структуры дерева result |> string.length |> should.equal(string.length(v1) + string.length(v2) + string.length(v3)) } /// Тест преобразования в список и обратно pub fn to_from_list_test() { let k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let k3 = gk() let v3 = gv() let original_list = [#(k1, v1), #(k2, v2), #(k3, v3)] 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, k1, int_compare) |> should.be_true lab2.contains(tree, k2, int_compare) |> should.be_true lab2.contains(tree, k3, int_compare) |> should.be_true } // Тесты свойств моноида /// Тест нейтрального элемента (левая единица) pub fn monoid_left_identity_test() { let k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) let result = lab2.concat(lab2.empty(), tree, int_compare) lab2.semantic_equal(tree, result, int_compare, string_equal) |> should.be_true } /// Тест нейтрального элемента (правая единица) pub fn monoid_right_identity_test() { let k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let tree = lab2.empty() |> lab2.insert(k1, v1, int_compare) |> lab2.insert(k2, v2, int_compare) let result = lab2.concat(tree, lab2.empty(), int_compare) lab2.semantic_equal(tree, result, int_compare, string_equal) |> should.be_true } /// Тест ассоциативности pub fn monoid_associativity_test() { let k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let k3 = gk() let v3 = gv() let tree1 = lab2.empty() |> lab2.insert(k1, v1, int_compare) let tree2 = lab2.empty() |> lab2.insert(k2, v2, int_compare) let tree3 = lab2.empty() |> lab2.insert(k3, v3, 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, k1, int_compare) |> should.be_true lab2.contains(left_assoc, k2, int_compare) |> should.be_true lab2.contains(left_assoc, k3, int_compare) |> should.be_true lab2.contains(right_assoc, k1, int_compare) |> should.be_true lab2.contains(right_assoc, k2, int_compare) |> should.be_true lab2.contains(right_assoc, k3, int_compare) |> should.be_true } /// Тест инвариантов красно-чёрного дерева (упрощённая проверка) pub fn red_black_invariant_test() { // Создаём большое дерево и проверяем, что поиск работает корректно let tree = lab2.from_list( list.map(list.range(1, 10), fn(i) { #(i, gv()) }), int_compare, ) lab2.size(tree) |> should.equal(10) // Проверяем, что все элементы можно найти let all_found = list.all(list.range(1, 10), fn(key) { lab2.contains(tree, key, int_compare) }) all_found |> should.be_true } /// Тест обновления существующего ключа pub fn update_existing_key_test() { let key = gk() let old_value = gv() let new_value = gv() let tree = lab2.empty() |> lab2.insert(key, old_value, int_compare) |> lab2.insert(key, new_value, int_compare) lab2.size(tree) |> should.equal(1) lab2.lookup(tree, key, int_compare) |> should.equal(option.Some(new_value)) } /// Тест свойства: insert -> lookup должен возвращать вставленное значение pub fn insert_lookup_property_test() { let test_cases = [ #(gk(), gv()), #(gk(), gv()), #(gk(), gv()), #(gk(), gv()), ] 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 key1 = gk() let key2 = gk() let key3 = gk() let tree = lab2.empty() lab2.size(tree) |> should.equal(0) let tree1 = lab2.insert(tree, key1, gv(), int_compare) lab2.size(tree1) |> should.equal(1) let tree2 = lab2.insert(tree1, key2, gv(), int_compare) lab2.size(tree2) |> should.equal(2) let tree3 = lab2.insert(tree2, key3, gv(), int_compare) lab2.size(tree3) |> should.equal(3) // Вставка существующего ключа не должна увеличивать размер let tree4 = lab2.insert(tree3, key2, gv(), int_compare) lab2.size(tree4) |> should.equal(3) } /// Тест свойства: filter сохраняет порядок и структуру pub fn filter_property_test() { let original = lab2.from_list( list.map(list.range(1, 5), fn(i) { #(i, gv()) }), 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 k1 = gk() let v1 = gk() let k2 = gk() let v2 = gk() let k3 = gk() let v3 = gk() let original = lab2.from_list([#(k1, v1), #(k2, v2), #(k3, v3)], 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, k1, int_compare) |> should.equal(option.Some(v1 * 2)) lab2.lookup(doubled, k2, int_compare) |> should.equal(option.Some(v2 * 2)) lab2.lookup(doubled, k3, int_compare) |> should.equal(option.Some(v3 * 2)) } /// Тест свойства: fold_left и fold_right дают корректные результаты pub fn fold_property_test() { let values = list.map(list.range(1, 5), fn(_) { gk() }) let tree = lab2.from_list(list.zip(list.range(1, 5), values), int_compare) let expected_sum = list.fold(values, 0, fn(acc, v) { acc + v }) // Сумма всех значений 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(expected_sum) sum_right |> should.equal(expected_sum) // Количество элементов 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 k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let k3 = gk() let v3 = gv() let k4 = gk() let v4 = gv() let original_list = [#(k1, v1), #(k2, v2), #(k3, v3), #(k4, v4)] 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.semantic_equal(tree, restored_tree, int_compare, 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 k1 = gk() let v1 = gv() let k2 = gk() let v2 = gv() let k3 = gk() let v3 = gv() let k4 = gk() let v4 = gv() let tree1 = lab2.from_list([#(k1, v1), #(k2, v2)], int_compare) let tree2 = lab2.from_list([#(k3, v3), #(k4, v4)], 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) // Оба дерева должны содержать все элементы [k1, k2, k3, k4] |> list.each(fn(key) { lab2.contains(concat1, key, int_compare) |> should.be_true lab2.contains(concat2, key, int_compare) |> should.be_true }) }