This is a follow-up question for String Calculator in C++. Considering the suggestions mentioned in MrBean Bremen's answer and Martin York's answer. I am trying to modify the code and trying to support:
braces
()
Trigonometric functions (
sin
,cos
,tan
,cot
,sec
andcsc
)sqrt
,abs
andlog
functions
The experimental implementation
StringCalculator
classclass StringCalculator { public: StringCalculator(const std::string& input) { input_string = input; } long double get_result() { auto input_without_space = remove_spaces(input_string); auto braces_levels = get_braces_levels(input_without_space); auto braces_level_max = *max_element(braces_levels.begin(), braces_levels.end()); while(braces_level_max > 0) { std::size_t inner_braces_start = 0; std::size_t inner_braces_end = 0; if(braces_levels[0] == braces_level_max) { inner_braces_start = 0; } for(std::size_t index = 1; index < braces_levels.size(); ++index) { if( braces_levels[index - 1] == braces_level_max - 1 && braces_levels[index] == braces_level_max) { inner_braces_start = index; break; } } for(std::size_t index = inner_braces_start; index < braces_levels.size(); ++index) { if(braces_levels[index] == braces_level_max) { inner_braces_end = index; } else { break; } } if(input_without_space.substr(inner_braces_start + 1, inner_braces_end - inner_braces_start - 1) == "") { throw std::runtime_error("Empty content in braces"); } StringCalculatorHelper sch(input_without_space.substr(inner_braces_start + 1, inner_braces_end - inner_braces_start - 1)); input_without_space.replace( inner_braces_start, inner_braces_end - inner_braces_start + 1, std::format("{:.20f}", sch.get_result())); braces_levels = get_braces_levels(input_without_space); braces_level_max = *max_element(braces_levels.begin(), braces_levels.end()); } StringCalculatorHelper sch(input_without_space); return sch.get_result(); } private: std::string input_string; constexpr std::vector<int> get_braces_levels(std::string_view input) { std::vector<int> braces_levels; int braces_level = 0; for(std::size_t index = 0; index < input.size(); ++index) { if(input[index] == '(') { braces_level = braces_level + 1; } braces_levels.emplace_back(braces_level); if(input[index] == ')') { braces_level = braces_level - 1; } } if(braces_level!=0) { throw std::runtime_error("Braces unbalanced"); } return braces_levels; } // copy from https://stackoverflow.com/a/83481/6667035 std::string remove_spaces(std::string str) { std::string::iterator end_pos = std::remove(str.begin(), str.end(), ' '); str.erase(end_pos, str.end()); return str; } };
StringCalculatorHelper
classclass StringCalculatorHelper { public: StringCalculatorHelper(const std::string& input) { input_string = input; } long double get_result() { numbers.clear(); operators.clear(); // remove spaces auto input_without_space = remove_spaces(input_string); parse(input_without_space); while(operators.size() > 0) { perform_computing(); } return numbers[0]; } private: std::string input_string; std::vector<long double> numbers; std::vector<std::string> operators; constexpr void parse(std::string_view input_string) { std::string single_number = ""; if( input_string[0] == '-') { operators.emplace_back(std::string{input_string[0]}); if(single_number == "") { single_number = "0"; } numbers.emplace_back(std::stold(single_number)); single_number = ""; } for(std::size_t index = 0; index < input_string.size(); ++index) { if(input_string.substr(index, 5) == "sqrt-") operators.emplace_back("sqrt-"); if(input_string.substr(index, 4) == "sqrt" && input_string.substr(index, 5) != "sqrt-") operators.emplace_back("sqrt"); if(input_string.substr(index, 4) == "sin-") operators.emplace_back("sin-"); if(input_string.substr(index, 4) == "cos-") operators.emplace_back("cos-"); if(input_string.substr(index, 4) == "tan-") operators.emplace_back("tan-"); if(input_string.substr(index, 4) == "cot-") operators.emplace_back("cot-"); if(input_string.substr(index, 4) == "sec-") operators.emplace_back("sec-"); if(input_string.substr(index, 4) == "csc-") operators.emplace_back("csc-"); if(input_string.substr(index, 3) == "abs") operators.emplace_back("abs"); if(input_string.substr(index, 3) == "log") operators.emplace_back("log"); if(input_string.substr(index, 3) == "sin" && input_string.substr(index, 4) != "sin-") operators.emplace_back("sin"); if(input_string.substr(index, 3) == "cos" && input_string.substr(index, 4) != "cos-") operators.emplace_back("cos"); if(input_string.substr(index, 3) == "tan" && input_string.substr(index, 4) != "tan-") operators.emplace_back("tan"); if(input_string.substr(index, 3) == "cot" && input_string.substr(index, 4) != "cot-") operators.emplace_back("cot"); if(input_string.substr(index, 3) == "sec" && input_string.substr(index, 4) != "sec-") operators.emplace_back("sec"); if(input_string.substr(index, 3) == "csc" && input_string.substr(index, 4) != "csc-") operators.emplace_back("csc"); if(std::isdigit(input_string[index]) || input_string[index] == '.' || input_string[index] == ',') single_number = single_number + input_string[index]; if( input_string[index] == '+' || (input_string[index] == '-' && std::isdigit(input_string[index - 1])) || input_string[index] == '*' || input_string[index] == '/' || input_string[index] == '^') { operators.emplace_back(std::string{input_string[index]}); if(single_number == "") { single_number = "0"; } numbers.emplace_back(std::stold(single_number)); single_number = ""; } } if(single_number == "") { single_number = "0"; } numbers.emplace_back(std::stold(single_number)); } constexpr void perform_computing() { // level 0 computation: advanced functions for(std::size_t index = 0; index < operators.size(); ++index) { if(operators[index] == "sqrt-") { numbers[index] = std::sqrt(-numbers[index]); reduce_operators(index); return; } if(operators[index] == "sqrt") { numbers[index] = std::sqrt(numbers[index]); reduce_operators(index); return; } if(operators[index] == "sin-") { numbers[index] = std::sin(-numbers[index]); reduce_operators(index); return; } if(operators[index] == "cos-") { numbers[index] = std::cos(-numbers[index]); reduce_operators(index); return; } if(operators[index] == "tan-") { numbers[index] = std::tan(-numbers[index]); reduce_operators(index); return; } if(operators[index] == "cot-") { numbers[index] = 1 / std::tan(-numbers[index]); reduce_operators(index); return; } if(operators[index] == "sec-") { numbers[index] = 1 / std::cos(-numbers[index]); reduce_operators(index); return; } if(operators[index] == "csc-") { numbers[index] = 1 / std::sin(-numbers[index]); reduce_operators(index); return; } if(operators[index] == "abs") { numbers[index] = std::abs(numbers[index]); reduce_operators(index); return; } if(operators[index] == "log") { numbers[index] = std::log(numbers[index]); reduce_operators(index); return; } if(operators[index] == "sin") { numbers[index] = std::sin(numbers[index]); reduce_operators(index); return; } if(operators[index] == "cos") { numbers[index] = std::cos(numbers[index]); reduce_operators(index); return; } if(operators[index] == "tan") { numbers[index] = std::tan(numbers[index]); reduce_operators(index); return; } if(operators[index] == "cot") { numbers[index] = 1 / std::tan(numbers[index]); reduce_operators(index); return; } if(operators[index] == "sec") { numbers[index] = 1 / std::cos(numbers[index]); reduce_operators(index); return; } if(operators[index] == "csc") { numbers[index] = 1 / std::sin(numbers[index]); reduce_operators(index); return; } } // level 1 computation: ^ (power) for(std::size_t rIndex = operators.size(); rIndex > 0; --rIndex) { std::size_t index = rIndex - 1; if(operators[index] == "^") { numbers[index] = std::pow(numbers[index], numbers[index + 1]); reduce_operators_and_numbers(index); return; } } // level 2 computation: * (multiplication) and / (division) for(std::size_t index = 0; index < operators.size(); ++index) { if(operators[index] == "*") { numbers[index] = numbers[index] * numbers[index + 1]; reduce_operators_and_numbers(index); return; } if(operators[index] == "/") { numbers[index] = numbers[index] / numbers[index + 1]; reduce_operators_and_numbers(index); return; } } // level 3 computation: + (addition) and - (subtraction) for(std::size_t index = 0; index < operators.size(); ++index) { if(operators[index] == "+") { numbers[index] = numbers[index] + numbers[index + 1]; reduce_operators_and_numbers(index); return; } if(operators[index] == "-") { numbers[index] = numbers[index] - numbers[index + 1]; reduce_operators_and_numbers(index); return; } } } constexpr void reduce_operators_and_numbers(std::size_t index) { reduce_operators(index); reduce_numbers(index); } constexpr void reduce_operators(std::size_t index) { for(std::size_t index1 = index; index1 < operators.size() - 1; ++index1) { operators[index1] = operators[index1 + 1]; } operators.resize(operators.size() - 1); } constexpr void reduce_numbers(std::size_t index) { for(std::size_t index1 = index; index1 < numbers.size() - 2; ++index1) { numbers[index1 + 1] = numbers[index1 + 2]; } numbers.resize(numbers.size() - 1); } // copy from https://stackoverflow.com/a/83481/6667035 std::string remove_spaces(std::string str) { std::string::iterator end_pos = std::remove(str.begin(), str.end(), ' '); str.erase(end_pos, str.end()); return str; } };
Full Testing Code
The full testing code:
// Advanced String Calculator in C++
#include <algorithm>
#include <cassert>
#include <cctype>
#include <chrono>
#include <cmath>
#include <concepts>
#include <execution>
#include <format>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <ranges>
#include <stack>
#include <string>
// From https://stackoverflow.com/a/37264642/6667035
#ifndef NDEBUG
# define M_Assert(Expr, Msg) \
M_Assert_Helper(#Expr, Expr, __FILE__, __LINE__, Msg)
#else
# define M_Assert(Expr, Msg) ;
#endif
void M_Assert_Helper(const char* expr_str, bool expr, const char* file, int line, const char* msg)
{
if (!expr)
{
std::cerr << "Assert failed:\t" << msg << "\n"
<< "Expected:\t" << expr_str << "\n"
<< "Source:\t\t" << file << ", line " << line << "\n";
abort();
}
}
struct recursive_print_fn
{
template<std::ranges::input_range T>
constexpr auto operator()(const T& input, const int level = 0) const
{
T output = input;
std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
[&](auto&& x)
{
std::cout << std::string(level, ' ') << x << std::endl;
return x;
}
);
return output;
}
template<std::ranges::input_range T>
requires (std::ranges::input_range<std::ranges::range_value_t<T>>)
constexpr auto operator()(const T& input, const int level = 0) const
{
T output = input;
std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
[&](auto&& element)
{
return operator()(element, level + 1);
}
);
return output;
}
};
inline constexpr recursive_print_fn recursive_print;
class StringCalculatorHelper
{
public:
StringCalculatorHelper(const std::string& input)
{
input_string = input;
}
long double get_result()
{
numbers.clear();
operators.clear();
// remove spaces
auto input_without_space = remove_spaces(input_string);
parse(input_without_space);
while(operators.size() > 0)
{
perform_computing();
}
return numbers[0];
}
private:
std::string input_string;
std::vector<long double> numbers;
std::vector<std::string> operators;
constexpr void parse(std::string_view input_string)
{
std::string single_number = "";
if( input_string[0] == '-')
{
operators.emplace_back(std::string{input_string[0]});
if(single_number == "")
{
single_number = "0";
}
numbers.emplace_back(std::stold(single_number));
single_number = "";
}
for(std::size_t index = 0; index < input_string.size(); ++index)
{
if(input_string.substr(index, 5) == "sqrt-")
operators.emplace_back("sqrt-");
if(input_string.substr(index, 4) == "sqrt" && input_string.substr(index, 5) != "sqrt-")
operators.emplace_back("sqrt");
if(input_string.substr(index, 4) == "sin-")
operators.emplace_back("sin-");
if(input_string.substr(index, 4) == "cos-")
operators.emplace_back("cos-");
if(input_string.substr(index, 4) == "tan-")
operators.emplace_back("tan-");
if(input_string.substr(index, 4) == "cot-")
operators.emplace_back("cot-");
if(input_string.substr(index, 4) == "sec-")
operators.emplace_back("sec-");
if(input_string.substr(index, 4) == "csc-")
operators.emplace_back("csc-");
if(input_string.substr(index, 3) == "abs")
operators.emplace_back("abs");
if(input_string.substr(index, 3) == "log")
operators.emplace_back("log");
if(input_string.substr(index, 3) == "sin" && input_string.substr(index, 4) != "sin-")
operators.emplace_back("sin");
if(input_string.substr(index, 3) == "cos" && input_string.substr(index, 4) != "cos-")
operators.emplace_back("cos");
if(input_string.substr(index, 3) == "tan" && input_string.substr(index, 4) != "tan-")
operators.emplace_back("tan");
if(input_string.substr(index, 3) == "cot" && input_string.substr(index, 4) != "cot-")
operators.emplace_back("cot");
if(input_string.substr(index, 3) == "sec" && input_string.substr(index, 4) != "sec-")
operators.emplace_back("sec");
if(input_string.substr(index, 3) == "csc" && input_string.substr(index, 4) != "csc-")
operators.emplace_back("csc");
if(std::isdigit(input_string[index]) || input_string[index] == '.' || input_string[index] == ',')
single_number = single_number + input_string[index];
if( input_string[index] == '+' ||
(input_string[index] == '-' && std::isdigit(input_string[index - 1])) ||
input_string[index] == '*' ||
input_string[index] == '/' ||
input_string[index] == '^')
{
operators.emplace_back(std::string{input_string[index]});
if(single_number == "")
{
single_number = "0";
}
numbers.emplace_back(std::stold(single_number));
single_number = "";
}
}
if(single_number == "")
{
single_number = "0";
}
numbers.emplace_back(std::stold(single_number));
}
constexpr void perform_computing()
{
// level 0 computation: advanced functions
for(std::size_t index = 0; index < operators.size(); ++index)
{
if(operators[index] == "sqrt-")
{
numbers[index] = std::sqrt(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "sqrt")
{
numbers[index] = std::sqrt(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "sin-")
{
numbers[index] = std::sin(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "cos-")
{
numbers[index] = std::cos(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "tan-")
{
numbers[index] = std::tan(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "cot-")
{
numbers[index] = 1 / std::tan(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "sec-")
{
numbers[index] = 1 / std::cos(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "csc-")
{
numbers[index] = 1 / std::sin(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "abs")
{
numbers[index] = std::abs(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "log")
{
numbers[index] = std::log(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "sin")
{
numbers[index] = std::sin(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "cos")
{
numbers[index] = std::cos(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "tan")
{
numbers[index] = std::tan(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "cot")
{
numbers[index] = 1 / std::tan(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "sec")
{
numbers[index] = 1 / std::cos(numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "csc")
{
numbers[index] = 1 / std::sin(numbers[index]);
reduce_operators(index);
return;
}
}
// level 1 computation: ^ (power)
for(std::size_t rIndex = operators.size(); rIndex > 0; --rIndex)
{
std::size_t index = rIndex - 1;
if(operators[index] == "^")
{
numbers[index] = std::pow(numbers[index], numbers[index + 1]);
reduce_operators_and_numbers(index);
return;
}
}
// level 2 computation: * (multiplication) and / (division)
for(std::size_t index = 0; index < operators.size(); ++index)
{
if(operators[index] == "*")
{
numbers[index] = numbers[index] * numbers[index + 1];
reduce_operators_and_numbers(index);
return;
}
if(operators[index] == "/")
{
numbers[index] = numbers[index] / numbers[index + 1];
reduce_operators_and_numbers(index);
return;
}
}
// level 3 computation: + (addition) and - (subtraction)
for(std::size_t index = 0; index < operators.size(); ++index)
{
if(operators[index] == "+")
{
numbers[index] = numbers[index] + numbers[index + 1];
reduce_operators_and_numbers(index);
return;
}
if(operators[index] == "-")
{
numbers[index] = numbers[index] - numbers[index + 1];
reduce_operators_and_numbers(index);
return;
}
}
}
constexpr void reduce_operators_and_numbers(std::size_t index)
{
reduce_operators(index);
reduce_numbers(index);
}
constexpr void reduce_operators(std::size_t index)
{
for(std::size_t index1 = index; index1 < operators.size() - 1; ++index1)
{
operators[index1] = operators[index1 + 1];
}
operators.resize(operators.size() - 1);
}
constexpr void reduce_numbers(std::size_t index)
{
for(std::size_t index1 = index; index1 < numbers.size() - 2; ++index1)
{
numbers[index1 + 1] = numbers[index1 + 2];
}
numbers.resize(numbers.size() - 1);
}
// copy from https://stackoverflow.com/a/83481/6667035
std::string remove_spaces(std::string str)
{
std::string::iterator end_pos = std::remove(str.begin(), str.end(), ' ');
str.erase(end_pos, str.end());
return str;
}
};
class StringCalculator
{
public:
StringCalculator(const std::string& input)
{
input_string = input;
}
long double get_result()
{
auto input_without_space = remove_spaces(input_string);
auto braces_levels = get_braces_levels(input_without_space);
auto braces_level_max = *max_element(braces_levels.begin(), braces_levels.end());
while(braces_level_max > 0)
{
std::size_t inner_braces_start = 0;
std::size_t inner_braces_end = 0;
if(braces_levels[0] == braces_level_max)
{
inner_braces_start = 0;
}
for(std::size_t index = 1; index < braces_levels.size(); ++index)
{
if( braces_levels[index - 1] == braces_level_max - 1 &&
braces_levels[index] == braces_level_max)
{
inner_braces_start = index;
break;
}
}
for(std::size_t index = inner_braces_start; index < braces_levels.size(); ++index)
{
if(braces_levels[index] == braces_level_max)
{
inner_braces_end = index;
}
else
{
break;
}
}
if(input_without_space.substr(inner_braces_start + 1, inner_braces_end - inner_braces_start - 1) == "")
{
throw std::runtime_error("Empty content in braces");
}
StringCalculatorHelper sch(input_without_space.substr(inner_braces_start + 1, inner_braces_end - inner_braces_start - 1));
input_without_space.replace(
inner_braces_start,
inner_braces_end - inner_braces_start + 1,
std::format("{:.20f}", sch.get_result()));
braces_levels = get_braces_levels(input_without_space);
braces_level_max = *max_element(braces_levels.begin(), braces_levels.end());
}
StringCalculatorHelper sch(input_without_space);
return sch.get_result();
}
private:
std::string input_string;
constexpr std::vector<int> get_braces_levels(std::string_view input)
{
std::vector<int> braces_levels;
int braces_level = 0;
for(std::size_t index = 0; index < input.size(); ++index)
{
if(input[index] == '(')
{
braces_level = braces_level + 1;
}
braces_levels.emplace_back(braces_level);
if(input[index] == ')')
{
braces_level = braces_level - 1;
}
}
if(braces_level!=0)
{
throw std::runtime_error("Braces unbalanced");
}
return braces_levels;
}
// copy from https://stackoverflow.com/a/83481/6667035
std::string remove_spaces(std::string str)
{
std::string::iterator end_pos = std::remove(str.begin(), str.end(), ' ');
str.erase(end_pos, str.end());
return str;
}
};
// From: https://stackoverflow.com/a/37686/6667035
constexpr bool AreSame(double a, double b, int tolerance) {
return std::fabs(a - b) < tolerance * std::numeric_limits<double>::epsilon();
}
int main()
{
auto start = std::chrono::system_clock::now();
StringCalculator sc1("-12.3456 + 2 ^ 3 * 3.123");
M_Assert(
AreSame(sc1.get_result(), 12.6384, 1),
"\"-12.3456 + 2 ^ 3 * 3.123\" test case failed");
StringCalculator sc2("-0.12345 * 3 - 5");
M_Assert(
AreSame(sc2.get_result(), -5.37035, 1),
"\"-0.12345 * 3 - 5\" test case failed");
StringCalculator sc3("1024*2+2048*4");
M_Assert(
AreSame(sc3.get_result(), 10240, 1),
"\"1024*2+2048*4\" test case failed");
StringCalculator sc4("10-2*3+2*2-7");
M_Assert(
AreSame(sc4.get_result(), 1, 1),
"\"10-2*3+2*2-7\" test case failed");
StringCalculator sc5("1+(2+(3+4))+2+3");
M_Assert(
AreSame(sc5.get_result(), 15, 1),
"\"1+(2+(3+4))+2+3\" test case failed");
StringCalculator sc6("1+sin(1)");
M_Assert(
AreSame(sc6.get_result(), 1.841470984807897, 10),
"\"1+sin(1)\" test case failed"
);
StringCalculator sc7("1+sin(2^2)");
M_Assert(
AreSame(sc7.get_result(), 0.243197504692072, 10),
"\"1+sin(2^2)\" test case failed");
StringCalculator sc8("1+cos(2^(2+2))");
M_Assert(
AreSame(sc8.get_result(), 0.042340519676615, 10),
"\"1+cos(2^(2+2))\" test case failed");
StringCalculator sc9("1+tan((1+2)*3)");
M_Assert(
AreSame(sc9.get_result(), 0.547684340558190, 10),
"\"1+tan((1+2)*3)\" test case failed");
StringCalculator sc10("1+cot(tan((1+2)*3))");
M_Assert(
AreSame(sc10.get_result(), -1.057976196110096, 10),
"\"1+cot(tan((1+2)*3))\" test case failed");
StringCalculator sc11("1+sec(cot(tan((1+2)*3)))");
M_Assert(
AreSame(sc11.get_result(), -1.136132630785074, 10),
"\"1+sec(cot(tan((1+2)*3)))\" test case failed");
StringCalculator sc12("2+3*csc(-2)");
M_Assert(
AreSame(sc12.get_result(), -1.299250510883849, 10),
"\"2+3*csc(-2)\" test case failed");
StringCalculator sc13("2 * sqrt(3) + 1");
M_Assert(
AreSame(sc13.get_result(), 4.464101615137754, 10),
"\"2 * sqrt(3) + 1\" test case failed");
StringCalculator sc14("2 * sqrt(abs(-3)) + 1");
M_Assert(
AreSame(sc14.get_result(), 4.464101615137754, 10),
"\"2 * sqrt(abs(-3)) + 1\" test case failed");
StringCalculator sc15("4^3^2");
M_Assert(
AreSame(sc15.get_result(), 262144, 10),
"\"4^3^2\" test case failed");
StringCalculator sc16("log(4^3)");
M_Assert(
AreSame(sc16.get_result(), 4.158883083359671, 10),
"\"log(4^3)\" test case failed");
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
std::time_t end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
return 0;
}
The output of the test code above:
Computation finished at Wed May 8 18:29:27 2024
elapsed time: 0.000208893
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
What changes has been made in the code since last question?
I am trying to implement an advanced string calculator with more function support in this post.
Why a new review is being asked for?
Please review the implementation of
StringCalculator
andStringCalculatorHelper
class and its tests.
1 Answer 1
Great that you have incorporated all review comments!
This is not a complete review (I may get back to it if time permits), but there are two things that caught my eye.
The parser:
- You continue parsing after you have found a token, instead of advancing to the next token and continue; this is also the cause for these extra checks as in
if(input_string.substr(index, 3) == "csc" input_string.substr(index, 4) != "csc-")
, and generally error prone. - The handling of a unary minus is weird. It should be handled (same as an unary plus which you don't handle) depending on the previously found token, instead of these additional checks for "sin-" etc.
Duplication:
- Also in the parser: the found tokens remain as strings, and have to be parsed again as the same strings later. Better use something like enums, that later allow you to use
switch
instead of these longif
constructs inperform_computing
(1). - These same
if
constructs also have the same redundant code (reduce_operators(index)
), which can be factored out (2). - The tests repeat the input string in the error message - this is also error prone. You write your own testing logic anyway, write a function that takes the input string and the expected result, make the calculation in that test function, and use the input also for the error message (3).
Also, by "lazy calculation" I didn't mean to calculate the result each time it is accessed, but just once and cache the result. Though that is probably not a real issue, as it will not be called more than once in practice.
(1) To show what I mean with the enums:
// define operators as enums
enum class Operator
{
add,
subtract,
plus,
minus,
abs,
sqrt,
...
// define operator strings with corresponding enums
constexpr static std::pair<std::string, Operator> op_names[] = {
{"abs", Operator::abs },
{"sqrt", Operator::sqrt},
{"sin", Operator::sin},
...
// in the parser, iterate over these:
for(std::size_t index = 0; index < input_string.size();)
{
bool found_operator = false;
for (const auto& op : op_names)
{
auto op_name = op.first;
if(input_string.substr(index, op_name.size()) == op_name)
{
operators.emplace_back(op.second);
index += op_name.size();
found_operator = true;
break;
}
}
if (found_operator)
{
continue;
}
...
(2) (question from comment) What I mean is that the code:
for(std::size_t index = 0; index < operators.size(); ++index)
{
if(operators[index] == "sqrt-")
{
numbers[index] = std::sqrt(-numbers[index]);
reduce_operators(index);
return;
}
if(operators[index] == "sqrt")
{
numbers[index] = std::sqrt(numbers[index]);
reduce_operators(index);
return;
}
// many more of these...
}
can be replaced by something like:
for(std::size_t index = 0; index < operators.size(); ++index)
{
bool operatorFound = true;
if(operators[index] == "sqrt-")
{
numbers[index] = std::sqrt(-numbers[index]);
}
else if(operators[index] == "sqrt")
{
numbers[index] = std::sqrt(numbers[index]);
}
// many more of these...
else
{
operatorFound = false;
}
if (operatorFound)
{
reduce_operators(index);
return;
}
}
(and ultimately with enums by a switch statement, or even a function table).
(3) So your tests would look like this:
auto start = std::chrono::system_clock::now();
testExpression("-12.3456 + 2 ^ 3 * 3.123", 12.6384, 1);
testExpression("-0.12345 * 3 - 5", -5.37035, 1);
testExpression("1024*2+2048*4", 10240, 1);
...
-
\$\begingroup\$ Thank you for answering. > These same
if
constructs also have the same redundant code (reduce_operators(index)
), which can be factored out. This is not true,reduce_operators(index)
should be placed after the computation and before the earlyreturn
clause. \$\endgroup\$JimmyHu– JimmyHu2024年05月10日 14:51:53 +00:00Commented May 10, 2024 at 14:51 -
1\$\begingroup\$ I put more explanations into the answer. \$\endgroup\$MrBean Bremen– MrBean Bremen2024年05月10日 18:24:45 +00:00Commented May 10, 2024 at 18:24
-
\$\begingroup\$ > Also, by "lazy calculation" I didn't mean to calculate the result each time it is accessed, but just once and cache the result. How can I cache the result and make the calculation just once? \$\endgroup\$JimmyHu– JimmyHu2024年05月13日 10:10:15 +00:00Commented May 13, 2024 at 10:10
-
1\$\begingroup\$ The result is just a double. Make it an optional instance variable (
optional<double> result
), and check if it is already set inget_result
. \$\endgroup\$MrBean Bremen– MrBean Bremen2024年05月13日 10:37:03 +00:00Commented May 13, 2024 at 10:37
Explore related questions
See similar questions with these tags.