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.