logic state test
This commit is contained in:
parent
eab192ebd3
commit
d154f8f1a6
20 changed files with 211642 additions and 461 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
project(FileSystemEmulator)
|
project(FSE)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
|
|
@ -18,12 +18,16 @@ target_link_libraries(interactive ${PROJECT_NAME})
|
||||||
|
|
||||||
add_executable(streamExec "app/StreamReader.cpp")
|
add_executable(streamExec "app/StreamReader.cpp")
|
||||||
target_link_libraries(streamExec ${PROJECT_NAME})
|
target_link_libraries(streamExec ${PROJECT_NAME})
|
||||||
file(COPY "test/commands" DESTINATION "${CMAKE_BINARY_DIR}/")
|
|
||||||
|
|
||||||
file(GLOB TEST_SOURCES "./test/*.cpp")
|
add_executable(TestFSE "test/Tests.cpp")
|
||||||
add_executable(Test${PROJECT_NAME} ${TEST_SOURCES})
|
target_link_libraries(TestFSE ${PROJECT_NAME} UnitTest++)
|
||||||
target_link_libraries(Test${PROJECT_NAME} ${PROJECT_NAME} UnitTest++)
|
|
||||||
|
|
||||||
|
add_executable(TestConsistency "test/TestConsistency.cpp")
|
||||||
|
target_link_libraries(TestConsistency ${PROJECT_NAME} UnitTest++)
|
||||||
|
|
||||||
add_test(NAME UnitTests COMMAND Test${PROJECT_NAME})
|
file(COPY "test/consistency_state" DESTINATION "${CMAKE_BINARY_DIR}/")
|
||||||
add_test(NAME StreamTest COMMAND valgrind --leak-check=full ./streamExec)
|
file(COPY "test/consistency_commands" DESTINATION "${CMAKE_BINARY_DIR}/")
|
||||||
|
file(COPY "test/checkConsistency" DESTINATION "${CMAKE_BINARY_DIR}/")
|
||||||
|
|
||||||
|
add_test(NAME UnitTests COMMAND valgrind ./TestFSE)
|
||||||
|
add_test(NAME TestConsistency COMMAND ./checkConsistency)
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,7 @@ int main(int argc, char* argv[]) {
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(inputFile, line)) {
|
while (std::getline(inputFile, line)) {
|
||||||
std::cout << ">> " << line << std::endl;
|
std::cout << ">> " << line << std::endl;
|
||||||
bool shouldStop = !interpreter.interpret(line);
|
if (!interpreter.interpret(line)) break;
|
||||||
// if (shouldStop) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void getMaxDepthUtil(ui32 depth, ui32& maxDepth) const;
|
void getMaxDepthUtil(ui32 depth, ui32& maxDepth) const;
|
||||||
void dumpUtil(std::stringstream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) override;
|
void dumpUtil(std::ostream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DirectoryTree mMembers;
|
DirectoryTree mMembers;
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
// tests
|
// use single vector for incoming links
|
||||||
// links have unique names
|
// move link nodes from link to node class or vise versa
|
||||||
// use C:/ for root
|
|
||||||
// copy operator exit if exists, do not rename
|
|
||||||
// clean-ups
|
// clean-ups
|
||||||
|
// links have unique names
|
||||||
|
// copy operator exit if exists, do not rename
|
||||||
// in subtree links should be in-tree after copy operation
|
// in subtree links should be in-tree after copy operation
|
||||||
// update link if exists
|
// update link if exists
|
||||||
|
|
||||||
|
|
@ -27,6 +27,7 @@ public:
|
||||||
bool moveNode(const Path& source, const Path& to);
|
bool moveNode(const Path& source, const Path& to);
|
||||||
bool makeLink(const Path& source, const Path& to, bool isDynamic);
|
bool makeLink(const Path& source, const Path& to, bool isDynamic);
|
||||||
|
|
||||||
|
std::ostream& dump(std::ostream& stream) const;
|
||||||
void log() const;
|
void log() const;
|
||||||
ui64 size() const;
|
ui64 size() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ public:
|
||||||
bool interpret(const std::string& command);
|
bool interpret(const std::string& command);
|
||||||
void printHelp();
|
void printHelp();
|
||||||
|
|
||||||
|
void dumpToFile(const std::string& name) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void reportError(const std::string& string) const;
|
void reportError(const std::string& string) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public:
|
||||||
|
|
||||||
// link on link is not allowed, so no inf looping here
|
// link on link is not allowed, so no inf looping here
|
||||||
std::shared_ptr<Node> findNode(const std::vector<Key>& path, ui32 currentDepth) override;
|
std::shared_ptr<Node> findNode(const std::vector<Key>& path, ui32 currentDepth) override;
|
||||||
void dumpUtil(std::stringstream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) override;
|
void dumpUtil(std::ostream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) override;
|
||||||
|
|
||||||
static bool linkNodes(const std::shared_ptr<Link>& link, const std::shared_ptr<Node>& target, bool hard);
|
static bool linkNodes(const std::shared_ptr<Link>& link, const std::shared_ptr<Node>& target, bool hard);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public:
|
||||||
virtual bool detachNode(const Key& key) { return false; }
|
virtual bool detachNode(const Key& key) { return false; }
|
||||||
virtual bool attachNode(const Key &newKey, std::shared_ptr<Node> newNode) { return false; }
|
virtual bool attachNode(const Key &newKey, std::shared_ptr<Node> newNode) { return false; }
|
||||||
virtual void getMaxDepthUtil(ui32 depth, ui32& maxDepth) const {}
|
virtual void getMaxDepthUtil(ui32 depth, ui32& maxDepth) const {}
|
||||||
virtual void dumpUtil(std::stringstream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents);
|
virtual void dumpUtil(std::ostream& stream, const Key& key, ui32 currentDepth, std::vector<bool>& indents);
|
||||||
virtual ui64 size() const { return 0; }
|
virtual ui64 size() const { return 0; }
|
||||||
virtual std::shared_ptr<Node> getTarget();
|
virtual std::shared_ptr<Node> getTarget();
|
||||||
virtual std::shared_ptr<Node> findNode(const std::vector<Key>& path, ui32 currentDepth = 0);
|
virtual std::shared_ptr<Node> findNode(const std::vector<Key>& path, ui32 currentDepth = 0);
|
||||||
|
|
@ -43,10 +43,10 @@ public:
|
||||||
virtual void removeOutgoingLinks() {}
|
virtual void removeOutgoingLinks() {}
|
||||||
|
|
||||||
ui32 getMaxDepth() const;
|
ui32 getMaxDepth() const;
|
||||||
void dump(std::stringstream& ss);
|
std::ostream& dump(std::ostream &stream);
|
||||||
void getNodeStraightPath(const std::shared_ptr<Node>& node, std::vector<std::shared_ptr<Node>>& path) const;
|
void getNodeStraightPath(const std::shared_ptr<Node>& node, std::vector<std::shared_ptr<Node>>& path) const;
|
||||||
bool empty() const { return !size(); }
|
bool empty() const { return !size(); }
|
||||||
static void indent(std::stringstream & ss, ui32 depth, std::vector<bool>& indents);
|
static void indent(std::ostream& ss, ui32 depth, std::vector<bool>& indents);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Key mKey;
|
Key mKey;
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ void Directory::getMaxDepthUtil(ui32 depth, ui32& maxDepth) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Directory::dumpUtil(std::stringstream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) {
|
void Directory::dumpUtil(std::ostream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) {
|
||||||
indents[currentDepth] = true;
|
indents[currentDepth] = true;
|
||||||
indent(ss, currentDepth, indents);
|
indent(ss, currentDepth, indents);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -381,19 +381,7 @@ bool FileSystem::moveNode(const Path &source, const Path &target) {
|
||||||
|
|
||||||
void FileSystem::log() const {
|
void FileSystem::log() const {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
dump(ss);
|
||||||
std::vector<std::shared_ptr<Node>> currentPath;
|
|
||||||
root->getNodeStraightPath(currentDirectory, currentPath);
|
|
||||||
std::reverse(currentPath.begin(), currentPath.end());
|
|
||||||
|
|
||||||
ss << "pwd [ ";
|
|
||||||
for (auto it = currentPath.begin(); it != currentPath.end(); ++it) {
|
|
||||||
ss << (*it)->mKey;
|
|
||||||
if (std::distance(it, currentPath.end()) > 1) ss << "/";
|
|
||||||
}
|
|
||||||
ss << " ]\n";
|
|
||||||
|
|
||||||
root->dump(ss);
|
|
||||||
std::cout << ss.str();
|
std::cout << ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -424,4 +412,21 @@ bool FileSystem::isPathContains(const std::shared_ptr<Node>& node, const std::sh
|
||||||
|
|
||||||
ui64 FileSystem::size() const {
|
ui64 FileSystem::size() const {
|
||||||
return root->size();
|
return root->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::ostream& FileSystem::dump(std::ostream &stream) const {
|
||||||
|
std::vector<std::shared_ptr<Node>> currentPath;
|
||||||
|
root->getNodeStraightPath(currentDirectory, currentPath);
|
||||||
|
std::reverse(currentPath.begin(), currentPath.end());
|
||||||
|
|
||||||
|
stream << "pwd [ ";
|
||||||
|
for (auto it = currentPath.begin(); it != currentPath.end(); ++it) {
|
||||||
|
stream << (*it)->mKey;
|
||||||
|
if (std::distance(it, currentPath.end()) > 1) stream << "/";
|
||||||
|
}
|
||||||
|
stream << " ]\n";
|
||||||
|
|
||||||
|
root->dump(stream);
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
void getWords(const std::string& in, std::vector<std::string>& out) {
|
void getWords(const std::string& in, std::vector<std::string>& out) {
|
||||||
std::stringstream ss(in);
|
std::stringstream ss(in);
|
||||||
|
|
@ -93,7 +94,7 @@ Interpreter::Interpreter() {
|
||||||
|
|
||||||
void Interpreter::reportError(const std::string& description) const {
|
void Interpreter::reportError(const std::string& description) const {
|
||||||
if (logType == DEFAULT)
|
if (logType == DEFAULT)
|
||||||
std::cout << "ERROR: " << description << "\n\n";
|
std::cerr << "ERROR: " << description << "\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::printHelp() {
|
void Interpreter::printHelp() {
|
||||||
|
|
@ -147,3 +148,8 @@ bool Interpreter::interpret(const std::string& command) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interpreter::dumpToFile(const std::string& name) const {
|
||||||
|
std::ofstream stream(name);
|
||||||
|
mFileSystem.dump(stream);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ bool Link::linkNodes(const std::shared_ptr<Link>& link, const std::shared_ptr<No
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO : move from link to node class or vise versa
|
|
||||||
void Link::removeOutgoingLinks() {
|
void Link::removeOutgoingLinks() {
|
||||||
auto target = mLink.lock();
|
auto target = mLink.lock();
|
||||||
assert(target);
|
assert(target);
|
||||||
|
|
@ -67,7 +66,7 @@ std::shared_ptr<Node> Link::findNode(const std::vector<Key>& path, ui32 currentD
|
||||||
return getLink()->findNode(path, currentDepth);
|
return getLink()->findNode(path, currentDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Link::dumpUtil(std::stringstream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) {
|
void Link::dumpUtil(std::ostream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) {
|
||||||
std::vector<std::shared_ptr<Node>> path;
|
std::vector<std::shared_ptr<Node>> path;
|
||||||
getNodeStraightPath(getLink(), path);
|
getNodeStraightPath(getLink(), path);
|
||||||
std::reverse(path.begin(), path.end());
|
std::reverse(path.begin(), path.end());
|
||||||
|
|
|
||||||
17
src/Node.cpp
17
src/Node.cpp
|
|
@ -73,21 +73,22 @@ void Node::getNodeStraightPath(const std::shared_ptr<Node>& node, std::vector<st
|
||||||
getNodeStraightPath(node->mParent.lock(), path);
|
getNodeStraightPath(node->mParent.lock(), path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::dump(std::stringstream& ss) {
|
std::ostream& Node::dump(std::ostream &stream) {
|
||||||
std::vector<bool> indents;
|
std::vector<bool> indents;
|
||||||
indents.resize(getMaxDepth());
|
indents.resize(getMaxDepth());
|
||||||
dumpUtil(ss, mKey, 0, indents);
|
dumpUtil(stream, mKey, 0, indents);
|
||||||
ss << "\n";
|
stream << "\n";
|
||||||
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::dumpUtil(std::stringstream& ss, const Key& key, ui32 currentDepth, std::vector<bool>& indents) {
|
void Node::dumpUtil(std::ostream &stream, const Key& key, ui32 currentDepth, std::vector<bool>& indents) {
|
||||||
indent(ss, currentDepth, indents);
|
indent(stream, currentDepth, indents);
|
||||||
ss << key;
|
stream << key;
|
||||||
// ss << " [h" << mIncomingHardLinks.size() << ": d" << mIncomingDynamicLinks.size() << "] ";
|
// ss << " [h" << mIncomingHardLinks.size() << ": d" << mIncomingDynamicLinks.size() << "] ";
|
||||||
ss << "\n";
|
stream << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Node::indent(std::stringstream & ss, ui32 depth, std::vector<bool>& indents) {
|
void Node::indent(std::ostream& ss, ui32 depth, std::vector<bool>& indents) {
|
||||||
if (!depth) return;
|
if (!depth) return;
|
||||||
for (auto i = 0; i < depth - 1; i++) {
|
for (auto i = 0; i < depth - 1; i++) {
|
||||||
ss << (indents[i] ? " |" : " ");
|
ss << (indents[i] ? " |" : " ");
|
||||||
|
|
|
||||||
29
test/TestConsistency.cpp
Normal file
29
test/TestConsistency.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
#include "Interpreter.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// generates and saves final state to check if there are any logic changes to the code
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
Interpreter interpreter;
|
||||||
|
interpreter.logType = Interpreter::NONE;
|
||||||
|
|
||||||
|
std::ifstream inputFile("commands");
|
||||||
|
|
||||||
|
if (!inputFile.is_open()) {
|
||||||
|
std::cerr << "Failed to open the file." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(inputFile, line)) {
|
||||||
|
interpreter.interpret(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputFile.close();
|
||||||
|
|
||||||
|
interpreter.dumpToFile("current_state");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
#include "Tests.hpp"
|
#include "Tests.hpp"
|
||||||
|
|
||||||
SUITE(FSE) {
|
SUITE(FSE) {
|
||||||
|
|
@ -48,11 +49,11 @@ SUITE(FSE) {
|
||||||
{ "mhl", 20 },
|
{ "mhl", 20 },
|
||||||
{ "mdl", 20 },
|
{ "mdl", 20 },
|
||||||
{ "cd", 5 },
|
{ "cd", 5 },
|
||||||
{ "rd", 1 },
|
{ "rd", 5 },
|
||||||
{ "del", 10 },
|
{ "del", 1 },
|
||||||
{ "deltree", 10 },
|
{ "deltree", 2 },
|
||||||
{ "move", 20 },
|
{ "move", 20 },
|
||||||
{ "copy", 20 },
|
{ "copy", 5 },
|
||||||
});
|
});
|
||||||
|
|
||||||
StringDistribution names({
|
StringDistribution names({
|
||||||
|
|
@ -62,6 +63,8 @@ SUITE(FSE) {
|
||||||
{ "c", 1 },
|
{ "c", 1 },
|
||||||
{ "d", 1 },
|
{ "d", 1 },
|
||||||
{ "e", 1 },
|
{ "e", 1 },
|
||||||
|
{ "f", 1 },
|
||||||
|
{ "g", 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
std::discrete_distribution<int> pathLenDist({ 0.1, 0.2, 0.4, 0.5, 0.1 });
|
std::discrete_distribution<int> pathLenDist({ 0.1, 0.2, 0.4, 0.5, 0.1 });
|
||||||
|
|
@ -75,13 +78,18 @@ SUITE(FSE) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::ofstream commandsDump("consistency_commands_new");
|
||||||
|
|
||||||
runFor(interpreter, timeLimitSec, itemLimit, [&]() {
|
runFor(interpreter, timeLimitSec, itemLimit, [&]() {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << commands.get();
|
ss << commands.get();
|
||||||
generatePath(ss);
|
generatePath(ss);
|
||||||
if (pathLenDist(rng) % 2) generatePath(ss);
|
if (pathLenDist(rng) % 2) generatePath(ss);
|
||||||
|
commandsDump << ss.str() << "\n";
|
||||||
return ss.str();
|
return ss.str();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interpreter.dumpToFile("consistency_state_new");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
const ui64 timeLimitSec = 3;
|
const ui64 timeLimitSec = 1;
|
||||||
const ui64 itemLimit = 1e6;
|
const ui64 itemLimit = 1e6;
|
||||||
|
|
||||||
std::random_device rd;
|
std::random_device rd;
|
||||||
|
|
|
||||||
3
test/consistency/check
Executable file
3
test/consistency/check
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
cd consistency
|
||||||
|
../TestConsistency
|
||||||
|
diff -s state current_state
|
||||||
204791
test/consistency/commands
Normal file
204791
test/consistency/commands
Normal file
File diff suppressed because it is too large
Load diff
6745
test/consistency/state
Normal file
6745
test/consistency/state
Normal file
File diff suppressed because it is too large
Load diff
412
tmp
412
tmp
|
|
@ -1,412 +0,0 @@
|
||||||
|
|
||||||
// WRITE LOG
|
|
||||||
// WRITE TESTS
|
|
||||||
|
|
||||||
// use tree for leafs in the node
|
|
||||||
// use 8byte + 3byte for the name of the node, also key in the tree
|
|
||||||
// don't store size in the tree
|
|
||||||
// better use RB tree
|
|
||||||
|
|
||||||
// each node type has different size
|
|
||||||
// implement node using virtual functions and inheritance
|
|
||||||
// only directory node has leafs (tree)
|
|
||||||
|
|
||||||
// make two classes - emulator and interpreter
|
|
||||||
// emulator stores only lower case names
|
|
||||||
|
|
||||||
// store number of incoming hard links in the node cache to ensure no hard-linked nodes can be removed
|
|
||||||
|
|
||||||
|
|
||||||
// convert command to lowercase then use full path as the key and update key pointer when descending to the leafs
|
|
||||||
|
|
||||||
// each node has parent pointer to check for current directory
|
|
||||||
|
|
||||||
// base node class has all the cache data. use it directly in the tree
|
|
||||||
// dynamic links ? -> yet another cache variable in each node?
|
|
||||||
|
|
||||||
/*
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ContainersCommon.hpp"
|
|
||||||
|
|
||||||
namespace tp {
|
|
||||||
|
|
||||||
template <typename NumericType>
|
|
||||||
struct AvlNumericKey {
|
|
||||||
|
|
||||||
NumericType val;
|
|
||||||
|
|
||||||
AvlNumericKey() = default;
|
|
||||||
AvlNumericKey(NumericType val) :
|
|
||||||
val(val) {}
|
|
||||||
|
|
||||||
inline bool descentRight(AvlNumericKey in) const { return in.val > val; }
|
|
||||||
inline bool descentLeft(AvlNumericKey in) const { return in.val < val; }
|
|
||||||
inline bool exactNode(AvlNumericKey in) const { return in.val == val; }
|
|
||||||
|
|
||||||
inline AvlNumericKey getFindKey() const { return *this; }
|
|
||||||
inline AvlNumericKey keyInRightSubtree(AvlNumericKey in) const { return in; }
|
|
||||||
inline AvlNumericKey keyInLeftSubtree(AvlNumericKey in) const { return in; }
|
|
||||||
|
|
||||||
template <typename NodeType>
|
|
||||||
inline void updateTreeCacheCallBack(const NodeType&) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Key, typename Data, class Allocator = DefaultAllocator>
|
|
||||||
class AvlTree {
|
|
||||||
typedef SelectValueOrReference<Key> KeyArg;
|
|
||||||
typedef SelectValueOrReference<Data> DataArg;
|
|
||||||
|
|
||||||
public:
|
|
||||||
class Node {
|
|
||||||
friend AvlTree;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Node(KeyArg aKey, DataArg aData) :
|
|
||||||
key(aKey),
|
|
||||||
data(aData) {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Data data;
|
|
||||||
Key key;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Node* mLeft = nullptr;
|
|
||||||
Node* mRight = nullptr;
|
|
||||||
Node* mParent = nullptr;
|
|
||||||
ualni mHeight = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline bool descentRight(KeyArg aKey) const { return key.descentRight(aKey); }
|
|
||||||
inline bool descentLeft(KeyArg aKey) const { return key.descentLeft(aKey); }
|
|
||||||
inline bool exactNode(KeyArg aKey) const { return key.exactNode(aKey); }
|
|
||||||
|
|
||||||
inline KeyArg getFindKey(const Node* node = nullptr) const { return key.getFindKey(); }
|
|
||||||
inline KeyArg keyInRightSubtree(KeyArg aKey) const { return key.keyInRightSubtree(aKey); }
|
|
||||||
inline KeyArg keyInLeftSubtree(KeyArg aKey) const { return key.keyInLeftSubtree(aKey); }
|
|
||||||
|
|
||||||
inline void updateTreeCacheCallBack() { key.updateTreeCacheCallBack(*this); }
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
AvlTree() {}
|
|
||||||
~AvlTree() { removeAll(); }
|
|
||||||
|
|
||||||
[[nodiscard]] ualni size() const { return mSize; }
|
|
||||||
|
|
||||||
Node* head() const { return this->mRoot; }
|
|
||||||
|
|
||||||
void insert(KeyArg key, DataArg data) {
|
|
||||||
mRoot = insertUtil(mRoot, key, data);
|
|
||||||
mRoot->mParent = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove(KeyArg key) {
|
|
||||||
mRoot = removeUtil(mRoot, key);
|
|
||||||
if (mRoot) mRoot->mParent = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node* maxNode(Node* head) const {
|
|
||||||
if (!head) return nullptr;
|
|
||||||
while (head->mRight != nullptr) {
|
|
||||||
head = head->mRight;
|
|
||||||
}
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node* minNode(Node* head) const {
|
|
||||||
if (!head) return nullptr;
|
|
||||||
while (head->mLeft != nullptr) {
|
|
||||||
head = head->mLeft;
|
|
||||||
}
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node* find(KeyArg key) const {
|
|
||||||
Node* iter = mRoot;
|
|
||||||
while (true) {
|
|
||||||
if (!iter) return nullptr;
|
|
||||||
if (iter->exactNode(key)) return iter;
|
|
||||||
if (iter->descentLeft(key)) {
|
|
||||||
key = iter->keyInLeftSubtree(key);
|
|
||||||
iter = iter->mLeft;
|
|
||||||
} else {
|
|
||||||
key = iter->keyInRightSubtree(key);
|
|
||||||
iter = iter->mRight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Node* findLessOrEq(KeyArg key) const {
|
|
||||||
Node* iter = mRoot;
|
|
||||||
while (true) {
|
|
||||||
if (!iter) return nullptr;
|
|
||||||
if (iter->exactNode(key)) return iter;
|
|
||||||
if (iter->descentLeft(key)) {
|
|
||||||
if (iter->mLeft) {
|
|
||||||
key = iter->keyInLeftSubtree(key);
|
|
||||||
iter = iter->mLeft;
|
|
||||||
} else {
|
|
||||||
return iter;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (iter->mRight) {
|
|
||||||
key = iter->keyInRightSubtree(key);
|
|
||||||
iter = iter->mRight;
|
|
||||||
} else {
|
|
||||||
return iter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns first invalid node
|
|
||||||
const Node* findInvalidNode(const Node* head) const {
|
|
||||||
if (head == nullptr) return nullptr;
|
|
||||||
|
|
||||||
if (head->mLeft) {
|
|
||||||
// TODO: incomplete test
|
|
||||||
if (!head->descentLeft(head->mLeft->getFindKey(head))) return head;
|
|
||||||
if (head->mLeft->mParent != head) return head;
|
|
||||||
if (!head->mRight && head->mLeft->mHeight != head->mHeight - 1) return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (head->mRight) {
|
|
||||||
if (!head->descentRight(head->mRight->getFindKey(head))) return head;
|
|
||||||
if (head->mRight->mParent != head) return head;
|
|
||||||
if (!head->mLeft && head->mRight->mHeight != head->mHeight - 1) return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (head->mLeft && head->mRight) {
|
|
||||||
if (max(head->mLeft->mHeight, head->mRight->mHeight) != head->mHeight - 1) return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
int balance = getNodeHeight(head->mRight) - getNodeHeight(head->mLeft);
|
|
||||||
|
|
||||||
if (balance > 1 || balance < -1) return head;
|
|
||||||
|
|
||||||
const Node* ret = findInvalidNode(head->mRight);
|
|
||||||
|
|
||||||
if (ret) return ret;
|
|
||||||
|
|
||||||
return findInvalidNode(head->mLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() { return findInvalidNode(head()) == nullptr; }
|
|
||||||
|
|
||||||
template <typename tFunctor>
|
|
||||||
void traverse(Node* node, bool after, tFunctor functor) {
|
|
||||||
if (!after) functor(node);
|
|
||||||
if (node->mLeft) traverse(node->mLeft, after, functor);
|
|
||||||
if (node->mRight) traverse(node->mRight, after, functor);
|
|
||||||
if (after) functor(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeAll() {
|
|
||||||
if (!mRoot) return;
|
|
||||||
removeUtil(mRoot);
|
|
||||||
mRoot = nullptr;
|
|
||||||
mSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeUtil(Node* node) {
|
|
||||||
if (node->mLeft) removeUtil(node->mLeft);
|
|
||||||
if (node->mRight) removeUtil(node->mRight);
|
|
||||||
deleteNode(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
template <class tArchiver>
|
|
||||||
void archiveWrite(tArchiver& file) const {
|
|
||||||
FAIL("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class tArchiver>
|
|
||||||
void archiveRead(tArchiver&) {
|
|
||||||
FAIL("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline void deleteNode(Node* node) {
|
|
||||||
node->~Node();
|
|
||||||
mAlloc.deallocate(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Node* newNode(KeyArg key, DataArg data) { return new (mAlloc.allocate(sizeof(Node))) Node(key, data); }
|
|
||||||
|
|
||||||
inline void injectNodeInstead(Node* place, Node* inject) {
|
|
||||||
// TODO : swap instead of copy
|
|
||||||
place->data = inject->data;
|
|
||||||
place->key = inject->key;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline alni getNodeHeight(const Node* node) const { return node ? node->mHeight : -1; }
|
|
||||||
|
|
||||||
// returns new head
|
|
||||||
Node* rotateLeft(Node* pivot) {
|
|
||||||
DEBUG_ASSERT(pivot);
|
|
||||||
|
|
||||||
Node* const head = pivot;
|
|
||||||
Node* const right = pivot->mRight;
|
|
||||||
Node* const right_left = right->mLeft;
|
|
||||||
Node* const parent = pivot->mParent;
|
|
||||||
|
|
||||||
// parents
|
|
||||||
if (right_left) right_left->mParent = head;
|
|
||||||
head->mParent = right;
|
|
||||||
right->mParent = parent;
|
|
||||||
|
|
||||||
// children
|
|
||||||
head->mRight = right_left;
|
|
||||||
right->mLeft = head;
|
|
||||||
|
|
||||||
// heights
|
|
||||||
head->mHeight = 1 + max(getNodeHeight(head->mLeft), getNodeHeight(head->mRight));
|
|
||||||
right->mHeight = 1 + max(getNodeHeight(right->mLeft), getNodeHeight(right->mRight));
|
|
||||||
|
|
||||||
// cache
|
|
||||||
head->updateTreeCacheCallBack();
|
|
||||||
right->updateTreeCacheCallBack();
|
|
||||||
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node* rotateRight(Node* pivot) {
|
|
||||||
DEBUG_ASSERT(pivot);
|
|
||||||
|
|
||||||
Node* const head = pivot;
|
|
||||||
Node* const left = pivot->mLeft;
|
|
||||||
Node* const left_right = left->mRight;
|
|
||||||
Node* const parent = pivot->mParent;
|
|
||||||
|
|
||||||
// parents
|
|
||||||
if (left_right) left_right->mParent = head;
|
|
||||||
head->mParent = left;
|
|
||||||
left->mParent = parent;
|
|
||||||
|
|
||||||
// children
|
|
||||||
head->mLeft = left_right;
|
|
||||||
left->mRight = head;
|
|
||||||
|
|
||||||
// heights
|
|
||||||
head->mHeight = 1 + max(getNodeHeight(head->mLeft), getNodeHeight(head->mRight));
|
|
||||||
left->mHeight = 1 + max(getNodeHeight(left->mLeft), getNodeHeight(left->mRight));
|
|
||||||
|
|
||||||
// cache
|
|
||||||
head->updateTreeCacheCallBack();
|
|
||||||
left->updateTreeCacheCallBack();
|
|
||||||
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
// recursively returns valid isLeft or isRight child or root
|
|
||||||
Node* insertUtil(Node* head, KeyArg key, DataArg data) {
|
|
||||||
|
|
||||||
Node* insertedNode;
|
|
||||||
|
|
||||||
if (head == nullptr) {
|
|
||||||
mSize++;
|
|
||||||
Node* out = newNode(key, data);
|
|
||||||
out->updateTreeCacheCallBack();
|
|
||||||
return out;
|
|
||||||
} else if (head->exactNode(key)) {
|
|
||||||
return head;
|
|
||||||
} else if (head->descentRight(key)) {
|
|
||||||
insertedNode = insertUtil(head->mRight, head->keyInRightSubtree(key), data);
|
|
||||||
head->mRight = insertedNode;
|
|
||||||
insertedNode->mParent = head;
|
|
||||||
} else {
|
|
||||||
insertedNode = insertUtil(head->mLeft, head->keyInLeftSubtree(key), data);
|
|
||||||
head->mLeft = insertedNode;
|
|
||||||
insertedNode->mParent = head;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update height
|
|
||||||
head->mHeight = 1 + max(getNodeHeight(head->mRight), getNodeHeight(head->mLeft));
|
|
||||||
|
|
||||||
alni balance = alni(getNodeHeight(head->mRight) - getNodeHeight(head->mLeft));
|
|
||||||
|
|
||||||
if (balance > 1) {
|
|
||||||
if (head->mRight->descentRight(head->keyInRightSubtree(key))) {
|
|
||||||
return rotateLeft(head);
|
|
||||||
} else {
|
|
||||||
head->mRight = rotateRight(head->mRight);
|
|
||||||
return rotateLeft(head);
|
|
||||||
}
|
|
||||||
} else if (balance < -1) {
|
|
||||||
if (head->mLeft->descentLeft(head->keyInLeftSubtree(key))) {
|
|
||||||
return rotateRight(head);
|
|
||||||
} else {
|
|
||||||
head->mLeft = rotateLeft(head->mLeft);
|
|
||||||
return rotateRight(head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
head->updateTreeCacheCallBack();
|
|
||||||
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node* removeUtil(Node* head, KeyArg key) {
|
|
||||||
if (head == nullptr) return head;
|
|
||||||
|
|
||||||
if (head->exactNode(key)) {
|
|
||||||
if (head->mRight && head->mLeft) {
|
|
||||||
Node* min = minNode(head->mRight);
|
|
||||||
auto const& newKey = min->getFindKey(head->mRight);
|
|
||||||
injectNodeInstead(head, min);
|
|
||||||
head->mRight = removeUtil(head->mRight, newKey);
|
|
||||||
} else if (head->mRight) {
|
|
||||||
injectNodeInstead(head, head->mRight);
|
|
||||||
deleteNode(head->mRight);
|
|
||||||
head->mRight = nullptr;
|
|
||||||
mSize--;
|
|
||||||
} else if (head->mLeft) {
|
|
||||||
injectNodeInstead(head, head->mLeft);
|
|
||||||
deleteNode(head->mLeft);
|
|
||||||
head->mLeft = nullptr;
|
|
||||||
mSize--;
|
|
||||||
} else {
|
|
||||||
deleteNode(head);
|
|
||||||
mSize--;
|
|
||||||
head = nullptr;
|
|
||||||
}
|
|
||||||
} else if (head->descentRight(key)) {
|
|
||||||
head->mRight = removeUtil(head->mRight, head->keyInRightSubtree(key));
|
|
||||||
} else if (head->descentLeft(key)) {
|
|
||||||
head->mLeft = removeUtil(head->mLeft, head->keyInLeftSubtree(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (head == nullptr) return head;
|
|
||||||
|
|
||||||
head->mHeight = 1 + max(getNodeHeight(head->mRight), getNodeHeight(head->mLeft));
|
|
||||||
alni balance = getNodeHeight(head->mRight) - getNodeHeight(head->mLeft);
|
|
||||||
|
|
||||||
if (balance < -1) {
|
|
||||||
if (getNodeHeight(head->mLeft->mLeft) >= getNodeHeight(head->mLeft->mRight)) {
|
|
||||||
return rotateRight(head);
|
|
||||||
} else {
|
|
||||||
head->mLeft = rotateLeft(head->mLeft);
|
|
||||||
return rotateRight(head);
|
|
||||||
}
|
|
||||||
} else if (balance > 1) {
|
|
||||||
if (getNodeHeight(head->mRight->mRight) >= getNodeHeight(head->mRight->mLeft)) {
|
|
||||||
return rotateLeft(head);
|
|
||||||
} else {
|
|
||||||
head->mRight = rotateRight(head->mRight);
|
|
||||||
return rotateLeft(head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
head->updateTreeCacheCallBack();
|
|
||||||
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Node* mRoot = nullptr;
|
|
||||||
ualni mSize = 0;
|
|
||||||
Allocator mAlloc;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue