diff --git a/CMakeLists.txt b/CMakeLists.txt index b3d20a8..d895331 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,7 @@ include_directories(shared) ##### subdirectories ##### -set(DIRS week-W week-2 week-3 week-4 week-5 week-6 week-7 week-8 week-9 week-10 week-11) +set(DIRS week-W week-2 week-3 week-4 week-5 week-6 week-7 week-8 week-9 week-10 week-11 week-15) foreach(DIR ${DIRS}) add_subdirectory(${DIR}) endforeach() diff --git a/week-15/CMakeLists.txt b/week-15/CMakeLists.txt new file mode 100644 index 0000000..139200d --- /dev/null +++ b/week-15/CMakeLists.txt @@ -0,0 +1,4 @@ +set(DIRS task-12-14 task-12-15 task-12-20) +foreach(DIR ${DIRS}) + add_subdirectory(${DIR}) +endforeach() diff --git a/week-15/task-12-14/CMakeLists.txt b/week-15/task-12-14/CMakeLists.txt new file mode 100644 index 0000000..0857059 --- /dev/null +++ b/week-15/task-12-14/CMakeLists.txt @@ -0,0 +1,3 @@ +include_directories(include) + +add_subdirectory(test) diff --git a/week-15/task-12-14/include/car_numbers.hpp b/week-15/task-12-14/include/car_numbers.hpp new file mode 100644 index 0000000..32887c7 --- /dev/null +++ b/week-15/task-12-14/include/car_numbers.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include +#include + +std::vector rus_car_numbers(const std::string& str) { + const std::regex pattern(R"(\b[ABEKMHOPCTYX]\d{3}[ABEKMHOPCTYX]{2}\b)"); + std::vector numbers; + std::ranges::for_each(std::sregex_iterator(std::cbegin(str), std::cend(str), pattern), + std::sregex_iterator(), + [&numbers](const auto& matches) { numbers.push_back(matches[0]); }); + return numbers; +} diff --git a/week-15/task-12-14/test/CMakeLists.txt b/week-15/task-12-14/test/CMakeLists.txt new file mode 100644 index 0000000..ad6db21 --- /dev/null +++ b/week-15/task-12-14/test/CMakeLists.txt @@ -0,0 +1 @@ +cpp_test(test-12-14.cpp) diff --git a/week-15/task-12-14/test/test-12-14.cpp b/week-15/task-12-14/test/test-12-14.cpp new file mode 100644 index 0000000..22a6ba9 --- /dev/null +++ b/week-15/task-12-14/test/test-12-14.cpp @@ -0,0 +1,80 @@ +#include "car_numbers.hpp" +#include "gtest/gtest.h" + +TEST(RusCarNumbersTest, EmptyInput) { + EXPECT_EQ(rus_car_numbers(""), std::vector({})); +} + +TEST(RusCarNumbersTest, NoNumbersInText) { + const std::string text = R"( + Just text without license plates. + Examples: 12345, ABCDEF, !@#$%^&* + Similar looking: AB12CD, X9Y9Z9 + )"; + EXPECT_EQ(rus_car_numbers(text), std::vector({})); +} + +TEST(RusCarNumbersTest, ValidNumbersWithoutRegion) { + const std::string text = R"( + Correct plates: A123BC, E555EE, K321AM + Parking spots: M111MM and T444YX + With trash: A123BC45 -> A123BC + Almost valid: X999XZ -> invalid (Z) + )"; + EXPECT_EQ(rus_car_numbers(text), std::vector({"A123BC", "E555EE", "K321AM", + "M111MM", "T444YX", "A123BC"})); +} + +TEST(RusCarNumbersTest, InvalidFormats) { + const std::string text = R"( + Invalid: + ABC123 (wrong order) + 123ABC (starts with digits) + A1B2C3 (alternating) + A123BC45 (with region, but takes A123BC) + A12BC (too short) + Z999ZZ (invalid letter Z) + )"; + EXPECT_EQ(rus_car_numbers(text), std::vector({"A123BC"})); +} + +TEST(RusCarNumbersTest, MixedContent) { + const std::string text = R"( + Valid: A777BC and C666CT + Invalid: AB123C, X9Y9Z9 + With region: T123TX42 -> T123TX + Trash: !Y222YA@ + Almost valid: O999OI -> invalid (I) + )"; + EXPECT_EQ(rus_car_numbers(text), + std::vector({"A777BC", "C666CT", "T123TX", "Y222YA"})); +} + +TEST(RusCarNumbersTest, EdgeCases) { + const std::string text = R"( + Edge cases: + A001AX (minimum) + X999XX (maximum) + B000TB (zeros in digits) + H555HH (same letters) + Almost valid: + A000AA0 (extra zero) + Y123YF (invalid F) + )"; + EXPECT_EQ(rus_car_numbers(text), + std::vector({"A001AX", "X999XX", "B000TB", "H555HH"})); +} + +TEST(RusCarNumbersTest, AllValidLettersCombinations) { + const std::string text = R"( + All valid letters: + A123BC, B456TE, E789KX + K321AM, M111MO, H555OP + O999PC, P777CT, C666TY + T444YX, Y222XA, X888AB + )"; + EXPECT_EQ( + rus_car_numbers(text), + std::vector({"A123BC", "B456TE", "E789KX", "K321AM", "M111MO", "H555OP", + "O999PC", "P777CT", "C666TY", "T444YX", "Y222XA", "X888AB"})); +} \ No newline at end of file diff --git a/week-15/task-12-15/CMakeLists.txt b/week-15/task-12-15/CMakeLists.txt new file mode 100644 index 0000000..0857059 --- /dev/null +++ b/week-15/task-12-15/CMakeLists.txt @@ -0,0 +1,3 @@ +include_directories(include) + +add_subdirectory(test) diff --git a/week-15/task-12-15/include/emails.hpp b/week-15/task-12-15/include/emails.hpp new file mode 100644 index 0000000..7095251 --- /dev/null +++ b/week-15/task-12-15/include/emails.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include +#include + +std::vector emails(const std::string& str) { + const std::regex pattern(R"(\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{1,}\b)"); + std::vector emails; + std::ranges::for_each(std::sregex_iterator(std::cbegin(str), std::cend(str), pattern), + std::sregex_iterator(), + [&emails](const auto& matches) { emails.push_back(matches[0]); }); + return emails; +} diff --git a/week-15/task-12-15/test/CMakeLists.txt b/week-15/task-12-15/test/CMakeLists.txt new file mode 100644 index 0000000..5629cf8 --- /dev/null +++ b/week-15/task-12-15/test/CMakeLists.txt @@ -0,0 +1 @@ +cpp_test(test-12-15.cpp) diff --git a/week-15/task-12-15/test/test-12-15.cpp b/week-15/task-12-15/test/test-12-15.cpp new file mode 100644 index 0000000..12611dc --- /dev/null +++ b/week-15/task-12-15/test/test-12-15.cpp @@ -0,0 +1,65 @@ +#include "emails.hpp" +#include "gtest/gtest.h" + +TEST(EmailExtractionTest, EmptyInput) { EXPECT_EQ(emails(""), std::vector({})); } + +TEST(EmailExtractionTest, NoEmailsInText) { + const std::string text = R"( + This is just regular text without any emails. + Some symbols: @hello, test@, @test.com + Numbers: 12345, random!text + )"; + EXPECT_EQ(emails(text), std::vector({})); +} + +TEST(EmailExtractionTest, ValidEmails) { + const std::string text = R"( + Contact us at: john.doe@example.com or support@company.org. + Student emails: student_2023@university.edu, a.b@c.d.xyz + Special cases: email+filter@gmail.com, name@sub.domain.co.uk + )"; + EXPECT_EQ(emails(text), + std::vector({"john.doe@example.com", "support@company.org", + "student_2023@university.edu", "a.b@c.d.xyz", + "email+filter@gmail.com", "name@sub.domain.co.uk"})); +} + +TEST(EmailExtractionTest, InvalidEmails) { + const std::string text = R"( + Invalid formats: + plaintext + @missing.start + missing@at + double@@sign.com + invalid@chars_here.com + missing@tld. + )"; + EXPECT_EQ(emails(text), std::vector({})); +} + +TEST(EmailExtractionTest, MixedContent) { + const std::string text = R"( + Valid: contact@valid.com, another@good.org + Invalid: bad@email, @wrong.com + Almost valid: almost@valid. but with space + Trash: !@#$%^&* + Valid but tricky: "spaces@around.com" (should extract without quotes) + )"; + EXPECT_EQ(emails(text), std::vector( + {"contact@valid.com", "another@good.org", "spaces@around.com"})); +} + +TEST(EmailExtractionTest, EdgeCases) { + const std::string text = R"( + Minimal: a@b.c + Long domain: email@sub.sub2.sub3.example.com + Special chars: user+filter@domain.com + Numbers: 1234@numbers.com + Hyphens: hyphen-ated@domain-name.com + Quotes: "email@inside.quotes" + )"; + EXPECT_EQ(emails(text), + std::vector({"a@b.c", "email@sub.sub2.sub3.example.com", + "user+filter@domain.com", "1234@numbers.com", + "hyphen-ated@domain-name.com", "email@inside.quotes"})); +} diff --git a/week-15/task-12-20/CMakeLists.txt b/week-15/task-12-20/CMakeLists.txt new file mode 100644 index 0000000..0857059 --- /dev/null +++ b/week-15/task-12-20/CMakeLists.txt @@ -0,0 +1,3 @@ +include_directories(include) + +add_subdirectory(test) diff --git a/week-15/task-12-20/include/calculator.hpp b/week-15/task-12-20/include/calculator.hpp new file mode 100644 index 0000000..6405a51 --- /dev/null +++ b/week-15/task-12-20/include/calculator.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +inline bool is_operator(const std::string& str) { + if (str.size() != 1) { + return false; + } + const char c = str[0]; + return c == '+' || c == '-' || c == '*' || c == '/'; +} + +inline bool may_be_number(const std::string& str) { + return (str[0] == '-' && str.size() > 1) || static_cast(std::isdigit(str[0])); +} + +double calculate(const std::string& str) { + if (str.empty()) { + throw std::invalid_argument("empty string"); + } + std::stack stack; + std::istringstream iss(str); + std::string token; + while (iss >> token) { + assert(!token.empty()); + if (may_be_number(token)) { + stack.push(std::stod(token)); + } else if (is_operator(token)) { + if (stack.size() < 2) { + throw std::invalid_argument("too few arguments for operation"); + } + const double b = stack.top(); + stack.pop(); + const double a = stack.top(); + stack.pop(); + switch (token[0]) { + case '+': + stack.push(a + b); + break; + case '-': + stack.push(a - b); + break; + case '*': + stack.push(a * b); + break; + case '/': + stack.push(a / b); + break; + default: + throw std::invalid_argument("unexpected operator"); + } + } else { + throw std::invalid_argument("unexpected token"); + } + } + return stack.top(); +} diff --git a/week-15/task-12-20/test/CMakeLists.txt b/week-15/task-12-20/test/CMakeLists.txt new file mode 100644 index 0000000..ccd0836 --- /dev/null +++ b/week-15/task-12-20/test/CMakeLists.txt @@ -0,0 +1 @@ +cpp_test(test-12-20.cpp) diff --git a/week-15/task-12-20/test/test-12-20.cpp b/week-15/task-12-20/test/test-12-20.cpp new file mode 100644 index 0000000..9b12a4c --- /dev/null +++ b/week-15/task-12-20/test/test-12-20.cpp @@ -0,0 +1,63 @@ +#include "calculator.hpp" +#include "gtest/gtest.h" + +TEST(RPNCalculatorTest, BasicOperations) { + EXPECT_DOUBLE_EQ(calculate("3 4 +"), 7.0); + EXPECT_DOUBLE_EQ(calculate("5 2 -"), 3.0); + EXPECT_DOUBLE_EQ(calculate("2 3 *"), 6.0); + EXPECT_DOUBLE_EQ(calculate("6 2 /"), 3.0); +} + +TEST(RPNCalculatorTest, FloatingPointOperations) { + EXPECT_DOUBLE_EQ(calculate("3.5 4.2 +"), 7.7); + EXPECT_DOUBLE_EQ(calculate("5.1 2.3 -"), 2.8); + EXPECT_DOUBLE_EQ(calculate("2.5 3.2 *"), 8.0); + EXPECT_DOUBLE_EQ(calculate("7.5 2.5 /"), 3.0); +} + +TEST(RPNCalculatorTest, ComplexExpressions) { + EXPECT_DOUBLE_EQ(calculate("3 4 2 * +"), 11.0); + EXPECT_DOUBLE_EQ(calculate("5 1 2 + 4 * + 3 -"), 14.0); + EXPECT_DOUBLE_EQ(calculate("4 2 5 * + 1 3 - /"), -7.0); +} + +TEST(RPNCalculatorTest, EdgeCases) { + EXPECT_DOUBLE_EQ(calculate("0 5 +"), 5.0); + EXPECT_DOUBLE_EQ(calculate("1 1 -"), 0.0); + EXPECT_DOUBLE_EQ(calculate("999 1 +"), 1000.0); + EXPECT_DOUBLE_EQ(calculate("-5 3 +"), -2.0); +} + +TEST(RPNCalculatorTest, InvalidInput) { + EXPECT_THROW(calculate(""), std::invalid_argument); + EXPECT_THROW(calculate("3 +"), std::invalid_argument); + EXPECT_THROW(calculate("3 4 + -"), std::invalid_argument); + EXPECT_THROW(calculate("a b +"), std::invalid_argument); +} + +TEST(RPNCalculatorTest, WhitespaceHandling) { + EXPECT_DOUBLE_EQ(calculate(" 3 4 + "), 7.0); + EXPECT_DOUBLE_EQ(calculate("3\n4\t+"), 7.0); + EXPECT_DOUBLE_EQ(calculate("3 4 + "), 7.0); +} + +TEST(RPNCalculatorTest, MultiDigitNumbers) { + EXPECT_DOUBLE_EQ(calculate("10 20 +"), 30.0); + EXPECT_DOUBLE_EQ(calculate("100 50 -"), 50.0); + EXPECT_DOUBLE_EQ(calculate("12 34 *"), 408.0); + EXPECT_DOUBLE_EQ(calculate("100 5 /"), 20.0); +} + +TEST(RPNCalculatorTest, PrecisionHandling) { + EXPECT_DOUBLE_EQ(calculate("0.1 0.2 +"), 0.3); + EXPECT_DOUBLE_EQ(calculate("1.234 2.345 +"), 3.579); + EXPECT_NEAR(calculate("1.0 3.0 /"), 0.333333, 1e-6); + EXPECT_NEAR(calculate("2.0 3.0 /"), 0.666666, 1e-6); +} + +TEST(RPNCalculatorTest, MixedIntegerAndFloatingPoint) { + EXPECT_DOUBLE_EQ(calculate("3 4.5 +"), 7.5); + EXPECT_DOUBLE_EQ(calculate("5.2 2 -"), 3.2); + EXPECT_DOUBLE_EQ(calculate("2 3.5 *"), 7.0); + EXPECT_DOUBLE_EQ(calculate("6.6 2.2 /"), 3.0); +}