From 8e4e4b131536b673c986d8ec60940975a045a2b8 Mon Sep 17 00:00:00 2001
From: Jirka Fink <fink@ktiml.mff.cuni.cz>
Date: Thu, 12 Oct 2023 11:54:43 +0200
Subject: [PATCH] Add more tests for the 1st and 2nd assignments
---
.../cpp/tree_successor_more_tests.cpp | 105 +++++++++
.../python/tree_successor_more_tests.py | 80 +++++++
01-tree_successor/task.md | 5 +
.../cpp/splay_operation_more_tests.cpp | 209 ++++++++++++++++++
.../python/splay_operation_more_tests.py | 159 +++++++++++++
02-splay_operation/task.md | 5 +
6 files changed, 563 insertions(+)
create mode 100644 01-tree_successor/cpp/tree_successor_more_tests.cpp
create mode 100644 01-tree_successor/python/tree_successor_more_tests.py
create mode 100644 02-splay_operation/cpp/splay_operation_more_tests.cpp
create mode 100644 02-splay_operation/python/splay_operation_more_tests.py
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 0000000..552762a
--- /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 0000000..3415b8f
--- /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 ec268c4..1a95b51 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 0000000..f70db3f
--- /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 0000000..1ebb8aa
--- /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 18d722a..665342c 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.
--
GitLab