IMPLEMENT TREAP PERSISTENCE

This commit is contained in:
Matúš Púll 2025-06-25 00:01:49 +02:00
parent 9fd135e540
commit 495bf542cf
5 changed files with 347 additions and 103 deletions

View file

@ -65,9 +65,6 @@ count_type Editor::get_number(string prompt) {
} }
// TODO undo
// Jump to end of line // Jump to end of line
void Editor::jump_line_end() { void Editor::jump_line_end() {
count_type line_size = file.get_line(file_offset+cur.r).size(); count_type line_size = file.get_line(file_offset+cur.r).size();
@ -150,6 +147,12 @@ find_result Editor::find(string text) {
} }
return {false, position()}; return {false, position()};
} }
void Editor::replace(position p, count_type length, string text) {
string line_text = file.get_line(p.r);
line_text.erase(p.c, length);
line_text.insert(p.c, text);
file.update(p.r, line_text);
}
bool Editor::take_action() { bool Editor::take_action() {
move(cur); move(cur);
@ -190,6 +193,12 @@ bool Editor::take_action() {
case 'w': case 'w':
save(); save();
break; break;
case 'u':
if(file.version() > 0) {
jump({file.undo(), 0});
print_file(file_offset);
}
break;
case 'i': case 'i':
mode = INSERT; mode = INSERT;
current_insert = file.get_line(file_offset + cur.r, false); current_insert = file.get_line(file_offset + cur.r, false);
@ -223,7 +232,7 @@ bool Editor::take_action() {
print_file(file_offset); print_file(file_offset);
break; break;
case 'p': case 'p':
selection.paste_selection(cur + file_offset); file.compress_versions(selection.paste_selection(cur + file_offset));
print_file(file_offset); print_file(file_offset);
break; break;
default: default:
@ -235,7 +244,7 @@ bool Editor::take_action() {
switch(ch) { switch(ch) {
case ESC: case ESC:
mode = NORMAL; mode = NORMAL;
file.set_line(cur.r, current_insert); file.update(cur.r, current_insert);
print_text(cur.r, substitute_tabs(current_insert)); print_text(cur.r, substitute_tabs(current_insert));
current_insert = ""; current_insert = "";
break; break;
@ -251,7 +260,7 @@ bool Editor::take_action() {
print_text(cur.r, substitute_tabs(current_insert)); print_text(cur.r, substitute_tabs(current_insert));
break; break;
case ENTER: case ENTER:
file.set_line(cur.r, current_insert); file.update(cur.r, current_insert);
file.split_line(cur + file_offset); file.split_line(cur + file_offset);
cur.r++; cur.c = 0; move(cur); cur.r++; cur.c = 0; move(cur);
current_insert = file.get_line(cur.r); current_insert = file.get_line(cur.r);
@ -288,7 +297,7 @@ bool Editor::take_action() {
mode = NORMAL; mode = NORMAL;
break; break;
case 'x': case 'x':
selection.remove_selection(cur + file_offset); file.compress_versions(selection.remove_selection(cur + file_offset)+1);
if(file_offset + cur.r >= file.size()) if(file_offset + cur.r >= file.size())
jump(cur + file_offset); jump(cur + file_offset);
print_file(file_offset); print_file(file_offset);

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <ncurses.h> #include <ncurses.h>
#include <vector> #include <vector>
#include <stack>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
@ -9,6 +10,7 @@ constexpr int RANDOM_SEED = 666;
using std::string; using std::string;
using std::vector; using std::vector;
using std::stack;
typedef unsigned long long count_type; typedef unsigned long long count_type;
// Position // Position
@ -29,43 +31,76 @@ struct line {
count_type priority, size; // Treap innards count_type priority, size; // Treap innards
string text; // Content string text; // Content
line *left, *right; // Sons line *left, *right; // Sons
line *next_in_version;
void set_left(line *_left) {
left = _left;
update_size();
}
void set_right(line *_right) {
right = _right;
update_size();
}
void update_size() { size = 1 + (left == nullptr ? 0 : left->size) + (right == nullptr ? 0 : right->size); }
line(count_type _p, string _t) : line(count_type _p, string _t) :
priority(_p), text(_t), size(1), left(nullptr), right(nullptr) {} priority(_p), text(_t), size(1), left(nullptr), right(nullptr), next_in_version(nullptr) {}
line(count_type _p, string _t, line *_left, line *_right, line *_next) :
priority(_p), text(_t), left(_left), right(_right), next_in_version(_next) {
update_size();
}
line(line *l) :
priority(l->priority), text(l->text), left(l->left), right(l->right), next_in_version(nullptr) {
update_size();
}
}; };
struct change { count_type row, count; }; struct change { count_type row, count; };
// Treap data structure // Treap data structure
class Treap { class Treap {
line *root; stack<change> changes;
stack<line*> root;
line* get_root() { return root.top(); };
two_lines split(line *l, count_type k); line* insert(line *l, count_type k, string s, bool new_version);
line* join(line *a, line *b); line* remove(line *l, count_type k);
line* join(two_lines two) { return join(two.first, two.second); } line* update(line *l, count_type k, string s, bool new_version);
line* pick_higher(line *l);
void delete_version(line *l);
count_type get_size(line *l); count_type get_size(line *l);
count_type get_priority(line *l);
line* find(line *l, count_type k); line* find(line *l, count_type k);
line* find(count_type k);
count_type bulk_find(vector<string> *vec, line *l, count_type k, count_type count); count_type bulk_find(vector<string> *vec, line *l, count_type k, count_type count);
public: public:
// General operations // General operations
Treap() { srand(120); root = nullptr; } Treap() { srand(RANDOM_SEED); root.push(nullptr); }
count_type size() { return get_size(root); } void construct(vector<string> &line);
count_type size() { return get_size(get_root()); }
void clear(); void clear();
// Line get operations // Versioning
string get_line(count_type r, bool substitute_tab = 1); count_type undo();
count_type version() { return root.size()-1; }
void compress_versions(count_type count);
// Line getters
string get_line(count_type r, bool substitute_tab = true);
vector<string> bulk_get_line(count_type r, count_type count); vector<string> bulk_get_line(count_type r, count_type count);
count_type get_tab_offset(position p) { return ::get_tab_offset(p.c, get_line(p.r, 0)); } count_type get_tab_offset(position p) { return ::get_tab_offset(p.c, get_line(p.r, 0)); }
// Line set operations // Line meta-operations
void split_line(position p); void insert(count_type k, string s, bool new_version = true);
void insert(count_type k, string s);
void remove(count_type k); void remove(count_type k);
void set_line(count_type k, string s) { find(k)->text = s; } void update(count_type k, string s, bool new_version = true);
// Substring operations // Other line operations
void split_line(position p, bool should_compress = true);
void merge_line(count_type k, bool should_compress = true);
void insert(position p, string t); void insert(position p, string t);
void remove(position p, count_type len = 1); void remove(position p, count_type len = 1);
}; };
@ -85,7 +120,8 @@ public:
count_type size() { return file->size(); } count_type size() { return file->size(); }
void copy_selection(position p); void copy_selection(position p);
void paste_selection(position p); count_type remove_selection(position p);
count_type paste_selection(position p);
}; };

View file

@ -48,10 +48,14 @@ int main(int argc, const char *argv[]) {
} }
// Load file // Load file
Treap file;
string line; string line;
vector<string> lines;
while (std::getline(infile, line)) while (std::getline(infile, line))
file.insert(file.size(), line); lines.push_back(line);
// Create a treap from file
Treap file;
file.construct(lines);
// Init // Init
initscr(); initscr();

View file

@ -24,13 +24,6 @@ void Selection::apply_on_selection(position p, F func, G func2) {
count_type size = ((start.r == end.r) ? end.c+1 : start_line.size()) - start.c; count_type size = ((start.r == end.r) ? end.c+1 : start_line.size()) - start.c;
func2(start.r, start.c, size, file, &content); func2(start.r, start.c, size, file, &content);
} }
// Removing selection
void Selection::remove_selection(position p) {
apply_on_selection(p,
[](count_type r, Treap *file, Treap *sel){ file->remove(r); },
[](count_type r, count_type start_c, count_type size, Treap *file, Treap *sel) { file->remove({r, start_c-file->get_tab_offset({r, start_c})}, size); }
);
}
// Copying selection // Copying selection
void Selection::copy_selection(position p) { void Selection::copy_selection(position p) {
apply_on_selection(p, apply_on_selection(p,
@ -38,20 +31,40 @@ void Selection::copy_selection(position p) {
[](count_type r, count_type start_c, count_type size, Treap *file, Treap *sel) { sel->insert(0, file->get_line(r, false).substr(start_c - file->get_tab_offset({r, start_c}), size)); } [](count_type r, count_type start_c, count_type size, Treap *file, Treap *sel) { sel->insert(0, file->get_line(r, false).substr(start_c - file->get_tab_offset({r, start_c}), size)); }
); );
} }
// Removing selection
count_type Selection::remove_selection(position p) {
apply_on_selection(p,
[](count_type r, Treap *file, Treap *sel){ file->remove(r); },
[](count_type r, count_type start_c, count_type size, Treap *file, Treap *sel) { file->remove({r, start_c-file->get_tab_offset({r, start_c})}, size); }
);
count_type min = p.r, max = pos.r;
if(min > max)
min = pos.r, max = p.r;
if(max > min) {
file->merge_line(min+1, false);
return max - min + 2;
}
else
return 1;
}
// Pasting selection // Pasting selection
void Selection::paste_selection(position p) { count_type Selection::paste_selection(position p) {
if(content.size() == 0) return; if(content.size() == 0) return 0;
// Insert last line inside of this // Insert last line inside of this
file->insert(p, content.get_line(content.size()-1, false)); file->insert(p, content.get_line(content.size()-1, false));
if(content.size() == 1) return; if(content.size() == 1) return 1;
// Insert first line in front and split // Insert first line in front and split
file->split_line(p); file->split_line(p, false);
file->insert(p, content.get_line(0, false)); file->insert(p, content.get_line(0, false));
if(content.size() == 2) return; if(content.size() == 2) return 4;
// Insert lines // Insert lines
for(count_type i = content.size()-2; i > 0; --i) for(count_type i = content.size()-2; i > 0; --i)
file->insert(p.r, content.get_line(i, false)); file->insert(p.r+1, content.get_line(i, false));
return content.size()+2;
} }

312
treap.cpp
View file

@ -2,99 +2,258 @@
// Get size uf a subtreap // Get size uf a subtreap
count_type Treap::get_size(line *l) { count_type Treap::get_size(line *l) {
if(l == nullptr) return 0; if(l == nullptr)
return 0;
return l->size; return l->size;
} }
count_type Treap::get_priority(line *l) {
// Split treap by k-th element if(l == nullptr)
two_lines Treap::split(line *l, count_type k) { return 0;
if(l == nullptr) return {nullptr, nullptr}; return l->priority;
if(get_size(l->left) >= k) {
// In the left subtree
auto two = split(l->left, k);
l->left = two.second;
l->size -= get_size(two.first);
return {two.first, l};
}
else {
// In the right subtree
auto two = split(l->right, k - (1+get_size(l->left)));
l->right = two.first;
l->size -= get_size(two.second);
return {l, two.second};
}
} }
// Join two treaps // Treap node rotations
line* Treap::join(line *a, line *b) { line *rotate_left(line *l) {
if(a == nullptr) return b; line *r = l->right, *mid = r->left;
if(b == nullptr) return a;
if(a->priority < b->priority) { // Switch
a->size += get_size(b); l->set_right(mid);
a->right = join(a->right, b); r->set_left(l);
return a;
return r;
}
line *rotate_right(line *r) {
line *l = r->left, *mid = l->right;
// Switch
r->set_left(mid);
l->set_right(r);
return l;
}
// Meta-operations
line* Treap::insert(line *l, count_type k, string s, bool new_version) {
if(l == nullptr)
return new line(rand(), s);
// Insert into left subtree
if(k <= get_size(l->left)) {
line *son = insert(l->left, k, s, new_version);
if(new_version)
l = new line(l->priority, l->text, son, l->right, son);
else
l->set_left(son);
if(get_priority(l->left) > get_priority(l))
l = rotate_right(l);
}
// Insert into right subtree
else {
line *son = insert(l->right, k - (get_size(l->left)+1), s, new_version);
if(new_version)
l = new line(l->priority, l->text, l->left, son, son);
else
l->set_right(son);
if(get_priority(l->right) > get_priority(l))
l = rotate_left(l);
}
return l;
}
line* Treap::remove(line *l, count_type k) {
line *lson = l->left, *rson = l->right;
// Remove from left subtree
if(k < get_size(lson)) {
line *son = remove(lson, k);
return new line(l->priority, l->text, son, rson, son);
}
// Remove this line
else if(k == get_size(l->left)) {
// Picking the son we can
if(lson == nullptr && rson == nullptr)
return nullptr;
else if(lson == nullptr)
return new line(l->priority, rson->text, rson->left, rson->right, nullptr);
else if(rson == nullptr)
return new line(l->priority, lson->text, lson->left, lson->right, nullptr);
// Picking the son with higher priority
else if(get_priority(lson) >= get_priority(rson)) {
line *son = new line(lson);
line l_copy = line(l->priority, l->text, son, l->right, nullptr);
rotate_right(&l_copy);
son->right = remove(&l_copy, get_size(l_copy.left));
son->next_in_version = son->right;
son->update_size();
return son;
} }
else { else {
b->size += get_size(a); line *son = new line(rson);
b->left = join(a, b->left); line l_copy = line(l->priority, l->text, l->left, son, nullptr);
return b;
rotate_left(&l_copy);
son->left = remove(&l_copy, get_size(l_copy.left));
son->next_in_version = son->left;
son->update_size();
return son;
}
}
// Remove from right subtree
else {
line *son = remove(rson, k - (get_size(lson)+1) );
return new line(l->priority, l->text, lson, son, son);
}
}
line* Treap::update(line *l, count_type k, string s, bool new_version) {
if(new_version) {
if(k < get_size(l->left)) {
line *son = update(l->left, k, s, new_version);
return new line(l->priority, l->text, son, l->right, son);
}
else if(k == get_size(l->left))
return new line(l->priority, s, l->left, l->right, nullptr);
else {
line *son = update(l->right, k - (1+get_size(l->left)), s, new_version);
return new line(l->priority, l->text, l->left, son, son);
}
}
else {
if(k < get_size(l->left))
update(l->left, k, s, new_version);
else if(k == get_size(l->left))
l->text = s;
else
update(l->right, k - (1+get_size(l->left)), s, new_version);
return nullptr;
} }
} }
// Find k-th line of file // Find k-th line of file
line* Treap::find(line *l, count_type k) { line* Treap::find(line *l, count_type k) {
if(l == nullptr) return nullptr; if(l == nullptr)
return nullptr;
if(k <= get_size(l->left)) if(k < get_size(l->left))
return find(l->left, k); return find(l->left, k);
else if(k == get_size(l->left)+1) else if(k == get_size(l->left))
return l; return l;
else else
return find(l->right, k - (1+get_size(l->left)) ); return find(l->right, k - (1+get_size(l->left)) );
} }
line* Treap::find(count_type k) {
if(k >= root->size) void Treap::construct(vector<string> &line) {
return nullptr; for(string text : line)
// Don't find index k, but k-th line -> +1 insert(size(), text, false);
return find(root, k+1); }
// Undo
void Treap::delete_version(line *l) {
if(l == nullptr)
return;
line *next = l->next_in_version;
delete l;
delete_version(next);
}
count_type Treap::undo() {
if(changes.size() == 0)
return 0;
// Get number of changes
change last = changes.top();
changes.pop();
// Delete all treap versions within the change
for(count_type i = 0; i < last.count; i++) {
line *last_root = root.top();
root.pop();
delete_version(last_root);
}
return last.row;
}
void Treap::compress_versions(count_type count) {
for(count_type i = 1; i < count; ++i)
changes.pop();
if(count > 0)
changes.top().count = count;
} }
// File access // File access
void Treap::clear() { void Treap::clear() {
while(size() > 0) { // Clear all versions
auto two = split(root, 1); while(root.size() > 1)
root = two.second; undo();
delete two.first; // Clear original file
} while(size() > 0)
remove(0);
} }
// Line insert // Line insert
void Treap::insert(count_type k, string s) { void Treap::insert(count_type k, string s, bool new_version) {
line *l = new line(rand(), s); if(k > size()) {
std::cerr << "Inserting out of file range\n";
if(root == nullptr) {
root = l;
return; return;
} }
auto two = split(root, k); line* new_root = insert(get_root(), k, s, new_version);
two.first = join(two.first, l);
root = join(two); if(new_version) {
root.push(new_root);
changes.push({k, 1});
}
else
root.top() = new_root;
} }
// Line removal // Line removal
void Treap::remove(count_type k) { void Treap::remove(count_type k) {
auto two = split(root, k+1); if(k >= size()) {
auto first_split = split(two.first, k); std::cerr << "Removing out of file range\n";
delete first_split.second; return;
two.first = first_split.first; }
root = join(two);
if(size() == 1)
update(0, "");
else {
root.push(remove(get_root(), k));
changes.push({k, 1});
}
}
// Line text update
void Treap::update(count_type k, string s, bool new_version) {
if(k >= size()) {
std::cerr << "Updating out of file range\n";
return;
}
if(s == find(get_root(), k)->text)
return;
line *new_root = update(get_root(), k, s, new_version);
if(new_version) {
root.push(new_root);
changes.push({k, 1});
}
} }
// Accessing the file // Accessing the file
string Treap::get_line(count_type r, bool substitute_tab) { string Treap::get_line(count_type r, bool substitute_tab) {
string line = find(r)->text; line *l = find(get_root(), r);
if(l == nullptr) {
std::cerr << r << " not found.\n";
return "";
}
string line = l->text;
if(substitute_tab) if(substitute_tab)
return substitute_tabs(line); return substitute_tabs(line);
else else
@ -129,20 +288,43 @@ count_type Treap::bulk_find(vector<string> *vec, line *l, count_type k, count_ty
// Access multiple adjacent vertices // Access multiple adjacent vertices
vector<string> Treap::bulk_get_line(count_type r, count_type count) { vector<string> Treap::bulk_get_line(count_type r, count_type count) {
vector<string> ret(0); vector<string> ret(0);
bulk_find(&ret, root, r+1, count); bulk_find(&ret, get_root(), r+1, count);
return ret; return ret;
} }
void Treap::insert(position p, string t) { void Treap::insert(position p, string t) {
find(p.r)->text.insert(p.c-get_tab_offset(p), t); string text = get_line(p.r, false);
text.insert(p.c-get_tab_offset(p), t);
if(text != get_line(p.r, false))
update(p.r, text);
} }
void Treap::remove(position p, count_type len) { void Treap::remove(position p, count_type len) {
find(p.r)->text.erase(p.c-get_tab_offset(p),len); string text = get_line(p.r, false);
text.erase(p.c-get_tab_offset(p),len);
if(text != get_line(p.r, false))
update(p.r, text);
} }
// Split line // Split line
void Treap::split_line(position p) { void Treap::split_line(position p, bool should_compress) {
string line = get_line(p.r); string line = get_line(p.r, false);
string newline = line.substr(p.c, line.size()); count_type place = p.c - get_tab_offset(p);
remove(p, line.size()-p.c); string newline = line.substr(place, line.size());
if(line.size()-place > 0)
remove(p, line.size()-place);
insert(p.r+1, newline); insert(p.r+1, newline);
if(should_compress)
compress_versions(2);
}
void Treap::merge_line(count_type k, bool should_compress) {
if(k == 0)
return;
string text = get_line(k, false);
remove(k);
update(k-1, get_line(k-1, false) + text);
if(should_compress)
compress_versions(2);
} }