--- tags: hierarchy service --- # Hierarchy Service contributed by < [henrychen0622](https://github.com/henrychen0622) > ## Introduction >In our application we organize data in a hierarchy and we have a service that we use to manage it. In this exercise you'll write a simplified version of that service. It uses a similar API, but in the interest of time, avoids tricky problems around availability, scalability, concurrency, and persistance. ## Environment ```shell $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 36 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 6 On-line CPU(s) list: 0-5 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i5-9400 CPU @ 2.90GHz CPU family: 6 Model: 158 Thread(s) per core: 1 Core(s) per socket: 6 Socket(s): 1 Stepping: 10 CPU max MHz: 2904.0000 CPU min MHz: 0.0000 BogoMIPS: 5808.00 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb r dtscp lm pni pclmulqdq dtes64 est tm2 ssse3 fma cx16 xtpr pdcm pcid sse4_1 sse4_2 movbe popcnt xsave osxsave avx f16c rdrand hypervisor lahf _lm abm 3dnowprefetch fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt ibrs ibpb stibp ssbd Virtualization features: Hypervisor vendor: Windows Subsystem for Linux Virtualization type: container ``` ## Problem description >Your service will maintain a hierarchy of nodes in memory. The API supports adding and deleting nodes, moving them to a new position in the tree, and searching. Each node is identified by a name and an ID (both strings). The name of a node must be unique among its siblings (i.e., the children of the node's parent); the ID must be unique among all nodes in the tree. >The interface uses JSON-encoded messages. Your program will receive requests on standard input and write responses to standard output. >Note: if standard output is buffered by default in your language, make sure to either disable buffering or do a flush after you write each response. If standard output is being buffered, the test client may not see your response, which will cause it to wait forever. >To simplify parsing, each request will be contained on a single line. Your responses don't have to be on a single line. Your program will not receive a new request until the response has been received from the previous request. Your program will never receive invalid JSON as a test case. Your program should exit gracefully when it detects end of file on standard input. >Note: If you want to do printf-style debugging, make sure to write to standard error instead of standard output. >We've given example input and output for each request (see below). Your responses don't have to look exactly like the example responses, but they have to be equivalent JSON objects. >We've also provided a test client to help validate your implementation. Please test your implementation with the test client. Your implementation will be expected to pass all of the tests provided by the test client, along with some additional tests. See below for information on running the test client. >We don't expect you to write your own test suite unless you feel it helps you work. >Beyond the API, we leave all implementation details to you. Use the language of your choice and architect the service however you see fit. You can use whatever references you want, but please restrict yourself to your language's core libraries (unless you need to use an external library to support JSON encoding/decoding). ## API Methods >For all methods except Query, the response is a simple JSON object indicating success or failure: >Success response: ```json {"ok":true} ``` >Failure response: ```json {"ok":false} ``` ## API operations ### Add Node >Description: - Add a new node to the tree. >Params: - name {string}: Node name - id {string}: Node ID - parent_id {string}: ID of the parent node; if ommitted or empty string, add this node as the root node (assuming there isn't already a root node) >Validation: - Two sibling nodes cannot have the same name. - No two nodes in the tree can have the same ID. - There can only be one root node (i.e., a node without a parent). - Name and ID must be specified and not empty strings. - If specified, parent node must exist. > Example: Add the root node Request: ```json {"add_node":{"id":"1","name":"Root"}} ``` Response: ```json {"ok":true} ``` > Example: Add a child node Request: ```json {"add_node":{"id":"4","name":"Child42","parent_id":"1"}} ``` Response: ```json {"ok":true} ``` > Example: Add a child node to nonexistent parent. Request: ```json {"add_node":{"id":"4","name":"Child78","parent_id":"200"}} ``` Response: ```json {"ok":false} ``` ```cpp void hierarchy::add_node(string name, string id, string parent_id) { /* Name and ID must be specified and not empty strings. */ if (id == "" || name == "") { std::cout << fail << std::endl; return; } /* There can only be one root node */ if (parent_id == "") { if (root != nullptr) { std::cout << fail << std::endl; return; } else { root = new Node(id, name); std::cout << pass << std::endl; return; } } /* Inorder Traversal */ stack<Node *> s; Node *cur = root; Node *parent = nullptr; while (cur || !s.empty()) { if (cur) { s.push(cur); cur = cur->child; } else { Node *node = s.top(); s.pop(); /* unique id, siblings cannot have the same name */ if ((node->id == id) || (node->parent_id == parent_id && node->name == name)) { std::cout << fail << std::endl; return; } if (node->id == parent_id) parent = node; cur = node->next; } } /* parent node must exist */ if (nullptr == parent) { std::cout << fail << std::endl; return; } /* check if it is the first child */ if (nullptr == parent->child) { parent->child = new Node(id, name, parent_id); std::cout << pass << std::endl; return; } else { cur = parent->child; if (cur->name > name) { parent->child = new Node(id, name, parent_id); parent->child->next = cur; std::cout << pass << std::endl; return; } while (cur->next && cur->next->name <= name) { cur = cur->next; } Node *next = cur->next; cur->next = new Node(id, name, parent_id); cur->next->next = next; std::cout << pass << std::endl; return; } cout << "it should never be printed !" << endl; return; } ``` ### Delete Node > Description: - Delete a node from the tree. > Params: - id {string}: ID of node to delete > Validation: - ID must be specified and not an empty string. - Node must exist. - Node must not have children. > Example: Add root node and then delete it. Request: ```json {"add_node":{"id":"1","name":"Root"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"delete_node":{"id":"1"}} ``` Response: ```json {"ok":true} ``` > Example: Delete nonexistent node Request: ```json {"delete_node":{"id":"1"}} ``` Response: ```json {"ok":false} ``` ```cpp void hierarchy::delete_node(string id) { /* ID must be specified and not empty strings. */ if (id == "") { std::cout << fail << std::endl; return; } /* Inorder Traversal */ stack<Node *> s; Node *cur = root; Node *parent = nullptr; while (cur || !s.empty()) { if (cur) { s.push(cur); cur = cur->child; } else { Node *node = s.top(); s.pop(); if (node->next && node->next->id == id) { /* Node must not have children. */ if (nullptr != node->next->child) { std::cout << fail << std::endl; return; } Node *next = node->next; node->next = node->next->next; delete next; std::cout << pass << std::endl; return; } if (node->id == id) { /* Node must not have children. */ if (nullptr != node->child) { std::cout << fail << std::endl; return; } parent = s.top(); parent->child = node->next; node->next = nullptr; delete node; std::cout << pass << std::endl; return; } cur = node->next; } } /* Node must exist. */ std::cout << fail << std::endl; } ``` ### Move Node > Description: - Move a node to a new parent in the tree > Params: - id {string}: ID of node to move - new_parent_id {string}: ID of the new parent node > Validation: - ID and new parent ID must be specified and not empty strings. - Both nodes must exist. - The name of the node to be moved must not be the same as those of any of the new parent's other children. - Move must not create a cycle in the tree. > Example: Add root and two children, then move one child under the other one. Request: ```json {"add_node":{"id":"1","name":"Root"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"1","id":"2","name":"A"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"1","id":"3","name":"B"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"move_node":{"id":"2","new_parent_id":"3"}} ``` Response: ```json {"ok":true} ``` > Example: Add root, child, child of child, then try to move first child under second (which would create a cycle). Request: ```json {"add_node":{"id":"1","name":"Root"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"1","id":"2","name":"A"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"2","id":"3","name":"B"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"move_node":{"id":"2","new_parent_id":"3"}} ``` Response: ```json {"ok":false} ``` ```cpp void hierarchy::move_node(string id, string new_parent_id) { /* ID and new parent ID must be specified and not empty strings. */ if (id == "" || new_parent_id == "" || id == root->id || id == new_parent_id) { std::cout << fail << std::endl; return; } stack<Node *> s; stack<Node *> ss; s.push(root); Node *parent_tmp = root; Node *parent = nullptr; Node *prev = nullptr; Node *child = nullptr; Node *new_parent = nullptr; int depth = 0; find_root_id_node(root, &new_parent, new_parent_id, depth); if (nullptr == new_parent) { std::cout << fail << std::endl; return; } /* Preorder Traversal */ while (s.size() > 0) { Node *node = s.top(); s.pop(); /* Move must not create a cycle in the tree. */ if (!child && node->next && node->next->id == id) { prev = node; child = node->next; if (child->child) { ss.push(child->child); while (ss.size() > 0) { Node *node_tmp = ss.top(); ss.pop(); if (node_tmp->id == new_parent_id) { std::cout << fail << std::endl; return; } if (node_tmp->child) ss.push(node_tmp->child); if (node_tmp->next) ss.push(node_tmp->next); } } break; } /* Move must not create a cycle in the tree. */ if (!child && node->id == id) { parent = parent_tmp; child = node; if (child->child) { ss.push(child->child); while (ss.size() > 0) { Node *node_tmp = ss.top(); ss.pop(); if (node_tmp->id == new_parent_id) { std::cout << fail << std::endl; return; } if (node_tmp->child) ss.push(node_tmp->child); if (node_tmp->next) ss.push(node_tmp->next); } } break; } if ((node->child) && (!child) || (child && prev)) { parent_tmp = node; s.push(node->child); } if (node->next) { if (node->next == child) { if (node->next->next) s.push(node->next->next); } else { s.push(node->next); } } } /* Both nodes must exist. */ if (child == nullptr) { std::cout << fail << std::endl; return; } /* check if it is the first child */ if (nullptr == new_parent->child) { if (prev) { prev->next = prev->next->next; child->next = nullptr; } else if (parent) { parent->child = child->next; child->next = nullptr; } else { cout << "somthing wrong, should not be here" << endl; return; } new_parent->child = child; std::cout << pass << std::endl; return; } else { Node *cur = new_parent->child; /* check same name */ while (cur) { if (cur->name == child->name) { std::cout << fail << std::endl; return; } cur = cur->next; } /* move from parent */ if (prev) { prev->next = prev->next->next; child->next = nullptr; } else if (parent) { parent->child = child->next; child->next = nullptr; } else { cout << "somthing wrong, should not be here" << endl; return; } /* move to new parent*/ cur = new_parent->child; if (cur->name > child->name) { new_parent->child = child; child->next = cur; child->parent_id = new_parent->id; std::cout << pass << std::endl; return; } while (cur->next && cur->next->name <= child->name) { cur = cur->next; } Node *next = cur->next; cur->next = child; child->next = next; child->parent_id = new_parent->id; std::cout << pass << std::endl; return; } } ``` ### Query > Description: - Return a list of nodes matching certain criteria. > Params: - min_depth {integer}: Minimum distance from root of query. Default: none. - max_depth {integer}: Maximum distance from root of query. Default: none. - names {list of strings}: If specified, only return nodes whose names are in the list. - ids {list of ids}: If specified, only return nodes whose IDs are in the list. - root_ids {list of ids}: Search subtrees rooted at specified nodes. If not specified, search from the root. If any ID in the list doesn't exist in the tree, ignore it. > Validation: > All parameterss are optional. If no parameters are specified, return all nodes. If no nodes match, return an empty list of nodes. There are no failure cases. Nodes must be returned in the order in which they would be found in a pre-order depth first traversal with sibling nodes being processed in order by name. Your query function should consider all nodes in that order and include any nodes in the response that match the specified parameters. Each node returned must match all specified query parameters, as described above. If a list of root IDs is specified, perform separate queries as described above for the subtrees rooted at each specified root node. The returned list of nodes should be the concatenation of the results of each individual subtree query in the order given by the list of root IDs. Ignore any specified root IDs that don't exist in the tree. For minimum and maximum depth, depth refers to the number of levels a given node is below the root of the query (i.e., the number of edges that must be traversed to get from the root of the query to the given node). For example, the root of a query is at depth 0, its children are at depth 1, their children are at depth 2, etc. If minimum depth is specified, only nodes whose depths are greater than or equal to the minimum depth should be considered. Similarly, if maximum depth is specified, only nodes whose depths are less than or equal to the maximum depth should be considered. If both minimum and maximum depths are specified, only nodes whose depths fall within that range should be considered. > Example: Add nodes and query with max_depth 1. Request: ```json {"add_node":{"id":"1","name":"Root"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"1","id":"2","name":"A"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"2","id":"3","name":"B"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"query":{"max_depth":1}} ``` Response (pretty-printed for ease of reading): ```json { "nodes": [ { "id": "1", "name": "Root", "parent_id": "" }, { "name": "A", "id": "2", "parent_id": "1" } ] } ``` > Example: Add nodes and query for nodes named "B". Request: ```json {"add_node":{"id":"1","name":"Root"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"1","id":"2","name":"A"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"add_node":{"parent_id":"2","id":"3","name":"B"}} ``` Response: ```json {"ok":true} ``` Request: ```json {"query":{"names":["B"]}} ``` Response (pretty-printed for ease of reading): ```json { "nodes": [ { "id": "3", "parent_id": "2", "name": "B" } ] } ``` ```cpp void hierarchy::query(int min_depth, int max_depth, vector<string>& names, vector<string>& ids, vector<string>& root_ids) { json j_arr; json j; json j_tmp; if (!root || (max_depth < min_depth)) { j["nodes"] = j_arr; std::cout << j.dump(4) << std::endl; return; } m_max_depth = max_depth; m_min_depth = min_depth; for (int i = 0; i < names.size(); i++) m_names_set.insert(names[i]); for (int i = 0; i < ids.size(); i++) m_ids_set.insert(ids[i]); for (int i = 0; i < root_ids.size(); i++) { m_root_ids_set.insert(root_ids[i]); } std::set<string>::iterator itr; if (!m_root_ids_set.empty()) { // code here for (itr = m_root_ids_set.begin(); itr != m_root_ids_set.end(); itr++) { int depth = 0; Node *node_be_found = nullptr; find_root_id_node(root, &node_be_found, *itr, depth); if (node_be_found != nullptr) { preOrder(node_be_found->child, depth + 1); } } } else { preOrder(root, 0); } j["nodes"] = m_j_arr; std::cout << j.dump(4) << std::endl; out: m_max_depth = INT_MAX; m_min_depth = 0; m_names_set.clear(); m_ids_set.clear(); m_root_ids_set.clear(); m_j_arr.clear(); } ``` ### API parser(json) > Use the third party lib to implement json parser <[nlohmann](https://github.com/nlohmann/json)> ```cpp void jsonDecodeProcess(hierarchy &h, json j) { h.m_mutex.lock(); json::iterator it = j.begin(); string input_fun = it.key(); string id; string name; string parent_id; string new_parent_id; int min_depth = 0; int max_depth = INT_MAX; vector<string> names; vector<string> ids; vector<string> root_ids; if (input_fun == "add_node") { if (nullptr != j[input_fun]["id"]) id = j[input_fun]["id"]; if (nullptr != j[input_fun]["name"]) name = j[input_fun]["name"]; if (nullptr == j[input_fun]["parent_id"]) { return h.add_node(name, id, ""); } else { parent_id = j[input_fun]["parent_id"]; h.add_node(name, id, parent_id); } } else if (input_fun == "delete_node") { if (nullptr != j[input_fun]["id"]) id = j[input_fun]["id"]; h.delete_node(id); } else if (input_fun == "move_node") { if (nullptr != j[input_fun]["id"]) id = j[input_fun]["id"]; if (nullptr != j[input_fun]["new_parent_id"]) new_parent_id = j[input_fun]["new_parent_id"]; h.move_node(id, new_parent_id); } else if (input_fun == "query") { if (j[input_fun]["min_depth"] != nullptr) min_depth = j[input_fun]["min_depth"]; if (j[input_fun]["max_depth"] != nullptr) max_depth = j[input_fun]["max_depth"]; if (j[input_fun]["names"] != nullptr) j[input_fun].at("names").get_to(names); if (j[input_fun]["ids"] != nullptr) j[input_fun].at("ids").get_to(ids); if (j[input_fun]["root_ids"] != nullptr) j[input_fun].at("root_ids").get_to(root_ids); h.query(min_depth, max_depth, names, ids, root_ids); } else { std::cout << h.fail << std::endl; } h.m_mutex.unlock(); } ``` ## Compile and excute ```shell make clean make ../test_client_darwin ./debug/bin/hierarchy ``` ## References [Combination of Id-ParentId and HierarchyId Approaches to Hierarchical Data](https://www.codeproject.com/Articles/1192607/Combination-of-Id-ParentId-and-HierarchyId) [Creating a hierarchy from parent child relationship](https://stackoverflow.com/questions/67740271/creating-a-hierarchy-from-parent-child-relationship)