Автор - Alex Mizrahi, RU.PROGRAMMING.LANGUAGES
#include
#include
#include
#include
#include
#include
#include
int parse_integer(const std::string& val) {
return atoi(val.c_str());
}
std::string toString(int i) {
char buf[32];
return itoa(i, buf, 10);
}
struct Table {
typedef std::string cell_addr_t;
typedef std::map cell_str_cont_t;
typedef std::map cell_num_cont_t;
typedef std::set cell_set_t;
Table() : w(0), h(0) {}
size_t w, h;
cell_str_cont_t exprs, values;
cell_num_cont_t num_values;
cell_set_t evaluated, evaluating;
cell_addr_t make_addr(size_t i, size_t j) {
return char('A' + j) + toString(i + 1);
}
void input() {
//this crap is unable to read empty cells.. so fuck it
std::cin >> h >> w;
for (size_t i = 0; i < h; ++i)
for (size_t j = 0; j < w; ++j) {
std::string val;
std::cin >> val;
if (!val.empty()) {
exprs[make_addr(i, j)] = val;
}
}
}
enum cell_kinds { cell_empty, cell_str, cell_num,
cell_expr, cell_error };
cell_kinds cell_kind(cell_addr_t addr) {
if (exprs[addr].empty()) return cell_empty;
if (exprs[addr][0] == '\'') return cell_str;
if (exprs[addr][0] == '=') return cell_expr;
//it should be a num cell, validate it:
const char* p = exprs[addr].c_str();
while (*p) {
if (!isdigit(*p)) return cell_error;
++p;
}
return cell_num;
}
void validate_addr(const cell_addr_t& addr) {
int j = addr[0] - 'A' + 1;
int i = addr[1] - '0';
if ((i >= 1) && (j >= 1) && (i <= h) && (j <= w)) return;
else throw std::runtime_error("wrong cell address");
}
int get_evald_num(cell_addr_t addr) {
std::string strval = get_evald_str(addr); //pull evaluation
cell_num_cont_t::iterator it = num_values.find(addr);
if (it != num_values.end()) {
return it->second;
} else {
if (!strval.empty() && strval[0] == '#') //it's an error
throw std::runtime_error(strval.substr(1));
throw std::runtime_error("not a number");
}
}
int read_term(const char*&p) {
if (isdigit(*p)) {
int val = atoi(p);
while (isdigit(*p)) ++p;
return val;
} else {
cell_addr_t addr(p, 2);
validate_addr(addr);
p += 2;
return get_evald_num(addr);
}
}
int eval_expr_int(const char* p) {
assert(p != 0 && *p != 0);
int res = read_term(p);
//read other terms (if any) and perform operation
do {
switch (*p) {
case 0: break;
case '+': res += read_term(++p);break;
case '*': res *= read_term(++p);break;
case '/': res /= read_term(++p);break;
case '-': res -= read_term(++p);break;
default: throw std::runtime_error("error in expression");
};
} while (*p);
return res;
}
std::string make_error(const std::string& msg) {
return "#" + msg;
}
std::string eval_expr(cell_addr_t addr) {
evaluating.insert(addr);
try {
int val = eval_expr_int(exprs[addr].c_str() + 1); //skip =
num_values[addr] = val;
evaluating.erase(addr);
return toString(val);
} catch (std::runtime_error& e) {
evaluating.erase(addr);
return make_error(e.what());
}
}
std::string get_evald_str(cell_addr_t addr) {
if (evaluated.count(addr)) { //if it's evaluated
return values[addr];
//TODO: check if it's present, do not create empty cells
}
if (evaluating.count(addr)) {
evaluating.erase(addr);
evaluated.insert(addr);//we've evaluated that this is cicrular reference
std::string val = make_error("circularref");
values[addr] = val;
return val;
}
std::string val;
switch (cell_kind(addr)) {
case cell_empty: break;
case cell_str: val = exprs[addr].substr(1);break; //skip first char
case cell_num:
val = exprs[addr];
num_values[addr] = parse_integer(val);
break;
case cell_expr: val = eval_expr(addr);break;
case cell_error: val = make_error("error in expression");
}
evaluated.insert(addr);
values[addr] = val;
return val;
}
void output() {
for (size_t i = 0; i < h; ++i) {
for (size_t j = 0; j < w; ++j) {
std::cout << get_evald_str(make_addr(i, j));
if (j != w - 1) std::cout << "\t";
}
std::cout << std::endl;
}
}
};
int main() {
Table t;
t.input();
t.output();
return 0;
}Все работает. Мои простые тесты проходит.
Комментарий автора:
AM> в некотором роде это data decoupling -- данные легко добавить, так что
AM> изначальное проектирование не имеет такой роли. но в месте с тем с таким
AM> подходом усложняется соблюдение когерентности данных, хотя это ещё вопрос
AM> -- если правильно закодировать функции-модификаторы данных получится не
AM> хуже, чем если бы данные хранились в объектах которые бы обеспечивали
AM> когерентность.
Я нахожу это достаточно разумным.