Spreadsheet
Last updated
Last updated
#include <string>
#include <map>
#include <iostream>
#include <queue>
#include <variant>
#include <unordered_set>
using namespace std;
// Talking about trade offs of either read-heavy or write-heavy?
// 1. Update Parent/
// 2. parent can get to child using different path?
class SpreadSheetBFS {
public:
map<string, variant<int, pair<string, string>>> m;
SpreadSheetBFS() {}
// Time: O(n)
int getCell(string key) {
if (!m.count(key)) {
throw runtime_error("Key not found");
}
return bfs(key);
}
// Time: O(1)
void setCell(string key, variant<int, pair<string, string>> val) {
m[key] = val;
}
int bfs(string key) {
queue<string> q;
q.push(key);
int res = 0;
while (!q.empty()) {
string curr = q.front();
q.pop();
if (holds_alternative<int>(m[curr])) {
res += std::get<int>(m[curr]);
}
else {
auto& children = get<pair<string, string>>(m[curr]);
if (!children.first.empty()) {
q.push(children.first);
}
if (!children.second.empty()) {
q.push(children.second);
}
}
}
return res;
}
};
// Intuition:
// if A dependes on B and C, means B and C's parent is A.
// If we update value on B or C, we also need to update value on A.
// So instead of traversing from A every time we are trying to get A value.
//
// We can traverse from children to parents based on value update on set function.
class SpreadSheetBFSWithCache {
public:
map<string, variant<int, pair<string, string>>> m;
// Parent relationship
unordered_map<string, unordered_set<string>> parents;
// Values
unordered_map<string, int> values;
bool hasCycleUtil(string key, unordered_map<string, int>& visited) {
for (auto p : parents[key]) {
if (visited.count(p)) {
if (visited[p] == 1) continue;
else if (visited[p] == 2) return true;
} else {
visited[p] = 1;
if (hasCycleUtil(p, visited)) {
return true;
}
}
}
visited[key] = 2;
return false;
}
bool hasCyclic(string key) {
unordered_map<string, int> visited;
return hasCycleUtil(key, visited);
}
// Time: O(1)
int getCell(string key) {
if (!values.count(key)) {
throw runtime_error("Key not found");
}
return values[key];
}
// Case 1: key doesn't exist: update parent relationship and val;
// Case 2: key exist:
// a. val -> children: update new value based on children, update parent relationship.
// b. children->val
// c. val->val
// d. children -> children
// Time: O(n)
bool setCell(string key, variant<int, pair<string, string>> val) {
// Connect parent and check cycle first.
pair<int, vector<string>> value = parse(val);
for (auto i : value.second) {
parents[i].insert(key);
}
if (hasCyclic(key)) {
for (auto i : value.second) {
parents[i].erase(key);
}
return false;
}
if (!m.count(key)) {
m[key] = val;
values[key] = value.first;
for (auto i: value.second) {
parents[i].insert(key);
}
return true;
}
variant<int, pair<string, string>> prevVal = m[key];
pair<int, vector<string>> prevValue = parse(prevVal);
m[key] = val;
// Invalidate previous parent relationship or keep track of value diff.
for (auto i: prevValue.second) {
parents[i].erase(key);
}
// Update parents relationship
for (auto i: value.second) {
parents[i].insert(key);
}
// Update downstream parent values using BFS.
int valueDiff = value.first - prevValue.first;
updateParentValues(key, valueDiff);
return true;
}
pair<int, vector<string>> parse(variant<int, pair<string, string>> val) {
if (holds_alternative<int>(val)) {
return {get<int>(val), {}};
} else {
auto& children = get<pair<string, string>>(val);
int value = values[children.first] + values[children.second];
return {value, {children.first, children.second}};
}
}
void updateParentValues(string key, int diff) {
queue<string> q;
q.push(key);
while (!q.empty()) {
string curr = q.front();
q.pop();
values[curr] += diff;
for (auto p : parents[curr]) {
q.push(p);
}
}
}
};
int main() {
// SpreadSheetBFS ss;
// ss.setCell("A", 6);
// ss.setCell("B", 7);
// ss.setCell("C", make_pair("A", "B"));
// cout << "A: " << ss.getCell("A") << endl; // 6
// cout << "B: " << ss.getCell("B") << endl; // 7
// cout << "C: " << ss.getCell("C") << endl; // 13
// ss.setCell("A", 13);
// cout << "C: " << ss.getCell("C") << endl; // 20
// // C -> B, G
// ss.setCell("C", make_pair("B", "G"));
// ss.setCell("B", make_pair("G", "D"));
// ss.setCell("G", make_pair("D", "F"));
// ss.setCell("D", 1);
// ss.setCell("F", 2);
// cout << "B: " << ss.getCell("B") << endl; // 4
// cout << "C: " << ss.getCell("C") << endl; // 7
// cout << "D: " << ss.getCell("D") << endl; // 1
// cout << "G: " << ss.getCell("G") << endl; // 3
// cout << "F: " << ss.getCell("F") << endl; // 2
SpreadSheetBFSWithCache ss;
ss.setCell("A", 6);
ss.setCell("B", 7);
ss.setCell("C", make_pair("A", "B"));
cout << "A: " << ss.getCell("A") << endl; // 6
cout << "B: " << ss.getCell("B") << endl; // 7
cout << "C: " << ss.getCell("C") << endl; // 13
ss.setCell("A", 13);
cout << "C: " << ss.getCell("C") << endl; // 20
// C -> B, G
ss.setCell("D", 1);
ss.setCell("F", 2);
ss.setCell("G", make_pair("D", "F"));
ss.setCell("B", make_pair("G", "D"));
ss.setCell("C", make_pair("B", "G"));
cout << "B: " << ss.getCell("B") << endl; // 4
cout << "C: " << ss.getCell("C") << endl; // 7
cout << "D: " << ss.getCell("D") << endl; // 1
cout << "G: " << ss.getCell("G") << endl; // 3
cout << "F: " << ss.getCell("F") << endl; // 2
// Cycle detection
cout << ss.setCell("D", make_pair("C", "A")) << endl; // 0
cout << "B: " << ss.getCell("B") << endl; // 4
cout << "C: " << ss.getCell("C") << endl; // 7
cout << "D: " << ss.getCell("D") << endl; // 1
cout << "G: " << ss.getCell("G") << endl; // 3
cout << "F: " << ss.getCell("F") << endl; // 2
// Cell A = Cell(6);
// Cell B = Cell(7);
// Cell C = Cell(13, "A", "B");
// SpreadSheet sheet;
// sheet.set("A", A);
// sheet.set("B", B);
// sheet.set("C", C);
// int a = sheet.get("A");
// cout << a << endl;
// int b = sheet.get("B");
// cout << b << endl;
// int c = sheet.get("C");
// cout << c << endl;
// A.val = 13;
// sheet.set("A", A);
// c = sheet.get("C");
// cout << c << endl;
return 0;
}
class Cell {
public:
int val;
string child1;
string child2;
Cell() : val(0), child1(""), child2("") {}
Cell(int val) : val(val), child1(""), child2("") {}
Cell(int val, string child1, string child2) : val(val), child1(child1), child2(child2) {}
};
// A -> [B, C], C -> [D, E]
// Without Cache
class SpreadSheet {
public:
unordered_map<string, Cell> m;
SpreadSheet() {}
int get(string key) {
if (!m.count(key)) {
throw;
}
return dfs(key);
}
int dfs(string key) {
Cell cell = m[key];
if (cell.child1.empty() && cell.child2.empty()) {
return cell.val;
}
int v1 = get(cell.child1);
int v2 = get(cell.child2);
return v1 + v2;
}
void set(string key, Cell cell) {
m[key] = cell;
}
};```