diff --git a/12-range_tree/cpp/Makefile b/12-range_tree/cpp/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..38c4dcd29a404a7b537edd674123d25ad692b75b --- /dev/null +++ b/12-range_tree/cpp/Makefile @@ -0,0 +1,13 @@ +test: range_tree_test + ./$< + +INCLUDE ?= . +CXXFLAGS=-std=c++11 -O2 -Wall -Wextra -g -Wno-sign-compare -I$(INCLUDE) + +range_tree_test: range_tree_test.cpp range_tree.h test_main.cpp + $(CXX) $(CXXFLAGS) $(filter %.cpp,$^) -o $@ + +clean: + rm -f range_tree_test + +.PHONY: clean test diff --git a/12-range_tree/cpp/range_tree.h b/12-range_tree/cpp/range_tree.h new file mode 100644 index 0000000000000000000000000000000000000000..61d238ce68fc5df8ac00910dbabe3971df423b4e --- /dev/null +++ b/12-range_tree/cpp/range_tree.h @@ -0,0 +1,177 @@ +#include <cstdint> +#include <limits> + +// A node of the tree +class Node { + public: + int64_t key; + int64_t value; + Node* left; + Node* right; + Node* parent; + + // Constructor + Node(int64_t key, int64_t value, Node* parent=nullptr, Node* left=nullptr, Node* right=nullptr) { + this->key = key; + this->value = value; + this->parent = parent; + this->left = left; + this->right = right; + if (left) left->parent = this; + if (right) right->parent = this; + } + +}; + +// Splay tree +class Tree { + public: + // Pointer to root of the tree; nullptr if the tree is empty. + Node* root; + + Tree(Node* root=nullptr) { + this->root = root; + } + + // Rotate the given `node` up. Perform a single rotation of the edge + // between the node and its parent, choosing left or right rotation + // appropriately. + virtual void rotate(Node* node) { + if (node->parent) { + if (node->parent->left == node) { + if (node->right) node->right->parent = node->parent; + node->parent->left = node->right; + node->right = node->parent; + } else { + if (node->left) node->left->parent = node->parent; + node->parent->right = node->left; + node->left = node->parent; + } + if (node->parent->parent) { + if (node->parent->parent->left == node->parent) + node->parent->parent->left = node; + else + node->parent->parent->right = node; + } else { + root = node; + } + + Node* original_parent = node->parent; + node->parent = node->parent->parent; + original_parent->parent = node; + } + } + + // Splay the given node. + virtual void splay(Node* node) { + while (node->parent && node->parent->parent) { + if ((node->parent->right == node && node->parent->parent->right == node->parent) || + (node->parent->left == node && node->parent->parent->left == node->parent)) { + rotate(node->parent); + rotate(node); + } else { + rotate(node); + rotate(node); + } + } + if (node->parent) + rotate(node); + } + + // Look up the given key in the tree, returning the + // the node with the requested key or nullptr. + Node* lookup(int64_t key) { + Node* node = root; + Node* node_last = nullptr; + while (node) { + node_last = node; + if (node->key == key) + break; + if (key < node->key) + node = node->left; + else + node = node->right; + } + if (node_last) + splay(node_last); + return node; + } + + // Insert a (key, value) into the tree. + // If the key is already present, nothing happens. + void insert(int64_t key, int64_t value) { + if (!root) { + root = new Node(key, value); + return; + } + + Node* node = root; + while (node->key != key) { + if (key < node->key) { + if (!node->left) + node->left = new Node(key, value, node); + node = node->left; + } else { + if (!node->right) + node->right = new Node(key, value, node); + node = node->right; + } + } + splay(node); + } + + // Delete given key from the tree. + // It the key is not present, do nothing. + // + // The implementation first splays the element to be removed to + // the root, and if it has both children, splays the largest element + // in the left subtree and links it to the original right subtree. + void remove(int64_t key) { + if (lookup(key)) { + Node* right = root->right; + root = root->left; + if (!root) { + root = right; + right = nullptr; + } + if (root) + root->parent = nullptr; + + if (right) { + Node* node = root; + while (node->right) + node = node->right; + splay(node); + root->right = right; + right->parent = root; + } + } + } + + // Return number of elements in range [left, right]. + // + // Given a closed range [left, right], return the sum of values of elements + // in the range, i.e., sum(value | (key, value) in tree, left <= key <= right). + int64_t range_sum(int64_t left, int64_t right) { + // TODO: Implement + } + + // Destructor to free all allocated memory. + ~Tree() { + Node* node = root; + while (node) { + Node* next; + if (node->left) { + next = node->left; + node->left = nullptr; + } else if (node->right) { + next = node->right; + node->right = nullptr; + } else { + next = node->parent; + delete node; + } + node = next; + } + } +}; diff --git a/12-range_tree/cpp/range_tree_test.cpp b/12-range_tree/cpp/range_tree_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..95d016921de8e492acf0bdcf7dcd2cd9c5d70549 --- /dev/null +++ b/12-range_tree/cpp/range_tree_test.cpp @@ -0,0 +1,81 @@ +#include <algorithm> +#include <functional> +#include <string> +#include <utility> +#include <vector> + +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); + +#include "range_tree.h" + +void create_test_tree(int64_t size, bool ascending, Tree& tree) { + vector<int64_t> sequence = {7}; + for (int64_t i = 2; i < size; i++) + sequence.push_back((sequence.back() * sequence.front()) % size); + if (ascending) + sort(sequence.begin(), sequence.end()); + + for (int64_t element : sequence) + tree.insert(element, element); +} + +void test_missing(int64_t size, bool ascending) { + Tree tree; + create_test_tree(size, ascending, tree); + + int64_t values = 0; + for (int64_t i = 0; i < size; i++) + values += tree.range_sum(-size, 0) + tree.range_sum(size, 2 * size); + EXPECT(values == 0, "Expected no values in an empty range"); +} + +void test_suffixes(int64_t size, bool ascending) { + Tree tree; + create_test_tree(size, ascending, tree); + + for (int64_t left = 1; left < size; left++) { + int64_t values = tree.range_sum(left, size - 1); + int64_t expected = size * (size - 1) / 2 - left * (left - 1) / 2; + EXPECT(values == expected, + "Expected " + to_string(expected) + " for range [" + to_string(left) + + ", " + to_string(size - 1) + "], got " + to_string(values)); + } +} + +void test_updates(int64_t size, bool ascending) { + Tree tree; + create_test_tree(size, ascending, tree); + + for (int64_t left = 1; left < size; left++) { + tree.remove(left); + tree.insert(left + size - 1, left + size - 1); + int64_t values = tree.range_sum(left + 1, size + left); + int64_t expected = (size + left) * (size + left - 1) / 2 - (left + 1) * left / 2; + EXPECT(values == expected, + "Expected " + to_string(expected) + " for range [" + to_string(left + 1) + + ", " + to_string(size + left) + "], got " + to_string(values)); + } +} + + +vector<pair<string, function<void()>>> tests = { + {"random_missing", [] { test_missing(13, false); }}, + {"random_suffixes", [] { test_suffixes(13, false); }}, + {"random_updates", [] { test_updates(13, false); }}, + + {"path_missing", [] { test_missing(13, true); }}, + {"path_suffixes", [] { test_suffixes(13, true); }}, + {"path_updates", [] { test_updates(13, true); }}, + + {"random_missing_big", [] { test_missing(199999, false); }}, + {"random_suffixes_big", [] { test_suffixes(199999, false); }}, + {"random_updates_big", [] { test_updates(199999, false); }}, + + {"path_missing_big", [] { test_missing(199999, true); }}, + {"path_suffixes_big", [] { test_suffixes(199999, true); }}, + {"path_updates_big", [] { test_updates(199999, true); }}, +}; diff --git a/12-range_tree/cpp/test_main.cpp b/12-range_tree/cpp/test_main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f4aff0785f636b7fd0ea1a15aa69dafe06f290f --- /dev/null +++ b/12-range_tree/cpp/test_main.cpp @@ -0,0 +1,43 @@ +#include <cstdlib> +#include <functional> +#include <iostream> +#include <string> +#include <utility> +#include <vector> + +using namespace std; + +extern vector<pair<string, function<void()>>> tests; + +void expect_failed(const string& message) { + cerr << "Test error: " << message << endl; + exit(1); +} + +int main(int argc, char* argv[]) { + vector<string> required_tests; + + if (argc > 1) { + required_tests.assign(argv + 1, argv + argc); + } else { + for (const auto& test : tests) + required_tests.push_back(test.first); + } + + for (const auto& required_test : required_tests) { + bool found = false; + for (const auto& test : tests) + if (required_test == test.first) { + cerr << "Running test " << required_test << endl; + test.second(); + found = true; + break; + } + if (!found) { + cerr << "Unknown test " << required_test << endl; + return 1; + } + } + + return 0; +} diff --git a/12-range_tree/python/range_tree.py b/12-range_tree/python/range_tree.py new file mode 100644 index 0000000000000000000000000000000000000000..161f79d6bd50636b6d03ac15426c8b763c2858d2 --- /dev/null +++ b/12-range_tree/python/range_tree.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +import math + +class Node: + """Node in a binary tree `Tree`""" + + def __init__(self, key, value, left=None, right=None, parent=None): + self.key = key + self.value = value + self.parent = parent + self.left = left + self.right = right + if left is not None: left.parent = self + if right is not None: right.parent = self + +class Tree: + """A splay tree implementation""" + + def __init__(self, root=None): + self.root = root + + def rotate(self, node): + """ Rotate the given `node` up. + + Performs a single rotation of the edge between the given node + and its parent, choosing left or right rotation appropriately. + """ + if node.parent is not None: + if node.parent.left == node: + if node.right is not None: node.right.parent = node.parent + node.parent.left = node.right + node.right = node.parent + else: + if node.left is not None: node.left.parent = node.parent + node.parent.right = node.left + node.left = node.parent + if node.parent.parent is not None: + if node.parent.parent.left == node.parent: + node.parent.parent.left = node + else: + node.parent.parent.right = node + else: + self.root = node + node.parent.parent, node.parent = node, node.parent.parent + + def splay(self, node): + """Splay the given node""" + while node.parent is not None and node.parent.parent is not None: + if (node.parent.right == node and node.parent.parent.right == node.parent) or \ + (node.parent.left == node and node.parent.parent.left == node.parent): + self.rotate(node.parent) + self.rotate(node) + else: + self.rotate(node) + self.rotate(node) + if node.parent is not None: + self.rotate(node) + + def lookup(self, key): + """Look up the given key in the tree. + + Returns the node with the requested key or `None`. + """ + node, node_last = self.root, None + while node is not None: + node_last = node + if node.key == key: + break + if key < node.key: + node = node.left + else: + node = node.right + if node_last is not None: + self.splay(node_last) + return node + + def insert(self, key, value): + """Insert (key, value) into the tree. + + If the key is already present, do nothing. + """ + if self.root is None: + self.root = Node(key, value) + return + + node = self.root + while node.key != key: + if key < node.key: + if node.left is None: + node.left = Node(key, value, parent=node) + node = node.left + else: + if node.right is None: + node.right = Node(key, value, parent=node) + node = node.right + self.splay(node) + + def remove(self, key): + """Remove given key from the tree. + + It the key is not present, do nothing. + + The implementation first splays the element to be removed to + the root, and if it has both children, splays the largest element + in the left subtree and links it to the original right subtree. + """ + if self.lookup(key) is not None: + right = self.root.right + self.root = self.root.left + if self.root is None: + self.root, right = right, None + if self.root is not None: + self.root.parent = None + + if right is not None: + node = self.root + while node.right is not None: + node = node.right + self.splay(node) + self.root.right = right + right.parent = self.root + + def range_sum(self, left, right): + """Return number of elements in range [left, right] + + Given a closed range [left, right], return the sum of values of elements + in the range, i.e., sum(value | (key, value) in tree, left <= key <= right). + """ + raise NotImplementedError() diff --git a/12-range_tree/python/range_tree_test.py b/12-range_tree/python/range_tree_test.py new file mode 100644 index 0000000000000000000000000000000000000000..bc866e16df3de9d97c9c12ce9a0d4a0291611d6f --- /dev/null +++ b/12-range_tree/python/range_tree_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import sys + +from range_tree import Tree + +def test_tree(size, ascending): + sequence = [pow(7, i, size) for i in range(1, size)] + if ascending: sequence = sorted(sequence) + + tree = Tree() + for element in sequence: + tree.insert(element, element) + return tree + +def test_missing(size, ascending): + tree = test_tree(size, ascending) + + values = 0 + for _ in range(size): + values += tree.range_sum(-size, 0) + values += tree.range_sum(size, 2 * size) + assert values == 0, "Expected no values in an empty range" + +def test_suffixes(size, ascending): + tree = test_tree(size, ascending) + + for left in range(1, size): + values = tree.range_sum(left, size - 1) + expected = size * (size - 1) // 2 - left * (left - 1) // 2 + assert values == expected, "Expected {} for range [{}, {}], got {}".format(expected, left, size - 1, values) + +def test_updates(size, ascending): + tree = test_tree(size, ascending) + + for left in range(1, size): + tree.remove(left) + tree.insert(left + size - 1, left + size - 1) + values = tree.range_sum(left + 1, size + left) + expected = (size + left) * (size + left - 1) // 2 - (left + 1) * left // 2 + assert values == expected, "Expected {} for range [{}, {}], got {}".format(expected, left + 1, size + left, values) + +tests = [ + ("random_missing", lambda: test_missing(13, False)), + ("random_suffixes", lambda: test_suffixes(13, False)), + ("random_updates", lambda: test_updates(13, False)), + + ("path_missing", lambda: test_missing(13, True)), + ("path_suffixes", lambda: test_suffixes(13, True)), + ("path_updates", lambda: test_updates(13, True)), + + ("random_missing_big", lambda: test_missing(19997, False)), + ("random_suffixes_big", lambda: test_suffixes(19997, False)), + ("random_updates_big", lambda: test_updates(19997, False)), + + ("path_missing_big", lambda: test_missing(19997, True)), + ("path_suffixes_big", lambda: test_suffixes(19997, True)), + ("path_updates_big", lambda: test_updates(19997, True)), +] + +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/12-range_tree/task.md b/12-range_tree/task.md new file mode 100644 index 0000000000000000000000000000000000000000..8f0f06442b9afd469dff5c349114c6f26e2fe2d1 --- /dev/null +++ b/12-range_tree/task.md @@ -0,0 +1,22 @@ +You are given an implementation of a splay tree which associates every numeric +key with a numeric value. The splay tree provides `lookup`, `insert`, and `remove` +operations. + +Your goal is to modify the splay tree to support range queries in amortized +logarithmic time. The operation you need to implement takes an range on the +input and should return the sum of values of the elements in the given range. + +As usual, you should submit only the `range_tree.{h,py}` file. + +## Optional: Range updates (for 5 points) + +If you also implement an operation +``` +range_update(left, right, delta) +``` +which adds `delta` to the value of all elements with key in `[left, right]` range +and runs in amortized logarithmic time, you will get 5 points. + +Currently there are no automated tests for this method; therefore, if you +implement it, submit the solution to ReCodEx and write an email to your +teaching assistant.