From 928ab63f70a773c97f59ac6c5ad30fe7d4ab71a8 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Fri, 17 Jul 2020 10:55:36 +0200 Subject: [PATCH 01/15] init calculator unit test TDD / extreame programing try --- test/project_test.cc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/project_test.cc b/test/project_test.cc index 5dbeb2f..9a2185d 100644 --- a/test/project_test.cc +++ b/test/project_test.cc @@ -15,5 +15,37 @@ TEST_F(ProjectTest, Run) { ASSERT_EQ(0, project_.run()); } +// TDD +// basic design calc(std::sting) -> double + +TEST_F(ProjectTest, AddPositive) { + ASSERT_EQ(project_.calc("2 3 +"), 5); +} + +TEST_F(ProjectTest, AddNegative) { + ASSERT_EQ(project_.calc("-2 -3 +"), -5); +} + +TEST_F(ProjectTest, Substract) { + ASSERT_EQ(project_.calc("5 2 -"), 3); +} + +TEST_F(ProjectTest, Multiply) { + ASSERT_EQ(project_.calc("3 7 *"), 21); +} + +TEST_F(ProjectTest, Devide) { + ASSERT_EQ(project_.calc("21 7 /"), 3); +} + +TEST_F(ProjectTest, MultiplyWithZero) { + ASSERT_EQ(project_.calc("21 0 *"), 0); +} + +TEST_F(ProjectTest, DevideWithZero) { + ASSERT_EQ(project_.calc("21 0 /"), -1); +} + + } // namespace testing } // namespace dev From 58d7d8c9eef03446054ec4e6c00045b151b042a3 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Mon, 20 Jul 2020 10:49:46 +0200 Subject: [PATCH 02/15] Init project backbone --- src/Calculator/calculator.cpp | 17 +++++++++++ src/Calculator/include/calculator.h | 11 +++++++ test/CMakeLists.txt | 5 ++++ test/calculator_test.cpp | 46 +++++++++++++++++++++++++++++ test/project_test.cc | 32 -------------------- 5 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 src/Calculator/calculator.cpp create mode 100644 src/Calculator/include/calculator.h create mode 100644 test/calculator_test.cpp diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp new file mode 100644 index 0000000..1db23b6 --- /dev/null +++ b/src/Calculator/calculator.cpp @@ -0,0 +1,17 @@ +#include "include/calculator.h" +#include + +Calculator::Calculator() +{ + std::cout << "Calculator created" << std::endl; +} + +Calculator::~Calculator() +{ + std::cout << "Calculator destroied" << std::endl; +} + +double Calculator::calculate(std::string input) +{ + return -1; +} diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h new file mode 100644 index 0000000..0954646 --- /dev/null +++ b/src/Calculator/include/calculator.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class Calculator{ +public: + Calculator(); + ~Calculator(); + + double calculate(std::string input); +}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7f9a5ab..3563812 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,3 +8,8 @@ target_link_libraries(ProjectTest PUBLIC ProjectLib) add_executable (RunTest ${CMAKE_SOURCE_DIR}/run.cc run_tests.cc) add_gtest(RunTest) + +add_executable(CalculatorTest + ${CMAKE_CURRENT_SOURCE_DIR}/calculator_test.cpp + ${CMAKE_SOURCE_DIR}/src/Calculator/calculator.cpp) +add_gtest(CalculatorTest) diff --git a/test/calculator_test.cpp b/test/calculator_test.cpp new file mode 100644 index 0000000..06f9c55 --- /dev/null +++ b/test/calculator_test.cpp @@ -0,0 +1,46 @@ +#include + +#include "../src/Calculator/include/calculator.h" + +class CalculatorTest : public ::testing::Test { + +public: + Calculator calculator; +}; + +//// TDD +//// basic design calc(std::sting) -> double + +TEST_F(CalculatorTest, Init) { + ASSERT_EQ(1, 1); +} + +TEST_F(CalculatorTest, AddPositive) { + ASSERT_EQ(calculator.calculate("2 3 +"), 5); +} + +TEST_F(CalculatorTest, AddNegative) { + ASSERT_EQ(calculator.calculate("-2 -3 +"), -5); +} + +TEST_F(CalculatorTest, Substract) { + ASSERT_EQ(calculator.calculate("5 2 -"), 3); +} + +TEST_F(CalculatorTest, Multiply) { + ASSERT_EQ(calculator.calculate("3 7 *"), 21); +} + +TEST_F(CalculatorTest, Devide) { + ASSERT_EQ(calculator.calculate("21 7 /"), 3); +} + +TEST_F(CalculatorTest, MultiplyWithZero) { + ASSERT_EQ(calculator.calculate("21 0 *"), 0); +} + +TEST_F(CalculatorTest, DevideWithZero) { + ASSERT_EQ(calculator.calculate("21 0 /"), -1); +} + +// Add complex (multiple operand) functions/tests diff --git a/test/project_test.cc b/test/project_test.cc index 9a2185d..5dbeb2f 100644 --- a/test/project_test.cc +++ b/test/project_test.cc @@ -15,37 +15,5 @@ TEST_F(ProjectTest, Run) { ASSERT_EQ(0, project_.run()); } -// TDD -// basic design calc(std::sting) -> double - -TEST_F(ProjectTest, AddPositive) { - ASSERT_EQ(project_.calc("2 3 +"), 5); -} - -TEST_F(ProjectTest, AddNegative) { - ASSERT_EQ(project_.calc("-2 -3 +"), -5); -} - -TEST_F(ProjectTest, Substract) { - ASSERT_EQ(project_.calc("5 2 -"), 3); -} - -TEST_F(ProjectTest, Multiply) { - ASSERT_EQ(project_.calc("3 7 *"), 21); -} - -TEST_F(ProjectTest, Devide) { - ASSERT_EQ(project_.calc("21 7 /"), 3); -} - -TEST_F(ProjectTest, MultiplyWithZero) { - ASSERT_EQ(project_.calc("21 0 *"), 0); -} - -TEST_F(ProjectTest, DevideWithZero) { - ASSERT_EQ(project_.calc("21 0 /"), -1); -} - - } // namespace testing } // namespace dev From 63aff0049e4c98d27e88f7498712536afdf9b2e3 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Mon, 20 Jul 2020 11:26:53 +0200 Subject: [PATCH 03/15] WIP: init Calculator --- src/Calculator/calculator.cpp | 46 +++++++++++++++++++++++++++++ src/Calculator/include/calculator.h | 11 +++++++ 2 files changed, 57 insertions(+) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 1db23b6..ee05647 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -1,5 +1,6 @@ #include "include/calculator.h" #include +#include Calculator::Calculator() { @@ -13,5 +14,50 @@ Calculator::~Calculator() double Calculator::calculate(std::string input) { + clear(); + parseInput(input); + + if (operators.at(0) == '+') { + return operands.at(0) + operands.at(1); + } + + if (operators.at(0) == '-') { + return operands.at(0) - operands.at(1); + } + + if (operators.at(0) == '*') { + return operands.at(0) * operands.at(1); + } + + if (operators.at(0) == '/') { + return operands.at(0) / operands.at(1); + } + return -1; } + +bool Calculator::parseInput(std::string input) +{ + + for (auto o : input) { + if (isOperator(o)) { + operators.push_back(o); + return true; + } + + operands.push_back(o); + return true; + } + return false; +} + +void Calculator::clear() +{ + operands.clear(); + operators.clear(); +} + + +bool Calculator::isOperator(const char& oper) { + return std::find(availableOperators.begin(), availableOperators.end(), oper) != availableOperators.end(); +} diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index 0954646..7e49afe 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -1,11 +1,22 @@ #pragma once #include +#include class Calculator{ + public: Calculator(); ~Calculator(); double calculate(std::string input); + +private: + bool parseInput(std::string input); + void clear(); + bool isOperator(const char& oper); + + std::vector operands; + std::vector operators; + const std::vector availableOperators{'+', '-', '*', '/'}; }; From 519cc202041cb96afbfd7eb51904a7e61ae35038 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 21 Jul 2020 09:08:01 +0200 Subject: [PATCH 04/15] inprove parsing input --- src/Calculator/calculator.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index ee05647..5008bde 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -1,6 +1,8 @@ #include "include/calculator.h" #include #include +#include +#include Calculator::Calculator() { @@ -38,17 +40,20 @@ double Calculator::calculate(std::string input) bool Calculator::parseInput(std::string input) { + // error handling should be implemented + std::istringstream inputStream(input); + std::vector tokens(std::istream_iterator{inputStream}, + std::istream_iterator()); - for (auto o : input) { - if (isOperator(o)) { - operators.push_back(o); - return true; + for (auto token : tokens) { + if (isOperator(token.at(0)) && token.size() == 1) { + operators.push_back(token.at(0)); + continue; } - operands.push_back(o); - return true; + operands.push_back(atof(token.c_str())); } - return false; + return true; } void Calculator::clear() From 36effa8241d9b6eebe283d281d2c2deb0f961a76 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 21 Jul 2020 10:22:34 +0200 Subject: [PATCH 05/15] fix division by zero --- src/Calculator/calculator.cpp | 4 ++++ test/calculator_test.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 5008bde..9a38209 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -32,6 +32,10 @@ double Calculator::calculate(std::string input) } if (operators.at(0) == '/') { + if (operands.at(1) == 0) { + std::cerr << "division by zero is undefined" << std::endl; + return 0; + } return operands.at(0) / operands.at(1); } diff --git a/test/calculator_test.cpp b/test/calculator_test.cpp index 06f9c55..0ae2dca 100644 --- a/test/calculator_test.cpp +++ b/test/calculator_test.cpp @@ -40,7 +40,7 @@ TEST_F(CalculatorTest, MultiplyWithZero) { } TEST_F(CalculatorTest, DevideWithZero) { - ASSERT_EQ(calculator.calculate("21 0 /"), -1); + ASSERT_EQ(calculator.calculate("21 0 /"), 0); } // Add complex (multiple operand) functions/tests From 8f16a19b4747bec47fc290c0c2c02e79f6f48de6 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 21 Jul 2020 11:15:01 +0200 Subject: [PATCH 06/15] Init complex operation --- CMakeLists.txt | 2 +- src/Calculator/calculator.cpp | 135 +++++++++++++++++++++------- src/Calculator/include/calculator.h | 21 +++-- test/calculator_test.cpp | 33 ++++++- 4 files changed, 149 insertions(+), 42 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 424ca2e..29b7a06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5.1) project(dummy_cmake_project) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) set(SOURCES project.cc) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 9a38209..8ec8f1e 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -6,67 +6,142 @@ Calculator::Calculator() { - std::cout << "Calculator created" << std::endl; -} - -Calculator::~Calculator() -{ - std::cout << "Calculator destroied" << std::endl; + initOperators(); } double Calculator::calculate(std::string input) { clear(); - parseInput(input); - if (operators.at(0) == '+') { - return operands.at(0) + operands.at(1); + if (!parseInput(input)) { + return 0.0; } - if (operators.at(0) == '-') { - return operands.at(0) - operands.at(1); - } - - if (operators.at(0) == '*') { - return operands.at(0) * operands.at(1); - } - - if (operators.at(0) == '/') { - if (operands.at(1) == 0) { - std::cerr << "division by zero is undefined" << std::endl; - return 0; + for (int priority = maxOperatorsPriority(); priority >= 0; priority--) { + for (std::size_t i = 0; i < m_operators.size(); i++) { + if (m_availableOperators.at(m_operators.at(i)) == priority) { + if (!intermediateCalculation(i)) { + return 0.0; + } + i -= 1; + } } - return operands.at(0) / operands.at(1); } - return -1; + return m_operands.front(); +} + +std::string Calculator::error() const +{ + return m_error; } bool Calculator::parseInput(std::string input) { - // error handling should be implemented std::istringstream inputStream(input); std::vector tokens(std::istream_iterator{inputStream}, std::istream_iterator()); for (auto token : tokens) { if (isOperator(token.at(0)) && token.size() == 1) { - operators.push_back(token.at(0)); + m_operators.push_back(token.at(0)); continue; } - operands.push_back(atof(token.c_str())); + auto operand = atof(token.c_str()); + if (isNumber(operand)) { + m_operands.push_back(operand); + continue; + } + + m_error = "input simbol: " + token + " not supported!"; + return false; } + + if (m_operands.size() - 1 != m_operators.size()) { + m_error = "input format error! number of operands and number of operators mismach!"; + return false; + } + return true; } void Calculator::clear() { - operands.clear(); - operators.clear(); + m_operands = {}; + m_operators = {}; + m_error.clear(); } -bool Calculator::isOperator(const char& oper) { - return std::find(availableOperators.begin(), availableOperators.end(), oper) != availableOperators.end(); +bool Calculator::isOperator(char oper) { + return m_availableOperators.find(oper) != m_availableOperators.end(); +} + +bool Calculator::isNumber(double) +{ + return true; +} + +bool Calculator::intermediateCalculation(u_int operator_idx) { + + auto oper = m_operators.at(operator_idx); + auto op1 = m_operands.at(operator_idx); + auto op2 = m_operands.at(operator_idx + 1); + + double tmp_res{0.0}; + + switch (oper) { + + case '*' : + tmp_res = op1 * op2; + break; + + case '/' : + if (op2 == 0) { + m_error = "division by zero is undefined"; + return false; + } + tmp_res = op1 / op2; + break; + + case '+' : + tmp_res = op1 + op2; + break; + + case '-' : + tmp_res = op1 - op2; + break; + + default: + m_error = "Operator not implemented!"; + return false; + } + + m_operators.erase(m_operators.begin() + operator_idx); + m_operands[operator_idx] = tmp_res; + m_operands.erase(m_operands.begin() + operator_idx + 1); + + return true; +} + +void Calculator::initOperators() +{ + m_availableOperators.insert({'*', 1}); + m_availableOperators.insert({'/', 1}); + m_availableOperators.insert({'+', 0}); + m_availableOperators.insert({'-', 0}); +} + +int Calculator::maxOperatorsPriority() +{ + using u_map_type = decltype(m_availableOperators)::value_type; + auto pr = std::max_element + ( + std::begin(m_availableOperators), std::end(m_availableOperators), + [] (const u_map_type & p1, const u_map_type & p2) { + return p1.second < p2.second; + } + ); + return pr->first; } diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index 7e49afe..d2b1502 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -1,22 +1,29 @@ -#pragma once +#pragma once #include +#include #include class Calculator{ public: Calculator(); - ~Calculator(); double calculate(std::string input); + std::string error() const; private: - bool parseInput(std::string input); + + void initOperators(); void clear(); - bool isOperator(const char& oper); + bool parseInput(std::string input); + bool intermediateCalculation(u_int operator_idx); + bool isOperator(char oper); + bool isNumber(double); + int maxOperatorsPriority(); - std::vector operands; - std::vector operators; - const std::vector availableOperators{'+', '-', '*', '/'}; + std::vector m_operands; + std::vector m_operators; + std::unordered_map m_availableOperators; + std::string m_error; }; diff --git a/test/calculator_test.cpp b/test/calculator_test.cpp index 0ae2dca..e46174d 100644 --- a/test/calculator_test.cpp +++ b/test/calculator_test.cpp @@ -2,13 +2,15 @@ #include "../src/Calculator/include/calculator.h" +#include + class CalculatorTest : public ::testing::Test { public: Calculator calculator; }; -//// TDD +//// TDD + extreme programing //// basic design calc(std::sting) -> double TEST_F(CalculatorTest, Init) { @@ -35,12 +37,35 @@ TEST_F(CalculatorTest, Devide) { ASSERT_EQ(calculator.calculate("21 7 /"), 3); } -TEST_F(CalculatorTest, MultiplyWithZero) { +TEST_F(CalculatorTest, MultiplyByZero) { ASSERT_EQ(calculator.calculate("21 0 *"), 0); } -TEST_F(CalculatorTest, DevideWithZero) { +TEST_F(CalculatorTest, DevideByZero) { ASSERT_EQ(calculator.calculate("21 0 /"), 0); + ASSERT_EQ(calculator.error(), "division by zero is undefined"); +} + +TEST_F(CalculatorTest, ComplexAdd) { + ASSERT_EQ(calculator.calculate("2 3 6 + +"), 11); +} + +TEST_F(CalculatorTest, ComplexPriorityOperation) { + ASSERT_EQ(calculator.calculate("2 3 6 + *"), 20); +} + +TEST_F(CalculatorTest, ComplexPriorityOperationMultiplyByZero) { + ASSERT_EQ(calculator.calculate("2 3 0 + *"), 2); +} + +TEST_F(CalculatorTest, ComplexPriorityOperationDevideByZero) { + ASSERT_EQ(calculator.calculate("2 3 0 + /"), 0); + ASSERT_EQ(calculator.error(), "division by zero is undefined"); } -// Add complex (multiple operand) functions/tests +//TEST_F(CalculatorTest, SimbolNotSuported) { +// ASSERT_EQ(calculator.calculate("2 3 #"), 0); +// // isOperand() returns true for # ?? +// std::cout << calculator.error(); +// ASSERT_EQ(calculator.error(), "input simbol: # not supported!"); +//} From a7858adbca18448402d89464b2929e54ce16c633 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Wed, 22 Jul 2020 11:12:38 +0200 Subject: [PATCH 07/15] replace index names with more meaningfull names --- src/Calculator/calculator.cpp | 15 +++++++++++---- src/Calculator/include/calculator.h | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 8ec8f1e..ba77b48 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -18,12 +18,14 @@ double Calculator::calculate(std::string input) } for (int priority = maxOperatorsPriority(); priority >= 0; priority--) { - for (std::size_t i = 0; i < m_operators.size(); i++) { - if (m_availableOperators.at(m_operators.at(i)) == priority) { - if (!intermediateCalculation(i)) { + for (std::size_t currentOperatorIndex = 0; + currentOperatorIndex < m_operators.size(); + currentOperatorIndex++) { + if (operatorPriority(m_operators.at(currentOperatorIndex)) == priority) { + if (!intermediateCalculation(currentOperatorIndex)) { return 0.0; } - i -= 1; + currentOperatorIndex -= 1; } } } @@ -145,3 +147,8 @@ int Calculator::maxOperatorsPriority() ); return pr->first; } + +int Calculator::operatorPriority(const char &oper) +{ + return m_availableOperators.at(oper); +} diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index d2b1502..196c14b 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -21,6 +21,7 @@ class Calculator{ bool isOperator(char oper); bool isNumber(double); int maxOperatorsPriority(); + int operatorPriority(const char& oper); std::vector m_operands; std::vector m_operators; From 9ca7e710d6155314dee076fa724176622fd78825 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Wed, 22 Jul 2020 11:41:40 +0200 Subject: [PATCH 08/15] implement isNumber() --- src/Calculator/calculator.cpp | 26 +++++++++++++++++++------- src/Calculator/include/calculator.h | 2 +- test/calculator_test.cpp | 10 ++++------ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index ba77b48..286169c 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -45,14 +45,14 @@ bool Calculator::parseInput(std::string input) std::istream_iterator()); for (auto token : tokens) { - if (isOperator(token.at(0)) && token.size() == 1) { + + if (token.size() == 1 && isOperator(token.at(0))) { m_operators.push_back(token.at(0)); continue; } - auto operand = atof(token.c_str()); - if (isNumber(operand)) { - m_operands.push_back(operand); + if (isNumber(token)) { + m_operands.push_back(atof(token.c_str())); continue; } @@ -77,12 +77,24 @@ void Calculator::clear() bool Calculator::isOperator(char oper) { - return m_availableOperators.find(oper) != m_availableOperators.end(); + bool Ok = m_availableOperators.find(oper) != m_availableOperators.end(); + return Ok; } -bool Calculator::isNumber(double) +bool Calculator::isNumber(std::string token) { - return true; + // consider negative numbers + if (token.at(0) == '-') { + token.erase(0, 1); + } + bool isDigit = !token.empty() && + std::find_if(token.begin(), + token.end(), + [](unsigned char c) { + return !std::isdigit(c); + } + ) == token.end(); + return isDigit; } bool Calculator::intermediateCalculation(u_int operator_idx) { diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index 196c14b..6e53438 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -19,7 +19,7 @@ class Calculator{ bool parseInput(std::string input); bool intermediateCalculation(u_int operator_idx); bool isOperator(char oper); - bool isNumber(double); + bool isNumber(std::string token); int maxOperatorsPriority(); int operatorPriority(const char& oper); diff --git a/test/calculator_test.cpp b/test/calculator_test.cpp index e46174d..b00637b 100644 --- a/test/calculator_test.cpp +++ b/test/calculator_test.cpp @@ -63,9 +63,7 @@ TEST_F(CalculatorTest, ComplexPriorityOperationDevideByZero) { ASSERT_EQ(calculator.error(), "division by zero is undefined"); } -//TEST_F(CalculatorTest, SimbolNotSuported) { -// ASSERT_EQ(calculator.calculate("2 3 #"), 0); -// // isOperand() returns true for # ?? -// std::cout << calculator.error(); -// ASSERT_EQ(calculator.error(), "input simbol: # not supported!"); -//} +TEST_F(CalculatorTest, SimbolNotSuported) { + ASSERT_EQ(calculator.calculate("2 3 #"), 0); + ASSERT_EQ(calculator.error(), "input simbol: # not supported!"); +} From 8b89e3d635f9155940f97370a2d175708fd3ca0f Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Mon, 27 Jul 2020 11:33:46 +0200 Subject: [PATCH 09/15] Refactor calculator to comply with Polish notation --- src/Calculator/calculator.cpp | 175 +++++++++++----------------- src/Calculator/include/calculator.h | 28 ++--- test/calculator_test.cpp | 13 ++- 3 files changed, 86 insertions(+), 130 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 286169c..ac09a6c 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -3,56 +3,35 @@ #include #include #include +#include Calculator::Calculator() { - initOperators(); } double Calculator::calculate(std::string input) { clear(); - if (!parseInput(input)) { - return 0.0; - } - - for (int priority = maxOperatorsPriority(); priority >= 0; priority--) { - for (std::size_t currentOperatorIndex = 0; - currentOperatorIndex < m_operators.size(); - currentOperatorIndex++) { - if (operatorPriority(m_operators.at(currentOperatorIndex)) == priority) { - if (!intermediateCalculation(currentOperatorIndex)) { - return 0.0; - } - currentOperatorIndex -= 1; - } - } - } - - return m_operands.front(); -} - -std::string Calculator::error() const -{ - return m_error; -} - -bool Calculator::parseInput(std::string input) -{ std::istringstream inputStream(input); std::vector tokens(std::istream_iterator{inputStream}, - std::istream_iterator()); + std::istream_iterator()); for (auto token : tokens) { if (token.size() == 1 && isOperator(token.at(0))) { - m_operators.push_back(token.at(0)); + auto res = calculateNext(token.at(0)); + + if (!res) { + return 0.0; + } + + m_tmp_operands.push(res.value()); continue; } if (isNumber(token)) { - m_operands.push_back(atof(token.c_str())); + m_tmp_operands.push(atof(token.c_str())); continue; } @@ -60,28 +39,72 @@ bool Calculator::parseInput(std::string input) return false; } - if (m_operands.size() - 1 != m_operators.size()) { - m_error = "input format error! number of operands and number of operators mismach!"; - return false; - } + return m_tmp_operands.top(); +} - return true; +std::string Calculator::error() const +{ + return m_error; } void Calculator::clear() { - m_operands = {}; - m_operators = {}; + m_tmp_operands = {}; m_error.clear(); } +Calculator::result Calculator::calculateNext(char oper) +{ + if (!isOperator(oper)) { + m_error = "operator: " + std::string{oper} + "not supported"; + return {}; + } + + if (m_tmp_operands.size() < min_operands_number) { + m_error = "wrong input string!"; + return {}; + } + + auto op1 = m_tmp_operands.top(); + m_tmp_operands.pop(); + auto op2 = m_tmp_operands.top(); + m_tmp_operands.pop(); + + switch (oper) { + case '*' : + return op1 * op2; + break; + + case '/' : + if (op1 == 0) { + m_error = "result is indefinite"; + return {}; + } + if (op2 == 0) { + m_error = "division by zero is undefined"; + return {}; + } + return op1 / op2; + break; + + case '+' : + return op1 + op2; + break; + + case '-' : + return op1 - op2; + break; + default: + return {}; + } +} + -bool Calculator::isOperator(char oper) { - bool Ok = m_availableOperators.find(oper) != m_availableOperators.end(); - return Ok; +bool Calculator::isOperator(char oper) const { + return std::find(m_availableOperators.begin(), m_availableOperators.end(), oper) != m_availableOperators.end(); } -bool Calculator::isNumber(std::string token) +bool Calculator::isNumber(std::string token) const { // consider negative numbers if (token.at(0) == '-') { @@ -96,71 +119,3 @@ bool Calculator::isNumber(std::string token) ) == token.end(); return isDigit; } - -bool Calculator::intermediateCalculation(u_int operator_idx) { - - auto oper = m_operators.at(operator_idx); - auto op1 = m_operands.at(operator_idx); - auto op2 = m_operands.at(operator_idx + 1); - - double tmp_res{0.0}; - - switch (oper) { - - case '*' : - tmp_res = op1 * op2; - break; - - case '/' : - if (op2 == 0) { - m_error = "division by zero is undefined"; - return false; - } - tmp_res = op1 / op2; - break; - - case '+' : - tmp_res = op1 + op2; - break; - - case '-' : - tmp_res = op1 - op2; - break; - - default: - m_error = "Operator not implemented!"; - return false; - } - - m_operators.erase(m_operators.begin() + operator_idx); - m_operands[operator_idx] = tmp_res; - m_operands.erase(m_operands.begin() + operator_idx + 1); - - return true; -} - -void Calculator::initOperators() -{ - m_availableOperators.insert({'*', 1}); - m_availableOperators.insert({'/', 1}); - m_availableOperators.insert({'+', 0}); - m_availableOperators.insert({'-', 0}); -} - -int Calculator::maxOperatorsPriority() -{ - using u_map_type = decltype(m_availableOperators)::value_type; - auto pr = std::max_element - ( - std::begin(m_availableOperators), std::end(m_availableOperators), - [] (const u_map_type & p1, const u_map_type & p2) { - return p1.second < p2.second; - } - ); - return pr->first; -} - -int Calculator::operatorPriority(const char &oper) -{ - return m_availableOperators.at(oper); -} diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index 6e53438..7d68024 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -1,11 +1,14 @@ #pragma once #include -#include -#include +#include +#include +#include class Calculator{ + using result = std::optional; + public: Calculator(); @@ -13,18 +16,15 @@ class Calculator{ std::string error() const; private: - - void initOperators(); void clear(); - bool parseInput(std::string input); - bool intermediateCalculation(u_int operator_idx); - bool isOperator(char oper); - bool isNumber(std::string token); - int maxOperatorsPriority(); - int operatorPriority(const char& oper); - - std::vector m_operands; - std::vector m_operators; - std::unordered_map m_availableOperators; + result calculateNext(char oper); + + bool isOperator(char oper) const; + bool isNumber(std::string token) const; + + std::stack m_tmp_operands; std::string m_error; + + static constexpr std::array m_availableOperators{'*', '/', '+', '-'}; + static constexpr int min_operands_number{2}; }; diff --git a/test/calculator_test.cpp b/test/calculator_test.cpp index b00637b..dd53ff4 100644 --- a/test/calculator_test.cpp +++ b/test/calculator_test.cpp @@ -11,6 +11,7 @@ class CalculatorTest : public ::testing::Test { }; //// TDD + extreme programing +//// Polish notation calculator //// basic design calc(std::sting) -> double TEST_F(CalculatorTest, Init) { @@ -26,7 +27,7 @@ TEST_F(CalculatorTest, AddNegative) { } TEST_F(CalculatorTest, Substract) { - ASSERT_EQ(calculator.calculate("5 2 -"), 3); + ASSERT_EQ(calculator.calculate("5 2 -"), -3); } TEST_F(CalculatorTest, Multiply) { @@ -34,7 +35,7 @@ TEST_F(CalculatorTest, Multiply) { } TEST_F(CalculatorTest, Devide) { - ASSERT_EQ(calculator.calculate("21 7 /"), 3); + ASSERT_EQ(calculator.calculate("7 21 /"), 3); } TEST_F(CalculatorTest, MultiplyByZero) { @@ -43,7 +44,7 @@ TEST_F(CalculatorTest, MultiplyByZero) { TEST_F(CalculatorTest, DevideByZero) { ASSERT_EQ(calculator.calculate("21 0 /"), 0); - ASSERT_EQ(calculator.error(), "division by zero is undefined"); + ASSERT_EQ(calculator.error(), "result is indefinite"); } TEST_F(CalculatorTest, ComplexAdd) { @@ -51,15 +52,15 @@ TEST_F(CalculatorTest, ComplexAdd) { } TEST_F(CalculatorTest, ComplexPriorityOperation) { - ASSERT_EQ(calculator.calculate("2 3 6 + *"), 20); + ASSERT_EQ(calculator.calculate("2 3 6 + *"), 18); } TEST_F(CalculatorTest, ComplexPriorityOperationMultiplyByZero) { - ASSERT_EQ(calculator.calculate("2 3 0 + *"), 2); + ASSERT_EQ(calculator.calculate("2 3 0 + *"), 6); } TEST_F(CalculatorTest, ComplexPriorityOperationDevideByZero) { - ASSERT_EQ(calculator.calculate("2 3 0 + /"), 0); + ASSERT_EQ(calculator.calculate("0 3 2 + /"), 0); ASSERT_EQ(calculator.error(), "division by zero is undefined"); } From e0b688604c65d54ad4ac2bc7e6a32fd0f819d111 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 28 Jul 2020 00:03:09 +0200 Subject: [PATCH 10/15] Extract poping operands --- src/Calculator/calculator.cpp | 21 ++++++++++++--------- src/Calculator/include/calculator.h | 8 ++++++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index ac09a6c..8445ad7 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -20,7 +20,7 @@ double Calculator::calculate(std::string input) for (auto token : tokens) { if (token.size() == 1 && isOperator(token.at(0))) { - auto res = calculateNext(token.at(0)); + auto res = calculateNext(popOperands(), token.at(0)); if (!res) { return 0.0; @@ -36,7 +36,7 @@ double Calculator::calculate(std::string input) } m_error = "input simbol: " + token + " not supported!"; - return false; + return 0.0; } return m_tmp_operands.top(); @@ -53,14 +53,9 @@ void Calculator::clear() m_error.clear(); } -Calculator::result Calculator::calculateNext(char oper) +Calculator::operands Calculator::popOperands() { - if (!isOperator(oper)) { - m_error = "operator: " + std::string{oper} + "not supported"; - return {}; - } - - if (m_tmp_operands.size() < min_operands_number) { + if (m_tmp_operands.size() < min_operandsNumber) { m_error = "wrong input string!"; return {}; } @@ -70,6 +65,14 @@ Calculator::result Calculator::calculateNext(char oper) auto op2 = m_tmp_operands.top(); m_tmp_operands.pop(); + return std::make_pair(op1, op2); +} + +Calculator::result Calculator::calculateNext(operands operands, char oper) +{ + auto op1 = operands.first; + auto op2 = operands.second; + switch (oper) { case '*' : return op1 * op2; diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index 7d68024..014e07c 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -4,10 +4,12 @@ #include #include #include +#include class Calculator{ using result = std::optional; + using operands = std::pair; public: Calculator(); @@ -17,7 +19,9 @@ class Calculator{ private: void clear(); - result calculateNext(char oper); + + operands popOperands(); + result calculateNext(operands operands, char oper); bool isOperator(char oper) const; bool isNumber(std::string token) const; @@ -26,5 +30,5 @@ class Calculator{ std::string m_error; static constexpr std::array m_availableOperators{'*', '/', '+', '-'}; - static constexpr int min_operands_number{2}; + static constexpr int min_operandsNumber{2}; }; From 1f6ca54744ca47326af16c3cefa63bdc5648e835 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 28 Jul 2020 00:20:37 +0200 Subject: [PATCH 11/15] extract tokenize() --- src/Calculator/calculator.cpp | 32 +++++++++++++++++++---------- src/Calculator/include/calculator.h | 11 ++++++---- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 8445ad7..34522df 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -3,7 +3,6 @@ #include #include #include -#include Calculator::Calculator() { @@ -13,13 +12,14 @@ double Calculator::calculate(std::string input) { clear(); - std::istringstream inputStream(input); - std::vector tokens(std::istream_iterator{inputStream}, - std::istream_iterator()); + for (auto token : tokenize(input)) { - for (auto token : tokens) { + if (isNumber(token)) { + m_tmp_operands.push(atof(token.c_str())); + continue; + } - if (token.size() == 1 && isOperator(token.at(0))) { + if (isOperator(token.at(0))) { auto res = calculateNext(popOperands(), token.at(0)); if (!res) { @@ -30,11 +30,6 @@ double Calculator::calculate(std::string input) continue; } - if (isNumber(token)) { - m_tmp_operands.push(atof(token.c_str())); - continue; - } - m_error = "input simbol: " + token + " not supported!"; return 0.0; } @@ -74,6 +69,7 @@ Calculator::result Calculator::calculateNext(operands operands, char oper) auto op2 = operands.second; switch (oper) { + case '*' : return op1 * op2; break; @@ -97,11 +93,25 @@ Calculator::result Calculator::calculateNext(operands operands, char oper) case '-' : return op1 - op2; break; + default: return {}; } } +Calculator::tokens Calculator::tokenize(std::string input) const +{ + std::istringstream inputStream(input); + std::vector tokens(std::istream_iterator{inputStream}, + std::istream_iterator()); + + if (tokens.empty()) { + return {}; + } + + return tokens; +} + bool Calculator::isOperator(char oper) const { return std::find(m_availableOperators.begin(), m_availableOperators.end(), oper) != m_availableOperators.end(); diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index 014e07c..354e25d 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -5,24 +5,27 @@ #include #include #include +#include class Calculator{ - using result = std::optional; - using operands = std::pair; - public: Calculator(); double calculate(std::string input); + void clear(); std::string error() const; private: - void clear(); + using result = std::optional; + using operands = std::pair; + using tokens = std::vector; +private: operands popOperands(); result calculateNext(operands operands, char oper); + tokens tokenize(std::string input) const; bool isOperator(char oper) const; bool isNumber(std::string token) const; From 14d0a049707a6b9af73e31f15b9a8adc7744aff5 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 28 Jul 2020 01:04:29 +0200 Subject: [PATCH 12/15] Fix - swap operands order --- src/Calculator/calculator.cpp | 4 ++-- src/Calculator/include/calculator.h | 4 ++++ test/calculator_test.cpp | 22 +++++++++------------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 34522df..c80a48c 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -55,10 +55,10 @@ Calculator::operands Calculator::popOperands() return {}; } - auto op1 = m_tmp_operands.top(); - m_tmp_operands.pop(); auto op2 = m_tmp_operands.top(); m_tmp_operands.pop(); + auto op1 = m_tmp_operands.top(); + m_tmp_operands.pop(); return std::make_pair(op1, op2); } diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index 354e25d..facd7b1 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -7,6 +7,10 @@ #include #include +/** + * @brief Polish notation calculator + */ + class Calculator{ public: diff --git a/test/calculator_test.cpp b/test/calculator_test.cpp index dd53ff4..0b5e194 100644 --- a/test/calculator_test.cpp +++ b/test/calculator_test.cpp @@ -10,14 +10,6 @@ class CalculatorTest : public ::testing::Test { Calculator calculator; }; -//// TDD + extreme programing -//// Polish notation calculator -//// basic design calc(std::sting) -> double - -TEST_F(CalculatorTest, Init) { - ASSERT_EQ(1, 1); -} - TEST_F(CalculatorTest, AddPositive) { ASSERT_EQ(calculator.calculate("2 3 +"), 5); } @@ -27,7 +19,7 @@ TEST_F(CalculatorTest, AddNegative) { } TEST_F(CalculatorTest, Substract) { - ASSERT_EQ(calculator.calculate("5 2 -"), -3); + ASSERT_EQ(calculator.calculate("5 2 -"), 3); } TEST_F(CalculatorTest, Multiply) { @@ -35,7 +27,7 @@ TEST_F(CalculatorTest, Multiply) { } TEST_F(CalculatorTest, Devide) { - ASSERT_EQ(calculator.calculate("7 21 /"), 3); + ASSERT_EQ(calculator.calculate("21 7 /"), 3); } TEST_F(CalculatorTest, MultiplyByZero) { @@ -44,7 +36,7 @@ TEST_F(CalculatorTest, MultiplyByZero) { TEST_F(CalculatorTest, DevideByZero) { ASSERT_EQ(calculator.calculate("21 0 /"), 0); - ASSERT_EQ(calculator.error(), "result is indefinite"); + ASSERT_EQ(calculator.error(), "division by zero is undefined"); } TEST_F(CalculatorTest, ComplexAdd) { @@ -59,12 +51,16 @@ TEST_F(CalculatorTest, ComplexPriorityOperationMultiplyByZero) { ASSERT_EQ(calculator.calculate("2 3 0 + *"), 6); } -TEST_F(CalculatorTest, ComplexPriorityOperationDevideByZero) { +TEST_F(CalculatorTest, ComplexPriorityOperationIndefinite) { ASSERT_EQ(calculator.calculate("0 3 2 + /"), 0); - ASSERT_EQ(calculator.error(), "division by zero is undefined"); + ASSERT_EQ(calculator.error(), "result is indefinite"); } TEST_F(CalculatorTest, SimbolNotSuported) { ASSERT_EQ(calculator.calculate("2 3 #"), 0); ASSERT_EQ(calculator.error(), "input simbol: # not supported!"); } + +TEST_F(CalculatorTest, VeryComplex) { + ASSERT_EQ(calculator.calculate("4 12 3 + * 2 / 5 5 + * 100 2 * - 2 /"), 50); +} From 3859df57922ec43f8a5bdda5288d248f2c73b8db Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 28 Jul 2020 10:39:34 +0200 Subject: [PATCH 13/15] Fix zero devide --- src/Calculator/calculator.cpp | 4 ---- test/calculator_test.cpp | 1 - 2 files changed, 5 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index c80a48c..2d457b0 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -75,10 +75,6 @@ Calculator::result Calculator::calculateNext(operands operands, char oper) break; case '/' : - if (op1 == 0) { - m_error = "result is indefinite"; - return {}; - } if (op2 == 0) { m_error = "division by zero is undefined"; return {}; diff --git a/test/calculator_test.cpp b/test/calculator_test.cpp index 0b5e194..67a0b13 100644 --- a/test/calculator_test.cpp +++ b/test/calculator_test.cpp @@ -53,7 +53,6 @@ TEST_F(CalculatorTest, ComplexPriorityOperationMultiplyByZero) { TEST_F(CalculatorTest, ComplexPriorityOperationIndefinite) { ASSERT_EQ(calculator.calculate("0 3 2 + /"), 0); - ASSERT_EQ(calculator.error(), "result is indefinite"); } TEST_F(CalculatorTest, SimbolNotSuported) { From 88ad611e6a2204e70b1d5ea8583d20261307ecba Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 28 Jul 2020 10:41:53 +0200 Subject: [PATCH 14/15] Fix No need of break after return in swithch --- src/Calculator/calculator.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index 2d457b0..b5abfc0 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -72,7 +72,6 @@ Calculator::result Calculator::calculateNext(operands operands, char oper) case '*' : return op1 * op2; - break; case '/' : if (op2 == 0) { @@ -80,15 +79,12 @@ Calculator::result Calculator::calculateNext(operands operands, char oper) return {}; } return op1 / op2; - break; case '+' : return op1 + op2; - break; case '-' : return op1 - op2; - break; default: return {}; From 0415176d925bf49cb548491d5526ba70a76e1700 Mon Sep 17 00:00:00 2001 From: Goran Shekerov Date: Tue, 28 Jul 2020 11:50:42 +0200 Subject: [PATCH 15/15] Extract operand/operator handles --- src/Calculator/calculator.cpp | 40 +++++++++++++++++++---------- src/Calculator/include/calculator.h | 8 +++--- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/Calculator/calculator.cpp b/src/Calculator/calculator.cpp index b5abfc0..121fabd 100644 --- a/src/Calculator/calculator.cpp +++ b/src/Calculator/calculator.cpp @@ -14,19 +14,15 @@ double Calculator::calculate(std::string input) for (auto token : tokenize(input)) { - if (isNumber(token)) { - m_tmp_operands.push(atof(token.c_str())); + if (isOperand(token)) { + handleOperandToken(token); continue; } - if (isOperator(token.at(0))) { - auto res = calculateNext(popOperands(), token.at(0)); - - if (!res) { - return 0.0; + if (isOperator(token)) { + if (!handleOperatorToken(token)) { + return {}; } - - m_tmp_operands.push(res.value()); continue; } @@ -91,7 +87,24 @@ Calculator::result Calculator::calculateNext(operands operands, char oper) } } -Calculator::tokens Calculator::tokenize(std::string input) const +void Calculator::handleOperandToken(const std::string &token) +{ + m_tmp_operands.push(atof(token.c_str())); +} + +Calculator::result Calculator::handleOperatorToken(const std::string &token) +{ + auto res = calculateNext(popOperands(), token.at(0)); + + if (!res) { + return {}; + } + + m_tmp_operands.push(res.value()); + return res; +} + +Calculator::tokens Calculator::tokenize(const std::string &input) const { std::istringstream inputStream(input); std::vector tokens(std::istream_iterator{inputStream}, @@ -105,11 +118,12 @@ Calculator::tokens Calculator::tokenize(std::string input) const } -bool Calculator::isOperator(char oper) const { - return std::find(m_availableOperators.begin(), m_availableOperators.end(), oper) != m_availableOperators.end(); +bool Calculator::isOperator(const std::string &oper) const { + auto _oper = oper.at(0); + return std::find(m_availableOperators.begin(), m_availableOperators.end(), _oper) != m_availableOperators.end(); } -bool Calculator::isNumber(std::string token) const +bool Calculator::isOperand(std::string token) const { // consider negative numbers if (token.at(0) == '-') { diff --git a/src/Calculator/include/calculator.h b/src/Calculator/include/calculator.h index facd7b1..db74c58 100644 --- a/src/Calculator/include/calculator.h +++ b/src/Calculator/include/calculator.h @@ -28,10 +28,12 @@ class Calculator{ private: operands popOperands(); result calculateNext(operands operands, char oper); + void handleOperandToken(const std::string& token); + result handleOperatorToken(const std::string& token); - tokens tokenize(std::string input) const; - bool isOperator(char oper) const; - bool isNumber(std::string token) const; + tokens tokenize(const std::string& input) const; + bool isOperator(const std::string& oper) const; + bool isOperand(std::string token) const; std::stack m_tmp_operands; std::string m_error;