From a2cd1123bd69ecb48b47061041b5481890fc0bd6 Mon Sep 17 00:00:00 2001 From: xiezheyuan <102935106+xiezheyuan@users.noreply.github.com> Date: Sun, 25 Feb 2024 19:38:13 +0800 Subject: [PATCH] Create carefree.hpp --- carefree.hpp | 887 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 887 insertions(+) create mode 100644 carefree.hpp diff --git a/carefree.hpp b/carefree.hpp new file mode 100644 index 0000000..410473c --- /dev/null +++ b/carefree.hpp @@ -0,0 +1,887 @@ +#pragma once + +#if __cplusplus < 201402L +#error "carefree library need C++ 14 or higher version" +#endif +#ifndef __GNUC__ +#error "carefree library need GCC compiler or similar compiler." +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::string; + +// logging and error' + +void critical(const char* message, bool exit = true){ + std::fprintf(stderr, "CRITICAL: %s\n", message); + if(exit) std::exit(1); +} + +void ensure(bool condition, const char* message){ + if(!condition) critical(message); +} + +void warning(const char* message){ + std::fprintf(stderr, "WARNING: %s\n", message); +} + +// type convert + +template +string __fts(T val, unsigned precision = 10){ + static char buffer[255], buffer2[2047]; + if(std::is_same::value) sprintf(buffer, "%%.%df", precision); + else if(std::is_same::value) sprintf(buffer, "%%.%dlf", precision); + else if(std::is_same::value) sprintf(buffer, "%%.%dlf", precision); + else critical("fts only supports float, double and long double"); + sprintf(buffer2, buffer, val); + string str(buffer2); + return str; +} + +string fts(float val, unsigned precision = 10){ + return __fts(val, precision); +} + +string fts(double val, unsigned precision = 10){ + return __fts(val, precision); +} + +string fts(long double val, unsigned precision = 10){ + return __fts(val, precision); +} + +template +std::vector fts(std::vector val, unsigned precision = 10){ + std::vector ans; + for(auto it = val.begin(); it != val.end(); ++it){ + ans.push_back(fts(*it, precision)); + } + return ans; +} + +template +std::vector ltv(std::initializer_list val){ + return std::vector(val); +} + +// random tricks + +std::mt19937_64 public_random_engine; + +template +T randint(T l, T r){ + return std::uniform_int_distribution(l, r)(public_random_engine); +} + +template +T uniform(T l, T r){ + return std::uniform_real_distribution(l, r)(public_random_engine); +} + +double random(){ + return uniform(0.0, 1.0); +} + +template +typename T::value_type choice(T val){ + return val[randint(0ull, val.size()-1)]; +} + +template +void shuffle(T val){ + std::shuffle(val.begin(), val.end(), public_random_engine); +} + +// sequence & string generator + +template +std::vector sequence(int n, T function){ + std::vector data; + for(int i=1;i<=n;i++){ + data.push_back(function(i)); + } + return data; +} + +template +std::vector sequence(int n, T l, T r){ + static_assert(std::is_integral::value || std::is_floating_point::value, "T must be integral or floating point"); + return sequence(n, [&](int i){ + if(std::is_integral::value) return (T)randint(l, r); + else return (T)uniform(l, r); + }); +} + +template +std::vector sequence(int lengthL, int lengthR, T l, T r){ + static_assert(std::is_integral::value || std::is_floating_point::value, "T must be integral or floating point"); + return sequence(randint(lengthL, lengthR), [&](int i){ + if(std::is_integral::value) return (T)randint(l, r); + else return (T)uniform(l, r); + }); +} + +template +std::vector real_cutting(int length, T total){ + std::vector data; + for(int i=length;i>=2;i--){ + int tmp = uniform(0.0, total / i * 2); + data.push_back(tmp);total = total - tmp; + } + data.push_back(total); + shuffle(data); + return data; +} + +template +std::vector int_cutting(int length, T total){ + std::vector data = real_cutting(length, (double)total); + std::vector data2; + for(int i=0;i +double timer(T function){ + auto start = std::chrono::system_clock::now(); + function(); + auto end = std::chrono::system_clock::now(); + return (double)std::chrono::duration_cast(end - start).count(); +} + +template +void gen_data(int subtask_count, int task_per_subtask, T function){ + int total_tasks = subtask_count * task_per_subtask; + int current_task = 0; + double average = 0; + int barWidth = 100; + std::cout << "["; + for (int k = 0; k < barWidth; ++k) { + std::cout<<" "; + } + std::cout << "] " << 0 << "% "; + std::cout<<"#-# N/A it / s\r"; + for(int i=1;i<=subtask_count;i++){ + for(int j=1;j<=task_per_subtask;j++){ + double time_ = timer([&](){ + function(i, j); + }); + average += time_; + current_task++; + float progress = static_cast(current_task) / total_tasks; + int pos = static_cast(barWidth * progress); + std::cout << "["; + for (int k = 0; k < barWidth; ++k) { + if (k < pos) std::cout << "="; + else if (k == pos) std::cout << ">"; + else std::cout << " "; + } + std::cout << "] " << int(progress * 100.0) << "% "; + std::cout< +void gen_data(int task_count, T function){ + int total_tasks = task_count; + int current_task = 0; + double average = 0; + int barWidth = 100; + std::cout << "["; + for (int k = 0; k < barWidth; ++k) { + std::cout<<" "; + } + std::cout << "] " << 0 << "% "; + std::cout<<"# N/A it / s\r"; + for(int i=1;i<=task_count;i++){ + double time_ = timer([&](){ + function(i); + }); + average += time_; + current_task++; + float progress = static_cast(current_task) / total_tasks; + int pos = static_cast(barWidth * progress); + std::cout << "["; + for (int k = 0; k < barWidth; ++k) { + if (k < pos) std::cout << "="; + else if (k == pos) std::cout << ">"; + else std::cout << " "; + } + std::cout << "] " << int(progress * 100.0) << "% "; + std::cout< > +class BalancedTree{ +private: + __gnu_pbds::tree tree; +public: + void insert(T x){tree.insert(x);} + bool erase(T x){return tree.erase(x);} + int rnk(T x){return tree.order_of_key(x)+1;} + T kth(int x){return *tree.find_by_order(x-1);} + int size(){return tree.size();} + bool empty(){return tree.empty();} +}; + +// graph structure + +using _Weight = long long; + +class graph{ +public: + struct edge{ + int from, to; + _Weight weight; + edge(int from, int to, _Weight weight = 0):from(from), to(to), weight(weight){} + }; + + struct weighted_output{ + string operator()(edge edg){ + return std::to_string(edg.from) + " " + std::to_string(edg.to) + " " + std::to_string(edg.weight); + } + }; + + struct unweighted_output{ + string operator()(edge edg){ + return std::to_string(edg.from) + " " + std::to_string(edg.to); + } + }; + +protected: + struct chain_node{ + int nxt, to; + _Weight w; + chain_node(){} + chain_node(int nxt, int to, _Weight w):nxt(nxt), to(to), w(w){} + }; + std::vector chain; + std::vector head; + std::vector > edge_map; + std::vector edge_vct; +public: + int N; + bool directed; + bool enable_edge_map; + + graph(int N, bool directed = false, bool enable_edge_map = true){ + this->N = N; + this->directed = directed; + this->enable_edge_map = enable_edge_map; + head = std::vector(N + 1, -1); + edge_map = std::vector >(N + 1); + } + + void add(edge edg, bool __add_vector = true){ + if(edg.from > N || edg.to > N) critical("edge out of range"); + if(edg.from < 0 || edg.to < 0) critical("edge out of range"); + chain.push_back(chain_node(head[edg.from], edg.to, edg.weight)); + head[edg.from] = chain.size() - 1; + if(!directed && __add_vector) add(edge(edg.to, edg.from, edg.weight), false); + if(__add_vector) edge_vct.push_back(edg); + if(enable_edge_map) edge_map[edg.from][edg.to] = true; + } + + void add(int from, int to, _Weight weight = 0){ + add(edge(from, to, weight)); + } + + bool has_edge(edge edg){ + if(!enable_edge_map) critical("edge map is not enabled"); + return edge_map[edg.from][edg.to]; + } + + bool has_edge(int from, int to){ + return has_edge(edge(from, to)); + } + + std::vector get_edges(){ + return edge_vct; + } + + std::vector get_edges(int from){ + std::vector edges; + for(int i = head[from]; ~i; i = chain[i].nxt) edges.push_back(edge(from, chain[i].to, chain[i].w)); + return edges; + } + + template + string to_string(bool shuffle = false){ + std::vector edges = get_edges(); + if(shuffle) std::shuffle(edges.begin(), edges.end(), public_random_engine); + string str; + for(auto it = edges.begin(); it != edges.end(); ++it){ + str += T()(*it); + if((it + 1) != edges.end()) str += '\n'; + } + return str; + } +}; + +using edge = graph::edge; +using weighted_output = graph::weighted_output; +using unweighted_output = graph::unweighted_output; + + +// graph tools + +bool is_tree(graph g){ + std::vector fa(g.N + 1); + for(int i=1;i<=g.N;i++) fa[i] = i; + auto edges = g.get_edges(); + if(edges.size() != g.N - 1) return false; + std::function find = [&](int x){ + return fa[x] == x ? x : fa[x] = find(fa[x]); + }; + for(auto i : edges){ + int u = i.from, v = i.to; + if(find(u) == find(v)) return false; + fa[find(u)] = find(v); + } + return true; +} + +graph relabel(graph g){ + std::vector perm; + for(int i=1;i<=g.N;i++) perm.push_back(i); + std::shuffle(perm.begin(), perm.end(), public_random_engine); + graph new_(g.N, g.directed, g.enable_edge_map); + for(auto i : g.get_edges()){ + new_.add(perm[i.from - 1], perm[i.to - 1], i.weight); + } + return new_; +} + +graph prufer_decode(int n, std::vector prufer, _Weight weightL = 0, _Weight weightR = 0){ + std::vector deg(n + 1, 1); + prufer.insert(prufer.begin(), 0); + graph g(n, false); + for(int i=1;i<=(n - 2);i++) deg[prufer[i]]++; + int ptr = 1, leaf = 0; + while(deg[ptr] != 1) ptr++; + leaf = ptr; + for(int i=1;i<=(n - 2);i++){ + int v = prufer[i]; + g.add(v, leaf, randint(weightL, weightR)); + if(--deg[v] == 1 && v < ptr) leaf = v; + else{ + ptr++; + while(deg[ptr] != 1) ptr++; + leaf = ptr; + } + } + g.add(n, leaf, randint(weightL, weightR)); + return g; +} + +std::vector get_depth(graph g){ + std::queue q; + std::vector depth(g.N + 1); + q.push(1);depth[1] = 1; + while(!q.empty()){ + auto u = q.front(); + q.pop(); + for(auto i : g.get_edges(u)){ + if(!depth[i.to]){ + depth[i.to] = depth[u] + 1; + q.push(i.to); + } + } + } + return depth; +} + +graph introvert(graph tree){ + auto depth = get_depth(tree); + graph g(tree.N, true); + for(auto i : tree.get_edges()){ + int u = i.from, v = i.to; + auto w = i.weight; + if(depth[u] < depth[v]) std::swap(u, v); + g.add(u, v, w); + } + return g; +} + +graph externalize(graph tree){ + auto depth = get_depth(tree); + graph g(tree.N, true); + for(auto i : tree.get_edges()){ + int u = i.from, v = i.to; + auto w = i.weight; + if(depth[u] > depth[v]) std::swap(u, v); + g.add(u, v, w); + } + return g; +} + +// tree generator and constructor + +graph lowhigh(int n, double low, double high, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + for(int i=2;i<=n;i++){ + int fa = randint(std::max((int)((i - 1) * low), 1), std::min((int)((i - 1) * high), i - 1)); + g.add(fa, i, randint(weightL, weightR)); + } + return g; +} + +graph naive_tree(int n, _Weight weightL = 0, _Weight weightR = 0){ + return lowhigh(n, 0, 1, weightL, weightR); +} + +graph tail(int n, int k, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + for(int i=2;i<=n;i++){ + int fa = randint(std::max(i - k, 1), i - 1); + g.add(fa, i, randint(weightL, weightR)); + } + return g; +} + +graph chain(int n, _Weight weightL = 0, _Weight weightR = 0){ + return tail(n, 1, weightL, weightR); +} + +graph star(int n, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + for(int i=2;i<=n;i++){ + g.add(1, i, randint(weightL, weightR)); + } + return g; +} + +graph flower(int n, _Weight weightL = 0, _Weight weightR = 0){ + return star(n, weightL, weightR); +} + +graph max_degree(int n, int k, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + BalancedTree > tree; + tree.insert({1, 0}); + for(int i=2;i<=n;i++){ + auto fa = tree.kth(randint(1, tree.size())); + tree.erase(fa); + if(fa.second < k) tree.insert({fa.first, fa.second + 1}); + g.add(fa.first, i, randint(weightL, weightR)); + tree.insert({i, 0}); + } + return g; +} + +graph binary_tree(int n, _Weight weightL = 0, _Weight weightR = 0){ + return max_degree(n, 3, weightL, weightR); +} + +graph chain_star(int n, int k, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + for(int i=2;i<=k;i++) g.add(i - 1, i, randint(weightL, weightR)); + for(int i=k + 1;i<=n;i++) g.add(randint(1, k), i, randint(weightL, weightR)); + return g; +} + +graph silkworm(int n, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + for(int i=2;i<=(n >> 1);i++) g.add(i - 1, i, randint(weightL, weightR)); + for(int i=(n >> 1) + 1;i <= (n >> 1) << 1;i++) g.add(i - (n >> 1), i, randint(weightL, weightR)); + if(((n >> 1) << 1) != n) g.add(randint(1, n - 1), n, randint(weightL, weightR)); + return g; +} + +graph firecrackers(int n, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false);int tmp = n / 3; + for(int i=2;i<=tmp;i++) g.add(i - 1, i, randint(weightL, weightR)); + for(int i=tmp + 1;i <= tmp * 2;i++) g.add(i - tmp, i, randint(weightL, weightR)); + for(int i=(tmp << 1) + 1;i <= tmp * 3;i++) g.add(i - (tmp << 1), i, randint(weightL, weightR)); + for(int i=(tmp * 3 + 1);i<=n;i++) g.add(randint(1, i - 1), i, randint(weightL, weightR)); + return g; +} + +graph complete(int n, int k, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + std::queue q;q.push(1);int tot = 1; + while(!q.empty()){ + int u = q.front(); + q.pop();int bound = std::min(tot + k - 1, n); + for(int i=tot + 1;i<=bound;i++){ + g.add(u, (++tot), randint(weightL, weightR)); + q.push(i); + } + } + return g; +} + +graph complete_binary(int n, _Weight weightL = 0, _Weight weightR = 0){ + return complete(n, 3, weightL, weightR); +} + +graph random_tree(int n, _Weight weightL = 0, _Weight weightR = 0){ + graph g(n, false); + std::vector prufer; + for(int i=1;i<=n-2;i++) prufer.push_back(randint(1, n)); + return prufer_decode(n, prufer, weightL, weightR); +} + +// graph generator & constructor + +graph dag(int n, int m, bool repeat_edges = false, _Weight weightL = 0, _Weight weightR = 0){ + graph tree = random_tree(n, weightL, weightR); + auto depth = get_depth(tree); + graph ret = externalize(tree); + for(int i=n;i<=m;i++){ + while(true){ + int u = randint(1, n); + int v = randint(1, n); + if(u == v) continue; + if(depth[u] > depth[v]) std::swap(u, v); + if(!repeat_edges && ret.has_edge(u, v)) continue; + ret.add(u, v, randint(weightL, weightR)); + break; + } + } + return ret; +} + +graph connected_undirected_graph(int n, int m, bool repeat_edges = false, bool self_loop = false, _Weight weightL = 0, _Weight weightR = 0){ + graph tree = random_tree(n, weightL, weightR); + graph ret = tree; + for(int i=n;i<=m;i++){ + while(true){ + int u = randint(1, n); + int v = randint(1, n); + if(!self_loop && u == v) continue; + if(!repeat_edges && ret.has_edge(u, v)) continue; + ret.add(u, v, randint(weightL, weightR)); + break; + } + } + return ret; +} + +graph connected_directed_graph(int n, int m, bool repeat_edges = false, bool self_loop = false, _Weight weightL = 0, _Weight weightR = 0){ + graph tree = random_tree(n, weightL, weightR); + graph ret = externalize(tree); + for(int i=n;i<=m;i++){ + while(true){ + int u = randint(1, n); + int v = randint(1, n); + if(!self_loop && u == v) continue; + if(!repeat_edges && ret.has_edge(u, v)) continue; + ret.add(u, v, randint(weightL, weightR)); + break; + } + } + return ret; +} + +graph random_graph(int n, int m, bool directed = true, bool repeat_edges = false, bool self_loop = false, _Weight weightL = 0, _Weight weightR = 0){ + graph ret(n, directed); + for(int i=1;i<=m;i++){ + while(true){ + int u = randint(1, n); + int v = randint(1, n); + if(!self_loop && u == v) continue; + if(!repeat_edges && ret.has_edge(u, v)) continue; + ret.add(u, v, randint(weightL, weightR)); + break; + } + } + return ret; +} + +// Input & Output + +class testcase_io{ +protected: + class file_writer { + protected: + std::FILE *fp; + void _ein(){ + if(fp == nullptr) critical("file is not opened."); + } + public: + string _filename; + file_writer(){} + file_writer(const char* filename){ + _filename = filename; + if(std::strlen(filename)){ + fp = std::fopen(filename, "w"); + ensure(fp != NULL, "Could not open file"); + } + else fp = nullptr; + } + void close(){if(fp != nullptr) std::fclose(fp);} + ~file_writer(){close();} + void writeChar(char val){_ein();std::fputc(val, fp);} + template + void writeInteger(T val){ + _ein(); + if(val < 0){val = -val;writeChar('-');} + if(val == 0){writeChar('0');return;} + if(val >= 10) writeInteger(val / 10); + writeChar('0' + val % 10); + } + void writeString(const char* val){ + _ein(); + const char* ptr = val; + while(*ptr != '\0') writeChar(*(ptr++)); + } + void writeString(string val){_ein();writeString(val.c_str());} + void flush(){_ein();std::fflush(fp);} + }; + + file_writer* fin; + file_writer* fout; + bool locked; + + void _eil(){ensure(!locked, "Input/output locked");} + void lock(){locked = true;} + +public: + testcase_io(string input_file, string output_file = ""){ + fin = new file_writer(input_file.c_str()); + fout = new file_writer(output_file.c_str()); + locked = false; + } + + testcase_io(string file_prefix, unsigned data_id, string input_suffix = ".in", string output_suffix = ".out", bool disable_output = false){ + fin = new file_writer((file_prefix + std::to_string(data_id) + input_suffix).c_str()); + if(!disable_output) fout = new file_writer((file_prefix + std::to_string(data_id) + output_suffix).c_str()); + else fout = new file_writer(""); + locked = false; + } + + testcase_io(string file_prefix, unsigned subtask_id, unsigned task_id, string input_suffix = ".in", string output_suffix = ".out", bool disable_output = false){ + fin = new file_writer((file_prefix + std::to_string(subtask_id) + "-" + std::to_string(task_id) + input_suffix).c_str()); + if(!disable_output) fout = new file_writer((file_prefix + std::to_string(subtask_id) + "-" + std::to_string(task_id) + output_suffix).c_str()); + else fout = new file_writer(""); + locked = false; + } + + void input_write(char val){_eil();fin->writeChar(val);} + void output_write(char val){_eil();fout->writeChar(val);} + + void input_write(int val){_eil();fin->writeInteger(val);} + void output_write(int val){_eil();fout->writeInteger(val);} + + void output_write(long long val){_eil();fout->writeInteger(val);} + void input_write(long long val){_eil();fin->writeInteger(val);} + + void input_write(unsigned int val){_eil();fin->writeInteger(val);} + void output_write(unsigned int val){_eil();fout->writeInteger(val);} + + void input_write(unsigned long long val){_eil();fin->writeInteger(val);} + void output_write(unsigned long long val){_eil();fout->writeInteger(val);} + + void input_write(unsigned short val){_eil();fin->writeInteger(val);} + void output_write(unsigned short val){_eil();fout->writeInteger(val);} + + void input_write(short val){_eil();fin->writeInteger(val);} + void output_write(short val){_eil();fout->writeInteger(val);} + + void input_write(__int128 val){_eil();fin->writeInteger(val);} + void output_write(__int128 val){_eil();fout->writeInteger(val);} + + void input_write(unsigned __int128 val){_eil();fin->writeInteger(val);} + void output_write(unsigned __int128 val){_eil();fout->writeInteger(val);} + + void input_write(string val){_eil();fin->writeString(val);} + void output_write(string val){_eil();fout->writeString(val);} + + void input_write(const char* val){_eil();fin->writeString(val);} + void output_write(const char* val){_eil();fout->writeString(val);} + + void input_write(bool val){_eil();fin->writeString(val ? "true" : "false");} + void output_write(bool val){_eil();fout->writeString(val ? "true" : "false");} + + template + void input_write(std::vector val){ + _eil(); + for(auto it = val.begin(); it != val.end(); ++it){ + input_write(*it); + if((it + 1) != val.end()) input_write(' '); + } + } + template + void output_write(std::vector val){ + _eil(); + for(auto it = val.begin(); it != val.end(); ++it){ + output_write(*it); + if((it + 1) != val.end()) output_write(' '); + } + } + + void input_write(graph val){_eil();input_write(val.to_string());} + void output_write(graph val){_eil();output_write(val.to_string());} + + void input_write(edge val){_eil();input_write(weighted_output()(val));} + void output_write(edge val){_eil();output_write(weighted_output()(val));} + + template + void input_write(T val, Args ...args){ + _eil(); + input_write(val); + input_write(' '); + input_write(args...); + } + + template + void output_write(T val, Args ...args){ + _eil(); + output_write(val); + output_write(' '); + output_write(args...); + } + + void input_writeln(){input_write('\n');} + void output_writeln(){output_write('\n');} + + template + void input_writeln(T val){_eil();input_write(val);input_write('\n');} + + template + void output_writeln(T val){_eil();output_write(val);output_write('\n');} + + template + void input_writeln(T val, Args ...args){ + _eil(); + input_writeln(val); + input_write(' '); + input_writeln(args...); + } + + template + void output_writeln(T val, Args ...args){ + _eil(); + output_writeln(val); + output_write(' '); + output_writeln(args...); + } + + bool is_locked(){return locked;} + + void input_flush(){fin->flush();} + void output_flush(){fout->flush();} + + void output_gen(string program){ + _eil();fin->close();fout->close();lock(); + int stdin_ = dup(fileno(stdin)); + freopen(fin->_filename.c_str(), "r", stdin); + int stdout_ = dup(fileno(stdout)); + freopen(fout->_filename.c_str(), "w", stdout); + int returnid = std::system(program.c_str()); + ensure(returnid == 0, "Program exited with non-zero return code"); + dup2(stdin_, fileno(stdin)); + dup2(stdout_, fileno(stdout)); + } + + string input_name(){ + return fin->_filename; + } + + string output_name(){ + return fout->_filename; + } + + ~testcase_io(){delete fin;delete fout;} +}; + +class luogu_testcase_config_writer { +protected: + string content; +public: + luogu_testcase_config_writer(){ + content = ""; + } + + void add(string input_name, int time_limit, int memory_limit, int score = -1, int subtask_id = -1){ + const string space = " "; + string current = ""; + current += (input_name + ":\n"); + current += (space + "timeLimit: " + std::to_string(time_limit) + "\n"); + current += (space + "memoryLimit: " + std::to_string(memory_limit) + "\n"); + if(score != -1) current += (space + "score: " + std::to_string(score) + "\n"); + if(subtask_id != -1) current += (space + "subtaskId: " + std::to_string(subtask_id) + "\n"); + current += "\n"; + content += current; + } + + void save(string filename = "config.yml"){ + FILE* fobj = fopen(filename.c_str(), "w"); + ensure(fobj != nullptr, ("Failed to open file " + filename).c_str()); + fputs(content.c_str(), fobj); + ensure(fclose(fobj) == 0, ("Failed to close file " + filename).c_str()); + } + + string to_string(){ + return content; + } +}; +