3
\$\begingroup\$

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 and csc)

  • sqrt, abs and log functions

The experimental implementation

  • StringCalculator class

    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;
            }
    };
    
  • StringCalculatorHelper class

    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;
            }
    };
    

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

Godbolt link is here.

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    String Calculator in C++

  • 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 and StringCalculatorHelper class and its tests.

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

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 long if constructs in perform_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);
...
\$\endgroup\$
4
  • \$\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 early return clause. \$\endgroup\$
    – JimmyHu
    Commented May 10 at 14:51
  • 1
    \$\begingroup\$ I put more explanations into the answer. \$\endgroup\$ Commented May 10 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
    Commented May 13 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 in get_result. \$\endgroup\$ Commented May 13 at 10:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.