diff --git a/modules/nedelin_d_number_to_words_lab3/CMakeLists.txt b/modules/nedelin_d_number_to_words_lab3/CMakeLists.txt new file mode 100644 index 00000000..5c277256 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/CMakeLists.txt @@ -0,0 +1,20 @@ +# Declare variables for binaries' names +get_filename_component(DIR_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +set(MODULE "${DIR_NAME}") +set(LIBRARY "lib_${MODULE}") +set(TESTS "test_${MODULE}") +set(APPLICATION "app_${MODULE}") + +# Include directory with public headers +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +# Add all submodules +add_subdirectory(src) +add_subdirectory(test) +add_subdirectory(application) + +############################################# +##### Testing +############################################# + +include("CTestTests.txt") diff --git a/modules/nedelin_d_number_to_words_lab3/CTestTests.txt b/modules/nedelin_d_number_to_words_lab3/CTestTests.txt new file mode 100644 index 00000000..1b65d7b9 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/CTestTests.txt @@ -0,0 +1,97 @@ +############################################# +##### Testing +############################################# + +set(prefix "${MODULE}") + +add_test( + NAME ${prefix}_convert_zero + COMMAND ${APPLICATION} "0" +) +set_tests_properties(${prefix}_convert_zero PROPERTIES + PASS_REGULAR_EXPRESSION "zero" + LABELS "${MODULE}" +) + +add_test( + NAME ${prefix}_convert_one + COMMAND ${APPLICATION} "1" +) +set_tests_properties(${prefix}_convert_one PROPERTIES + PASS_REGULAR_EXPRESSION "one" + LABELS "${MODULE}" +) + +add_test( + NAME ${prefix}_convert_negative + COMMAND ${APPLICATION} "-123" +) +set_tests_properties(${prefix}_convert_negative PROPERTIES + PASS_REGULAR_EXPRESSION "negative one hundred twenty three" + LABELS "${MODULE}" +) + +add_test( + NAME ${prefix}_convert_large_number + COMMAND ${APPLICATION} "1000000" +) +set_tests_properties(${prefix}_convert_large_number PROPERTIES + PASS_REGULAR_EXPRESSION "one million" + LABELS "${MODULE}" +) + +add_test( + NAME ${prefix}_convert_edge_case_twenty + COMMAND ${APPLICATION} "20" +) +set_tests_properties(${prefix}_convert_edge_case_twenty PROPERTIES + PASS_REGULAR_EXPRESSION "twenty" + LABELS "${MODULE}" +) + +# Тесты для ошибок и невалидных вводов +add_test( + NAME ${prefix}_invalid_input + COMMAND ${APPLICATION} "abc" +) +set_tests_properties(${prefix}_invalid_input PROPERTIES + PASS_REGULAR_EXPRESSION "Invalid number format!" + LABELS "${MODULE}" +) + +add_test( + NAME ${prefix}_missing_argument + COMMAND ${APPLICATION} +) +set_tests_properties(${prefix}_missing_argument PROPERTIES + PASS_REGULAR_EXPRESSION "Usage: number_to_words " + LABELS "${MODULE}" +) + +add_test( + NAME ${prefix}_extra_argument + COMMAND ${APPLICATION} "123" "456" +) +set_tests_properties(${prefix}_extra_argument PROPERTIES + PASS_REGULAR_EXPRESSION "Usage: number_to_words " + LABELS "${MODULE}" +) + +# Граничные значения +add_test( + NAME ${prefix}_convert_min_int + COMMAND ${APPLICATION} "-2147483648" +) +set_tests_properties(${prefix}_convert_min_int PROPERTIES + PASS_REGULAR_EXPRESSION "negative two billion one hundred forty seven million four hundred eighty three thousand six hundred forty eight" + LABELS "${MODULE}" +) + +add_test( + NAME ${prefix}_convert_max_int + COMMAND ${APPLICATION} "2147483647" +) +set_tests_properties(${prefix}_convert_max_int PROPERTIES + PASS_REGULAR_EXPRESSION "two billion one hundred forty seven million four hundred eighty three thousand six hundred forty seven" + LABELS "${MODULE}" +) \ No newline at end of file diff --git a/modules/nedelin_d_number_to_words_lab3/application/Application.cpp b/modules/nedelin_d_number_to_words_lab3/application/Application.cpp new file mode 100644 index 00000000..3832db01 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/application/Application.cpp @@ -0,0 +1,14 @@ +// Copyright 2024 Nedelin Dmitry + +#include + +#include "include/number_to_wordsApp.h" + +int main(int argc, const char** argv) { + Application app; + auto output = app.runApp(argc, argv); + for (auto str : output) { + std::cout << str << std::endl; + } + return 0; +} diff --git a/modules/nedelin_d_number_to_words_lab3/application/CMakeLists.txt b/modules/nedelin_d_number_to_words_lab3/application/CMakeLists.txt new file mode 100644 index 00000000..e8a53c3d --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/application/CMakeLists.txt @@ -0,0 +1,15 @@ +set(target ${APPLICATION}) + +file(GLOB srcs "*.cpp") +set_source_files_properties(${srcs} PROPERTIES + LABELS "${MODULE};Application") + +add_executable(${target} ${srcs}) +set_target_properties(${target} PROPERTIES + OUTPUT_NAME ${MODULE} + LABELS "${MODULE};Application") + +target_link_libraries(${target} ${LIBRARY}) +if (UNIX) + target_link_libraries(${target} ${CMAKE_THREAD_LIBS_INIT}) +endif (UNIX) diff --git a/modules/nedelin_d_number_to_words_lab3/include/number_to_words.h b/modules/nedelin_d_number_to_words_lab3/include/number_to_words.h new file mode 100644 index 00000000..2bb7cf52 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/include/number_to_words.h @@ -0,0 +1,16 @@ +// Copyright 2024 Nedelin Dmitry + +#ifndef MODULES_NEDELIN_D_NUMBER_TO_WORDS_LAB3_INCLUDE_NUMBER_TO_WORDS_H_ +#define MODULES_NEDELIN_D_NUMBER_TO_WORDS_LAB3_INCLUDE_NUMBER_TO_WORDS_H_ + +#include + +class NumberToWords { + public: + std::string convert(int number); + + private: + std::string convertLessThanOneThousand(int number); +}; + +#endif // MODULES_NEDELIN_D_NUMBER_TO_WORDS_LAB3_INCLUDE_NUMBER_TO_WORDS_H_ diff --git a/modules/nedelin_d_number_to_words_lab3/include/number_to_wordsApp.h b/modules/nedelin_d_number_to_words_lab3/include/number_to_wordsApp.h new file mode 100644 index 00000000..855004a2 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/include/number_to_wordsApp.h @@ -0,0 +1,17 @@ +// Copyright 2024 Nedelin Dmitry + +#ifndef MODULES_NEDELIN_D_NUMBER_TO_WORDS_LAB3_INCLUDE_NUMBER_TO_WORDSAPP_H_ +#define MODULES_NEDELIN_D_NUMBER_TO_WORDS_LAB3_INCLUDE_NUMBER_TO_WORDSAPP_H_ + +#include +#include + +class Application{ + public: + std::vector runApp(int argc, const char** argv); + + private: + std::string processNumber(const std::string& input); +}; + +#endif // MODULES_NEDELIN_D_NUMBER_TO_WORDS_LAB3_INCLUDE_NUMBER_TO_WORDSAPP_H_ diff --git a/modules/nedelin_d_number_to_words_lab3/src/CMakeLists.txt b/modules/nedelin_d_number_to_words_lab3/src/CMakeLists.txt new file mode 100644 index 00000000..b1dc57b0 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/src/CMakeLists.txt @@ -0,0 +1,18 @@ +set(target ${LIBRARY}) + +file(GLOB srcs "*.cpp") +file(GLOB hdrs "../include/*.h") +set_source_files_properties(${srcs} ${hdrs} PROPERTIES + LABELS "${MODULE};Library") + +add_library(${target} STATIC ${srcs} ${hdrs}) +set_target_properties(${target} PROPERTIES + OUTPUT_NAME ${MODULE} + LABELS "${MODULE};Library") + +if (UNIX) + target_link_libraries(${target} ${CMAKE_THREAD_LIBS_INIT}) +endif (UNIX) +target_link_libraries(${target} ${LIBRARY_DEPS}) + +set(LIBRARY_DEPS "${LIBRARY_DEPS};${target}" PARENT_SCOPE) diff --git a/modules/nedelin_d_number_to_words_lab3/src/number_to_words.cpp b/modules/nedelin_d_number_to_words_lab3/src/number_to_words.cpp new file mode 100644 index 00000000..b7ced8d2 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/src/number_to_words.cpp @@ -0,0 +1,100 @@ +// Copyright 2024 Nedelin Dmitry + +#include "include/number_to_words.h" + +#include +#include +#include + +std::string trim(const std::string& str) { + std::string trimmed = str; + auto end = trimmed.find_last_not_of(' '); + if (end != std::string::npos) { + trimmed.erase(end + 1); + } + return trimmed; +} + +std::string NumberToWords::convert(int number) { + if (number == 0) { + return "zero"; + } + if (number == INT_MIN) { + return "negative two billion one hundred forty seven million four " + "hundred eighty three thousand six hundred forty eight"; + } + + std::stringstream ss; + + if (number < 0) { + ss << "negative "; + number = -number; + } + + static constexpr int BILLION = 1000000000; + static constexpr int MILLION = 1000000; + static constexpr int THOUSAND = 1000; + + if (number >= BILLION) { + ss << convertLessThanOneThousand(number / BILLION) << " billion"; + number %= BILLION; + if (number > 0) { + ss << " "; + } + } + + if (number >= MILLION) { + ss << convertLessThanOneThousand(number / MILLION) << " million"; + number %= MILLION; + if (number > 0) { + ss << " "; + } + } + + if (number >= THOUSAND) { + ss << convertLessThanOneThousand(number / THOUSAND) << " thousand"; + number %= THOUSAND; + if (number > 0) { + ss << " "; + } + } + + if (number > 0) { + ss << convertLessThanOneThousand(number); + } + + return trim(ss.str()); +} + +std::string NumberToWords::convertLessThanOneThousand(int number) { + static const std::array units{ + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", + "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", + "sixteen", "seventeen", "eighteen", "nineteen" + }; + static const std::array tens{ + "", "", "twenty", "thirty", "forty", "fifty", "sixty", + "seventy", "eighty", "ninety" + }; + + std::stringstream ss; + if (number >= 100) { + ss << units[number / 100] << " hundred"; + number %= 100; + if (number > 0) { + ss << " "; + } + } + if (number >= 20) { + ss << tens[number / 10]; + number %= 10; + if (number > 0) { + ss << " "; + } + } + if (number > 0) { + ss << units[number]; + } + + return ss.str(); +} diff --git a/modules/nedelin_d_number_to_words_lab3/src/number_to_wordsApp.cpp b/modules/nedelin_d_number_to_words_lab3/src/number_to_wordsApp.cpp new file mode 100644 index 00000000..a0ed37dd --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/src/number_to_wordsApp.cpp @@ -0,0 +1,39 @@ +// Copyright 2024 Nedelin Dmitry + +#include "include/number_to_words.h" +#include "include/number_to_wordsApp.h" + +#include +#include + +std::vector Application::runApp(int argc, const char** argv) { + std::vector output; + + if (argc != 2) { + output.push_back("Usage: number_to_words "); + return output; + } + + std::string input = argv[1]; + try { + output.push_back(processNumber(input)); + } + catch (const std::invalid_argument&) { + output.push_back("Invalid number format!"); + } + + return output; +} + +std::string Application::processNumber(const std::string& input) { + int number; + try { + number = std::stoi(input); + } + catch (...) { + throw std::invalid_argument("Invalid input"); + } + + NumberToWords converter; + return converter.convert(number); +} diff --git a/modules/nedelin_d_number_to_words_lab3/test/CMakeLists.txt b/modules/nedelin_d_number_to_words_lab3/test/CMakeLists.txt new file mode 100644 index 00000000..cd06c57b --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/test/CMakeLists.txt @@ -0,0 +1,27 @@ +set(target ${TESTS}) + +file(GLOB srcs "*.cpp") +set_source_files_properties(${srcs} PROPERTIES + LABELS "${MODULE};Test") + +add_executable(${target} ${srcs} ${hdrs}) +set_target_properties(${target} PROPERTIES + LABELS "${MODULE};Test") + +if (UNIX) + target_link_libraries(${target} gtest ${CMAKE_THREAD_LIBS_INIT} pthread) +endif (UNIX) +target_link_libraries(${target} gtest ${LIBRARY}) + +# VS2012 doesn't support correctly the tuples yet, +# see http://code.google.com/p/googletest/issues/detail?id=412 +if(MSVC) + target_compile_definitions(${target} PUBLIC _VARIADIC_MAX=10) +endif() + +add_test( + NAME ${MODULE}_gtest + COMMAND ${target} +) +set_tests_properties (${MODULE}_gtest PROPERTIES + LABELS "${MODULE}") diff --git a/modules/nedelin_d_number_to_words_lab3/test/test_main.cpp b/modules/nedelin_d_number_to_words_lab3/test/test_main.cpp new file mode 100644 index 00000000..ffd4f722 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/test/test_main.cpp @@ -0,0 +1,9 @@ +// Copyright 2024 Nedelin Dmitry + +#include +#include "include/number_to_wordsApp.h" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/modules/nedelin_d_number_to_words_lab3/test/test_number_to_words.cpp b/modules/nedelin_d_number_to_words_lab3/test/test_number_to_words.cpp new file mode 100644 index 00000000..ce8abb80 --- /dev/null +++ b/modules/nedelin_d_number_to_words_lab3/test/test_number_to_words.cpp @@ -0,0 +1,118 @@ +// Copyright 2024 Nedelin Dmitry + +#include +#include + +#include "include/number_to_words.h" +#include "include/number_to_wordsApp.h" + +TEST(NumberToWordsTest, ConvertZero) { + NumberToWords converter; + ASSERT_EQ(converter.convert(0), "zero"); +} + +TEST(NumberToWordsTest, ConvertPositiveNumbers) { + NumberToWords converter; + ASSERT_EQ(converter.convert(1), "one"); + ASSERT_EQ(converter.convert(12), "twelve"); + ASSERT_EQ(converter.convert(123), "one hundred twenty three"); + ASSERT_EQ(converter.convert(1234), + "one thousand two hundred thirty four"); + ASSERT_EQ(converter.convert(1234567), + "one million two hundred thirty four thousand " + "five hundred sixty seven"); +} + +TEST(NumberToWordsTest, ConvertNegativeNumbers) { + NumberToWords converter; + ASSERT_EQ(converter.convert(-1), "negative one"); + ASSERT_EQ(converter.convert(-12), "negative twelve"); + ASSERT_EQ(converter.convert(-123), "negative one hundred twenty three"); +} + +TEST(NumberToWordsTest, ConvertLargeNumbers) { + NumberToWords converter; + ASSERT_EQ(converter.convert(1000000000), "one billion"); + ASSERT_EQ(converter.convert(1172839450), + "one billion one hundred seventy two million eight hundred " + "thirty nine thousand four hundred fifty"); +} + +TEST(NumberToWordsTest, ConvertNegativeLargeNumbers) { + NumberToWords converter; + ASSERT_EQ(converter.convert(-1000000000), "negative one billion"); + ASSERT_EQ(converter.convert(-1172839450), + "negative one billion one hundred seventy two million " + "eight hundred thirty nine thousand four hundred fifty"); +} + +TEST(NumberToWordsTest, ConvertEdgeCases) { + NumberToWords converter; + ASSERT_EQ(converter.convert(0), "zero"); + ASSERT_EQ(converter.convert(INT_MAX), + "two billion one hundred forty seven million four hundred " + "eighty three thousand six hundred forty seven"); + ASSERT_EQ(converter.convert(INT_MIN), + "negative two billion one hundred forty seven million four " + "hundred eighty three thousand six hundred forty eight"); +} + +TEST(NumberToWordsTest, ConvertTensAndUnits) { + NumberToWords converter; + ASSERT_EQ(converter.convert(20), "twenty"); + ASSERT_EQ(converter.convert(99), "ninety nine"); + ASSERT_EQ(converter.convert(101), "one hundred one"); + ASSERT_EQ(converter.convert(111), "one hundred eleven"); +} + +TEST(ApplicationTest, ValidInput_PositiveNumber) { + Application app; + const char* argv[] = { "app", "123" }; + auto output = app.runApp(2, argv); + ASSERT_EQ(output.size(), static_cast(1)); + EXPECT_EQ(output[0], "one hundred twenty three"); +} + +TEST(ApplicationTest, ValidInput_NegativeNumber) { + Application app; + const char* argv[] = { "app", "-456" }; + auto output = app.runApp(2, argv); + ASSERT_EQ(output.size(), static_cast(1)); + EXPECT_EQ(output[0], "negative four hundred fifty six"); +} + +TEST(ApplicationTest, InvalidInput_NonNumeric) { + Application app; + const char* argv[] = { "app", "abc" }; + auto output = app.runApp(2, argv); + ASSERT_EQ(output.size(), static_cast(1)); + EXPECT_EQ(output[0], "Invalid number format!"); +} + +TEST(ApplicationTest, MissingArgument) { + Application app; + const char* argv[] = { "app" }; + auto output = app.runApp(1, argv); + ASSERT_EQ(output.size(), static_cast(1)); + EXPECT_EQ(output[0], "Usage: number_to_words "); +} + +TEST(ApplicationTest, Boundary_MinInt) { + Application app; + const char* argv[] = { "app", "-2147483648" }; + auto output = app.runApp(2, argv); + ASSERT_EQ(output.size(), static_cast(1)); + EXPECT_EQ(output[0], + "negative two billion one hundred forty seven million four " + "hundred eighty three thousand six hundred forty eight"); +} + +TEST(ApplicationTest, Boundary_MaxInt) { + Application app; + const char* argv[] = { "app", "2147483647" }; + auto output = app.runApp(2, argv); + ASSERT_EQ(output.size(), static_cast(1)); + EXPECT_EQ(output[0], + "two billion one hundred forty seven million four hundred " + "eighty three thousand six hundred forty seven"); +}