diff --git a/01-tree_successor/cpp/tree_successor_more_tests.cpp b/01-tree_successor/cpp/tree_successor_more_tests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..552762a5202ca4e15ca7d2dede05ec15447ef4df --- /dev/null +++ b/01-tree_successor/cpp/tree_successor_more_tests.cpp @@ -0,0 +1,105 @@ +#include <algorithm> +#include <functional> +#include <cstdint> +#include <string> +#include <utility> +#include <vector> + +#include "tree_successor.h" + +using namespace std; + +// If the condition is not true, report an error and halt. +#define EXPECT(condition, message) do { if (!(condition)) expect_failed(message); } while (0) +void expect_failed(const string& message); + +void test(const vector<int>& sequence, Tree &tree) { + Node* node = tree.successor(nullptr); + for (const auto& element : sequence) { + EXPECT(node, "Expected successor " + to_string(element) + ", got nullptr"); + EXPECT(node->key == element, + "Expected successor " + to_string(element) + ", got " + to_string(node->key)); + node = tree.successor(node); + } + EXPECT(!node, "Expected no successor, got " + to_string(node->key)); +} + +vector<int> get_linear_sequence() { + vector<int> numbers; + for (int i = 0; i < 10000000; i++) + numbers.push_back((int)(7.13*i)); + return numbers; +} + +void test_path(bool right) { + vector<int> numbers = get_linear_sequence(); + Tree tree; + Node *node = nullptr; + if (right) + for (int key : numbers) + node = tree.insert(key, node); + else + for (int index = numbers.size() - 1; index >= 0; --index) + node = tree.insert(numbers[index], node); + + test(numbers, tree); +} + +void test_two_paths() { + vector<int> numbers = get_linear_sequence(); + Tree tree; + Node *node = nullptr; + for(size_t i = numbers.size()/2; i < numbers.size(); i++) + node = tree.insert(numbers[i], node); + node = nullptr; + for(int i = numbers.size()/2 - 1; i >= 0; i--) + node = tree.insert(numbers[i], node); + + test(numbers, tree); +} + +void test_sequence(vector<int> &&sequence) { + Tree tree; + for (const auto& element : sequence) + tree.insert(element); + + sort(sequence.begin(), sequence.end()); + test(sequence, tree); +} + +void test_random() { + vector<int> sequence = {997}; + for (int i = 2; i < 199999; i++) + sequence.push_back((sequence.back() * int64_t(997)) % 199999); + test_sequence(move(sequence)); +} + +void test_trivial() { + test_sequence({5}); + test_sequence({7,9}); + test_sequence({7,3}); + test_sequence({5,3,7}); +} + +void test_comb() { + vector<int> numbers = get_linear_sequence(); + Tree tree; + Node *node = nullptr; + for(size_t i = numbers.size()/2; i < numbers.size(); i++) + node = tree.insert(numbers[i], node); + node = nullptr; + for(int i = numbers.size()/2 - 1; i >= 0; i-=2) { + node = tree.insert(numbers[i-1], node); + tree.insert(numbers[i], node); + } + test(numbers, tree); +} + +vector<pair<string, function<void()>>> tests = { + { "trivial", test_trivial }, + { "right_path", []{ test_path(true); } }, + { "left_path", []{ test_path(false); } }, + { "random_tree", test_random }, + { "two_paths", test_two_paths }, + { "comb", test_comb } +}; diff --git a/01-tree_successor/python/tree_successor_more_tests.py b/01-tree_successor/python/tree_successor_more_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..3415b8ff688ca141584343c5639bd1b905313614 --- /dev/null +++ b/01-tree_successor/python/tree_successor_more_tests.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import sys + +from tree_successor import Tree + +def test_tree(tree, sequence): + node = tree.successor(None) + for element in sequence: + assert node is not None, "Expected successor {}, got None".format(element) + assert node.key == element, "Expected successor {}, got {}".format(element, node.key) + node = tree.successor(node) + assert node is None, "Expected no successor, got {}".format(node.key) + +def test_sequence(sequence): + tree = Tree() + for i in sequence: + tree.insert(i) + sequence.sort() + test_tree(tree, sequence) + +def test_trivial_tree(): + test_sequence([5]) + test_sequence([7,9]) + test_sequence([7,3]) + test_sequence([5,3,7]) + +def test_random_tree(): + test_sequence([pow(997, i, 199999) for i in range(1, 199999)]) + +def test_path(right): + sequence = [int(7.13*i) for i in range(1000000)] + tree = Tree() + node = None + sequence_insert = sequence if right else reversed(sequence) + for key in sequence_insert: + node = tree.insert(key, node) + test_tree(tree, sequence) + +def test_two_paths(): + sequence_left = [int(7.13*i) for i in range(1000000)] + sequence_right = [int(7.13*i) for i in range(1000000, 2000000)] + tree = Tree() + node = None + for key in sequence_right: + node = tree.insert(key, node) + node = None + for key in reversed(sequence_left): + node = tree.insert(key, node) + test_tree(tree, sequence_left + sequence_right) + +def test_comb(): + sequence = [int(7.13*i) for i in range(1000000)] + tree = Tree() + node = None + for i in range(len(sequence)//2, len(sequence)): + node = tree.insert(sequence[i], node) + node = None + for i in range(len(sequence)//2-1, 0, -2): + node = tree.insert(sequence[i-1], node) + tree.insert(sequence[i], node) + test_tree(tree, sequence) + +tests = [ + ("trivial", test_trivial_tree), + ("random_tree", test_random_tree), + ("right_path", lambda: test_path(True)), + ("left_path", lambda: test_path(False)), + ("two_paths", test_two_paths), + ("comb", test_comb), +] + +if __name__ == "__main__": + for required_test in sys.argv[1:] or [name for name, _ in tests]: + for name, test in tests: + if name == required_test: + print("Running test {}".format(name), file=sys.stderr) + test() + break + else: + raise ValueError("Unknown test {}".format(name)) diff --git a/01-tree_successor/task.md b/01-tree_successor/task.md index ec268c46e235ad886e1f3171e7188b97f0acbe3c..1a95b5143580676d4b5bcc668b5b3c46c395629d 100644 --- a/01-tree_successor/task.md +++ b/01-tree_successor/task.md @@ -15,3 +15,8 @@ You should submit the file `tree_successor.*` (but not the `tree_successor_test.*`). Source code templates can be found in [the git repository](https://gitlab.kam.mff.cuni.cz/datovky/assignments/-/tree/master). + +Files tree_successor_more_tests.{cpp,py} contain additional tests +for bugs discovered in students' solutions during this semester. +They are not included on recodex, but your program should pass them +in few seconds. diff --git a/02-splay_operation/cpp/splay_operation_more_tests.cpp b/02-splay_operation/cpp/splay_operation_more_tests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f70db3ff999d3cfbd6a76478b81dcabc0e467697 --- /dev/null +++ b/02-splay_operation/cpp/splay_operation_more_tests.cpp @@ -0,0 +1,209 @@ +#include <algorithm> +#include <cassert> +#include <fstream> +#include <functional> +#include <string> +#include <utility> +#include <vector> + +#include "splay_operation.h" + +using namespace std; + +// If the condition is not true, report an error and halt. +#define EXPECT(condition, message) do { if (!(condition)) expect_failed(message); } while (0) +void expect_failed(const string& message); + +// Flatten the tree: return a sorted list of all keys in the tree. +vector<int> flatten(const Tree& tree) { + constexpr int L = 0, R = 1, F = 2; + + Node* node = tree.root; + vector<int> flattened, stack = {L}; + while (!stack.empty()) { + if (stack.back() == L) { + stack.back() = R; + if (node->left) { + node = node->left; + stack.push_back(L); + } + } else if (stack.back() == R) { + flattened.push_back(node->key); + stack.back() = F; + if (node->right) { + node = node->right; + stack.push_back(L); + } + } else { + node = node->parent; + stack.pop_back(); + } + } + return flattened; +} + +// Test for splay operation with required helpers +class TestSplay { + public: + static Node* deserialize_node(const string& text, int& index) { + EXPECT(text[index++] == '(', "Internal error during example deserialization"); + if (text[index] == ')') { + index++; + return nullptr; + } else { + int comma = text.find(',', index); + int key = stoi(text.substr(index, comma - index)); + Node* left = deserialize_node(text, (index=comma + 1)); + Node* right = deserialize_node(text, ++index); + EXPECT(text[index++] == ')', "Internal error during example deserialization"); + return new Node(key, nullptr, left, right); + } + } + + static Node* deserialize_root(const string& text) { + int index = 0; + Node* root = deserialize_node(text, index); + assert(index == text.size()); + return root; + } + + static string compare(Node* system, Node* gold) { + if (!system && gold) { + return "expected node with key " + to_string(gold->key) + ", found None"; + } else if (system && !gold) { + return "expected None, found node with key " + to_string(system->key); + } else if (system && gold) { + if (system->key != gold->key) + return "expected node with key " + to_string(gold->key) + ", found " + to_string(system->key); + auto result = compare(system->left, gold->left); + if (!result.empty()) return result; + return compare(system->right, gold->right); + } + return string(); + } + + static void test() { + ifstream splay_tests_file("splay_tests.txt"); + EXPECT(splay_tests_file.is_open(), "Cannot open splay_tests.txt file with the tests"); + + string original, splayed; + int target; + while (splay_tests_file >> original >> target >> splayed) { + Tree original_tree(deserialize_root(original)); + Tree splayed_tree(deserialize_root(splayed)); + + Node* target_node = original_tree.root; + while (target_node && target_node->key != target) + if (target < target_node->key) + target_node = target_node->left; + else + target_node = target_node->right; + EXPECT(target_node, "Internal error during finding the target node in the tree to splay"); + + original_tree.splay(target_node); + auto error = compare(original_tree.root, splayed_tree.root); + EXPECT(error.empty(), "Error running splay on key " + to_string(target) + " of " + original + ": " + error); + } + } +}; + +const int elements = 5000000; + +void test_lookup() { + // Insert even numbers + Tree tree; + for (int i = 0; i < elements; i += 2) + tree.insert(i); + + // Find non-existing + for (int i = 1; i < elements; i += 2) + for (int j = 0; j < 10; j++) + EXPECT(!tree.lookup(i), "Non-existing element was found"); + + // Find existing + for (int i = 0; i < elements; i += 2) + for (int j = 0; j < 10; j++) + EXPECT(tree.lookup(i), "Existing element was not found"); +} + +void test_insert() { + // Test validity first + { + Tree tree; + vector<int> sequence = {997}; + for (int i = 2; i < 1999; i++) + sequence.push_back((sequence.back() * sequence.front()) % 1999); + for (const auto& i : sequence) + tree.insert(i); + + vector<int> flattened = flatten(tree); + sort(sequence.begin(), sequence.end()); + EXPECT(flattened == sequence, "Incorrect tree after a sequence of inserts"); + } + + // Test speed + { + Tree tree; + for (int i = 0; i < elements; i++) + for (int j = 0; j < 10; j++) + tree.insert(i); + } + + { + Tree tree; + for (int i = elements; i >= 0; i--) + tree.insert(i); + for (int i = 0; i < elements; i++) + tree.insert(elements); + } +} + +void test_remove() { + // Test validity first + { + Tree tree; + for (int i = 2; i < 1999 * 2; i++) + tree.insert(i); + + vector<int> sequence = {2 * 997}; + for (int i = 2; i < 1999; i++) + sequence.push_back(2 * ((sequence.back() * sequence.front() / 4) % 1999)); + for (const auto& i : sequence) + tree.remove(i + 1); + + vector<int> flattened = flatten(tree); + sort(sequence.begin(), sequence.end()); + EXPECT(flattened == sequence, "Correct tree after a sequence of removes"); + } + + // Test speed + { + Tree tree; + for (int i = 0; i < elements; i++) + tree.insert(i); + + // Non-existing elements + for (int i = 1; i < elements; i += 2) + for (int j = 0; j < 10; j++) + tree.remove(i); + + // Existing elements + for (int i = 2; i < elements; i += 2) + for (int j = 0; j < 10; j++) + tree.remove(i); + } + { + Tree tree; + for (int i = 1; i < elements; i++) + tree.insert(i); + for (int i = 1; i < elements; i++) + tree.remove(0); + } +} + +vector<pair<string, function<void()>>> tests = { + { "splay", TestSplay::test }, + { "lookup", test_lookup }, + { "insert", test_insert }, + { "remove", test_remove }, +}; diff --git a/02-splay_operation/python/splay_operation_more_tests.py b/02-splay_operation/python/splay_operation_more_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..1ebb8aa7e07cd64ce2b1cf6bd74307591f2f2cec --- /dev/null +++ b/02-splay_operation/python/splay_operation_more_tests.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +import itertools +import math +import sys + +from splay_operation import Tree, Node + +def flatten(tree): + """Flatten given tree in ascending order.""" + L, R, F = 0, 1, 2 + + node, stack, flattened = tree.root, [L], [] + while node is not None: + if stack[-1] == L: + stack[-1] = R + if node.left is not None: + node = node.left + stack.append(L) + elif stack[-1] == R: + flattened.append(node.key) + stack[-1] = F + if node.right is not None: + node = node.right + stack.append(L) + else: + node = node.parent + stack.pop() + + return flattened + +def test_splay(): + def deserialize_tree(string): + def deserialize_node(i): + assert string[i] == "(" + i += 1 + if string[i] == ")": + return i + 1, None + else: + comma = string.find(",", i) + comma2, left = deserialize_node(comma + 1) + rparen, right = deserialize_node(comma2 + 1) + assert string[rparen] == ")" + return rparen + 1, Node(int(string[i : comma]), left=left, right=right) + + index, root = deserialize_node(0) + assert index == len(string) + return Tree(root) + + def compare(system, gold): + if system is None and gold is not None: + return "expected node with key {}, found None".format(gold.key) + elif system is not None and gold is None: + return "expected None, found node with key {}".format(system.key) + elif system is not None and gold is not None: + if system.key != gold.key: + return "expected node with key {}, found {}".format(gold.key, system.key) + return compare(system.left, gold.left) or compare(system.right, gold.right) + + with open("splay_tests.txt", "r") as splay_tests_file: + for line in splay_tests_file: + original_serialized, target_serialized, splayed_serialized = line.rstrip("\n").split() + original = deserialize_tree(original_serialized) + splayed = deserialize_tree(splayed_serialized) + target = int(target_serialized) + + node = original.root + while node is not None and node.key != target: + if target < node.key: node = node.left + else: node = node.right + assert node is not None + + original.splay(node) + error = compare(original.root, splayed.root) + assert not error, "Error running splay on key {} of {}: {}".format(node.key, original_serialized, error) + +def test_lookup(): + tree = Tree() + for elem in range(0, 100000, 2): + tree.insert(elem) + + # Find non-existing + for elem in range(1, 100000, 2): + for _ in range(10): + assert tree.lookup(elem) is None, "Non-existing element was found" + + # Find existing + for elem in range(0, 100000, 2): + for _ in range(10): + assert tree.lookup(elem) is not None, "Existing element was not found" + +def test_insert(): + # Test validity first + tree = Tree() + sequence = [pow(997, i, 1999) for i in range(1, 1999)] + for elem in sequence: + tree.insert(elem) + assert flatten(tree) == sorted(sequence), "Incorrect tree after a sequence of inserts" + + # Test speed + elements = 200000 + tree = Tree() + for elem in range(elements): + for _ in range(10): + tree.insert(elem) + + tree = Tree() + for elem in reversed(range(elements)): + tree.insert(elem) + for elem in range(elements): + tree.insert(elements) + +def test_remove(): + # Test validity first + tree = Tree() + for elem in range(2, 1999 * 2): + tree.insert(elem) + + sequence = [2 * pow(997, i, 1999) for i in range(1, 1999)] + for elem in sequence: + tree.remove(elem + 1) + assert flatten(tree) == sorted(sequence), "Incorrect tree after a sequence of removes" + + # Test speed + elements = 200000 + tree = Tree() + for elem in range(0, elements, 2): + tree.insert(elem) + + # Non-existing elements + for elem in range(1, elements, 2): + for _ in range(10): + tree.remove(elem) + + # Existing elements + for elem in range(2, elements, 2): + tree.remove(elem) + + tree = Tree() + for elem in range(1, elements): + tree.insert(elem) + for elem in range(elements): + tree.remove(0) + +tests = [ + ("splay", test_splay), + ("lookup", test_lookup), + ("insert", test_insert), + ("remove", test_remove), +] + +if __name__ == "__main__": + for required_test in sys.argv[1:] or [name for name, _ in tests]: + for name, test in tests: + if name == required_test: + print("Running test {}".format(name), file=sys.stderr) + test() + break + else: + raise ValueError("Unknown test {}".format(name)) diff --git a/02-splay_operation/task.md b/02-splay_operation/task.md index 18d722a6330ae91fd9978e489ad89bd25d5b86ee..665342ccca780408501f065208cdad4e3fadef3c 100644 --- a/02-splay_operation/task.md +++ b/02-splay_operation/task.md @@ -10,3 +10,8 @@ You should submit the `splay_operation.*` file (but not the `splay_operation_test.*`). Source code templates can be found in [the git repository](https://gitlab.kam.mff.cuni.cz/datovky/assignments/-/tree/master). + +Files splay_operation_more_tests.{cpp,py} contain additional tests +for bugs discovered in students' solutions during this semester. +They are not included on recodex, but your program should pass them +in few seconds.